├── .nvmrc ├── examples └── design │ ├── css │ └── webflow.css │ ├── js │ └── webflow.js │ ├── fonts │ └── Avenir-Book.ttf │ ├── images │ └── webflow.jpg │ └── index.html ├── .eslintrc ├── test ├── mocha.opts └── integration │ ├── create.js │ ├── generate.js │ └── update.js ├── .eslintignore ├── lib ├── create │ ├── templates │ │ ├── .gitignore │ │ ├── .eslintrc │ │ ├── .eslintignore │ │ ├── client │ │ │ ├── imports │ │ │ │ ├── custom │ │ │ │ │ ├── index.js │ │ │ │ │ └── components │ │ │ │ │ │ └── App.jsx │ │ │ │ └── generated │ │ │ │ │ └── components │ │ │ │ │ └── App.jsx │ │ │ ├── main.html │ │ │ └── main.jsx │ │ ├── .travis.yml │ │ └── tests │ │ │ └── app-should-exist.js │ ├── copy-boilerplate.js │ ├── log-instruction.js │ ├── create-meteor.js │ ├── install-meteor-dependencies.js │ ├── index.js │ ├── add-script-commands.js │ └── install-npm-dependencies.js ├── plugins │ ├── apollo │ │ ├── templates │ │ │ ├── client │ │ │ │ └── index.js │ │ │ ├── server │ │ │ │ └── index.js │ │ │ └── imports │ │ │ │ ├── apollo │ │ │ │ ├── mocks.js │ │ │ │ ├── schema.js │ │ │ │ ├── connectors.js │ │ │ │ └── resolvers.js │ │ │ │ └── startup │ │ │ │ ├── client │ │ │ │ └── index.js │ │ │ │ └── server │ │ │ │ └── index.js │ │ ├── index.js │ │ └── after-create.js │ └── index.js ├── helpers │ ├── shelljs.js │ ├── log-task.js │ ├── read-one-html.js │ └── regenerate-folder-in-public.js ├── update │ ├── copy-images.js │ ├── index.js │ ├── run-reacterminator.js │ ├── unzip-design.js │ ├── check-dot-design-dir.js │ ├── copy-css.js │ └── copy-fonts.js └── generate.js ├── .gitignore ├── bin ├── stanza-update.js ├── stanza-generate.js ├── stanza-create.js └── stanza.js ├── .travis.yml ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.1.0 2 | -------------------------------------------------------------------------------- /examples/design/css/webflow.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/design/js/webflow.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/design/fonts/Avenir-Book.ttf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/design/images/webflow.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "poetic" 3 | } 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --timeout 600000 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | templates 3 | test/example 4 | -------------------------------------------------------------------------------- /lib/create/templates/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .design/ 3 | -------------------------------------------------------------------------------- /lib/create/templates/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "poetic" 3 | } 4 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/client/index.js: -------------------------------------------------------------------------------- 1 | import '../imports/startup/client/index'; 2 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/server/index.js: -------------------------------------------------------------------------------- 1 | import '../imports/startup/server/index'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | npm-debug.log 4 | doc 5 | examples/example 6 | .coveralls.yml 7 | -------------------------------------------------------------------------------- /lib/create/templates/.eslintignore: -------------------------------------------------------------------------------- 1 | .design/ 2 | node_modules/ 3 | client/js/ 4 | client/imports/generated/ 5 | -------------------------------------------------------------------------------- /lib/helpers/shelljs.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | 3 | shell.config.verbose = true; 4 | 5 | module.exports = shell; 6 | -------------------------------------------------------------------------------- /lib/plugins/apollo/index.js: -------------------------------------------------------------------------------- 1 | const afterCreate = require('./after-create'); 2 | 3 | module.exports = { 4 | afterCreate, 5 | }; 6 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/apollo/mocks.js: -------------------------------------------------------------------------------- 1 | const mocks = { 2 | String: () => "It works!", 3 | }; 4 | 5 | export default mocks; 6 | -------------------------------------------------------------------------------- /lib/create/templates/client/imports/custom/index.js: -------------------------------------------------------------------------------- 1 | import ComponentsApp from './components/App'; 2 | 3 | export default { 4 | 'components/App': ComponentsApp, 5 | }; 6 | -------------------------------------------------------------------------------- /lib/helpers/log-task.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const chalk = require('chalk'); 4 | 5 | module.exports = function logTask(task) { 6 | console.log(chalk.green.bold(`===== ${task}`)); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/update/copy-images.js: -------------------------------------------------------------------------------- 1 | const regenerateFolderInPublic = require('../helpers/regenerate-folder-in-public'); 2 | 3 | module.exports = function copyImages() { 4 | regenerateFolderInPublic('images'); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/apollo/schema.js: -------------------------------------------------------------------------------- 1 | const typeDefinitions = ` 2 | type Query { 3 | testString: String 4 | } 5 | 6 | schema { 7 | query: Query 8 | } 9 | `; 10 | 11 | export default [typeDefinitions]; 12 | -------------------------------------------------------------------------------- /lib/create/templates/client/main.html: -------------------------------------------------------------------------------- 1 | 2 | App 3 | 4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /lib/helpers/read-one-html.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const fs = require('fs'); 3 | 4 | module.exports = function readOneHtml() { 5 | const firstHtmlFilePath = glob.sync('.design/*.html')[0]; 6 | return fs.readFileSync(firstHtmlFilePath, 'utf-8'); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/startup/client/index.js: -------------------------------------------------------------------------------- 1 | import ApolloClient from 'apollo-client'; 2 | import { meteorClientConfig } from 'meteor/apollo'; 3 | import { Meteor } from 'meteor/meteor'; 4 | 5 | Meteor.startup(() => { 6 | const client = new ApolloClient(meteorClientConfig()); 7 | }); 8 | -------------------------------------------------------------------------------- /lib/create/templates/client/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meteor } from 'meteor/meteor'; 3 | import { render } from 'react-dom'; 4 | import App from './imports/generated/components/App.jsx'; 5 | 6 | Meteor.startup(() => { 7 | render(, document.getElementById('render-target')); 8 | }); 9 | -------------------------------------------------------------------------------- /lib/create/templates/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "5" 5 | before_install: 6 | - curl https://install.meteor.com | /bin/sh 7 | - export PATH="$HOME/.meteor:$PATH" 8 | - npm install -g chimp 9 | before_script: 10 | - nohup bash -c "meteor 2>&1 &" && sleep 30; cat nohup.out 11 | services: 12 | - mongodb 13 | -------------------------------------------------------------------------------- /bin/stanza-update.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | const program = require('commander'); 5 | const update = require('../lib/update/index'); 6 | 7 | program.on('--help', () => { 8 | console.log('Update the meteor project using the design.zip from webflow'); 9 | }); 10 | 11 | program.parse(process.argv); 12 | 13 | update(); 14 | -------------------------------------------------------------------------------- /lib/create/copy-boilerplate.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { cp } = require('../helpers/shelljs'); 3 | const logTask = require('../helpers/log-task'); 4 | 5 | module.exports = function copyBoilerplate() { 6 | // copy boilerplate 7 | logTask('Copy boilerplate'); 8 | const templatesPath = path.resolve(__dirname, './templates'); 9 | cp('-R', `${templatesPath}/.`, './'); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/helpers/regenerate-folder-in-public.js: -------------------------------------------------------------------------------- 1 | const logTask = require('../helpers/log-task'); 2 | const { mkdir, rm, cp } = require('../helpers/shelljs'); 3 | 4 | module.exports = function regenerateFolderInPublic(folder) { 5 | logTask(`Regenerate ${folder}`); 6 | rm('-rf', `public/${folder}`); 7 | mkdir('-p', `public/${folder}`); 8 | cp(`.design/${folder}/*`, `public/${folder}`); 9 | }; 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | before_install: 9 | - curl https://install.meteor.com | /bin/sh 10 | - export PATH="$HOME/.meteor:$PATH" 11 | before_script: 12 | - npm prune 13 | after_success: 14 | - npm run semantic-release 15 | branches: 16 | except: 17 | - /^v\d+\.\d+\.\d+$/ 18 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/apollo/connectors.js: -------------------------------------------------------------------------------- 1 | class SampleConnector { 2 | constructor(){ 3 | this.store = new Map() 4 | } 5 | 6 | get(key){ 7 | return this.store.get(key); 8 | } 9 | 10 | set(key, value){ 11 | return this.store.set(key, value); 12 | } 13 | } 14 | 15 | const connectors = { 16 | sample: SampleConnector 17 | }; 18 | 19 | export default connectors; 20 | -------------------------------------------------------------------------------- /bin/stanza-generate.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | const program = require('commander'); 5 | const generate = require('../lib/generate'); 6 | 7 | program 8 | .arguments('') 9 | .action(generate); 10 | 11 | program.on('--help', () => { 12 | console.log('Create a meteor project with react and redux configured.'); 13 | }); 14 | 15 | program.parse(process.argv); 16 | -------------------------------------------------------------------------------- /lib/create/log-instruction.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk'); 3 | 4 | module.exports = function logInstruction(projectName) { 5 | console.log('\n==============================================\n'); 6 | console.log(chalk.red('TODO:')); 7 | console.log(chalk.green(` 8 | 1. cd ${projectName} 9 | 2. copy webflow zip file here as design.zip 10 | 3. stanza update 11 | `)); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/create/templates/client/imports/generated/components/App.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react' 3 | import custom from '../../custom/index'; 4 | 5 | export default class App extends React.Component { 6 | render () { 7 | return ( 8 |
FIND ME AT client/imports/generated/components/App.jsx
9 | ) 10 | } 11 | } 12 | 13 | const customize = custom['components/App'] || ((x) =>x); 14 | 15 | export default customize(App); 16 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/startup/server/index.js: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from 'meteor/apollo'; 2 | import schema from '../../apollo/schema'; 3 | import mocks from '../../apollo/mocks'; 4 | import resolvers from '../../apollo/resolvers'; 5 | import { Meteor } from 'meteor/meteor'; 6 | 7 | Meteor.startup(() => { 8 | createApolloServer({ 9 | graphiql: true, 10 | pretty: true, 11 | schema, 12 | resolvers, 13 | mocks, 14 | }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /lib/plugins/apollo/templates/imports/apollo/resolvers.js: -------------------------------------------------------------------------------- 1 | /* Your connectors are defined in connector.js and used here to retrieve your 2 | * data */ 3 | import Connectors from './connectors'; 4 | 5 | const resolveFunctions = { 6 | /*RootQuery: { 7 | Add RootQuery code here this must be defined in your schema 8 | }, 9 | RootMutation: { 10 | Add RootMutation code here this must be defined in your schema 11 | }, 12 | */ 13 | } 14 | 15 | export default resolveFunctions; 16 | -------------------------------------------------------------------------------- /lib/create/create-meteor.js: -------------------------------------------------------------------------------- 1 | const { exec, mkdir, rm, cd } = require('../helpers/shelljs'); 2 | const logTask = require('../helpers/log-task'); 3 | 4 | module.exports = function createMeteor(projectName) { 5 | // create meteor project 6 | logTask('Create meteor project'); 7 | exec(`meteor create ${projectName}`); 8 | cd(projectName); 9 | 10 | logTask('Clean client and server'); 11 | rm('-rf', './client', './server'); 12 | mkdir('./client', './server'); 13 | 14 | logTask('Install dependencies'); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/update/index.js: -------------------------------------------------------------------------------- 1 | const unzipDesign = require('./unzip-design'); 2 | const copyImages = require('./copy-images'); 3 | const copyCss = require('./copy-css'); 4 | const copyFonts = require('./copy-fonts'); 5 | const runReacterminator = require('./run-reacterminator'); 6 | const checkDotDesignDir = require('./check-dot-design-dir'); 7 | 8 | module.exports = function update() { 9 | unzipDesign(); 10 | 11 | checkDotDesignDir(); 12 | 13 | copyImages(); 14 | 15 | copyCss(); 16 | 17 | copyFonts(); 18 | 19 | runReacterminator(); 20 | }; 21 | -------------------------------------------------------------------------------- /bin/stanza-create.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | const program = require('commander'); 5 | const create = require('../lib/create/index'); 6 | 7 | program 8 | .arguments('') 9 | .option('--apollo, -a') 10 | .action((name, options) => { 11 | if (!name) { 12 | console.error('ERROR: No name given!'); 13 | process.exit(1); 14 | } 15 | 16 | create(name, options); 17 | }); 18 | 19 | program.on('--help', () => { 20 | process.exit(0); 21 | }); 22 | 23 | program.parse(process.argv); 24 | -------------------------------------------------------------------------------- /lib/update/run-reacterminator.js: -------------------------------------------------------------------------------- 1 | const logTask = require('../helpers/log-task'); 2 | const reacterminator = require('reacterminator'); 3 | 4 | module.exports = function runReacterminator() { 5 | logTask('Regenerate components via reacterminator'); 6 | reacterminator( 7 | { type: 'path', content: '.design/' }, 8 | { 9 | outputPath: 'client/imports', 10 | changeLinksForParamStore: true, 11 | generateFiles: true, 12 | recursive: true, 13 | overrideFiles: true, 14 | fileToComponent: true, 15 | } 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/create/templates/tests/app-should-exist.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint-disable func-names, prefer-arrow-callback */ 3 | 4 | // These are Chimp globals 5 | /* globals browser assert server */ 6 | 7 | // PLEASE READ: http://guide.meteor.com/testing.html#acceptance-testing 8 | describe('app should exist', function () { 9 | beforeEach(function () { 10 | browser.url('http://localhost:3000'); 11 | }); 12 | 13 | it('can create a list @watch', function () { 14 | const doesExist = browser.waitForExist('render-target'); 15 | 16 | assert.equal(doesExist, true); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/integration/create.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const fs = require('fs'); 3 | const { assert } = require('chai'); 4 | const path = require('path'); 5 | const create = require('../../lib/create'); 6 | const { cd, rm } = require('shelljs'); 7 | 8 | describe('create', () => { 9 | it('create a meteor app', () => { 10 | const examplesPath = path.resolve(__dirname, '../../examples'); 11 | cd(examplesPath); 12 | rm('-rf', 'example'); 13 | 14 | create('example'); 15 | 16 | assert(fs.statSync('client/main.jsx').isFile()); 17 | assert(fs.statSync('.gitignore').isFile()); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lib/update/unzip-design.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const logTask = require('../helpers/log-task'); 3 | const DESIGN_FILE = 'design.zip'; 4 | const { exec, rm } = require('../helpers/shelljs'); 5 | 6 | module.exports = function unzipDesign() { 7 | // check if DESIGN_FILE exists 8 | try { 9 | const hasZipFile = fs.statSync(DESIGN_FILE).isFile(); 10 | if (!hasZipFile) { 11 | return; 12 | } 13 | } catch (e) { 14 | return; 15 | } 16 | 17 | logTask('Regenerate .design/ folder'); 18 | 19 | // create necessary folders 20 | rm('-rf', '.design/'); 21 | exec(`unzip ${DESIGN_FILE} -d .design/`); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/update/check-dot-design-dir.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const chalk = require('chalk'); 4 | const fs = require('fs'); 5 | 6 | module.exports = function checkDotDesignDir() { 7 | // check if DESIGN_FILE exists 8 | try { 9 | const hasDotDesignDir = fs.statSync('.design').isDirectory(); 10 | if (!hasDotDesignDir) { 11 | throw new Error('Can not find design.zip or .design folder'); 12 | } 13 | } catch (e) { 14 | console.log(chalk.red.bold( 15 | 'You need to download webflow zip file here and rename it as design.zip.' 16 | )); 17 | throw new Error('need webflow design.zip file.'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /lib/create/install-meteor-dependencies.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('../helpers/shelljs'); 2 | 3 | module.exports = function installMeteorDependencies() { 4 | // meteor dependencies 5 | const meteorDependenciesToRemove = [ 6 | 'autopublish', 7 | 'insecure', 8 | 'blaze-html-templates', 9 | ]; 10 | exec(`meteor remove ${meteorDependenciesToRemove.join(' ')}`); 11 | 12 | const meteorDependencies = [ 13 | 'accounts-base', 14 | 'static-html', 15 | 'react-meteor-data', 16 | 'aldeed:simple-schema', 17 | 'aldeed:collection2', 18 | 'dburles:collection-helpers', 19 | 'poetic:meteor-subscribe-all', 20 | ]; 21 | exec(`meteor add ${meteorDependencies.join(' ')}`); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/create/index.js: -------------------------------------------------------------------------------- 1 | const createMeteor = require('./create-meteor'); 2 | const installMeteorDependencies = require('./install-meteor-dependencies'); 3 | const installNpmDependencies = require('./install-npm-dependencies'); 4 | const addScriptCommands = require('./add-script-commands'); 5 | const copyBoilerplate = require('./copy-boilerplate'); 6 | const logInstruction = require('./log-instruction'); 7 | const plugins = require('../plugins/index'); 8 | 9 | module.exports = function create(projectName, options) { 10 | createMeteor(projectName); 11 | 12 | installMeteorDependencies(); 13 | 14 | installNpmDependencies(); 15 | 16 | addScriptCommands(); 17 | 18 | copyBoilerplate(); 19 | 20 | plugins.runHook('afterCreate', options); 21 | 22 | logInstruction(); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/generate.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const path = require('path'); 3 | const rt = require('reacterminator'); 4 | const { ls } = require('shelljs'); 5 | 6 | function getMeteorRoot() { 7 | let currentPath = path.resolve('.'); 8 | do { 9 | const paths = ls('-A', currentPath); 10 | const isMeteorRoot = 11 | _.includes(paths, '.meteor') && 12 | _.includes(paths, 'package.json'); 13 | 14 | if (isMeteorRoot) { 15 | return currentPath; 16 | } 17 | 18 | currentPath = path.resolve(currentPath, '..'); 19 | } 20 | while (currentPath !== '/'); 21 | 22 | throw new Error('Ops, can not find your meteor project root directory.'); 23 | } 24 | 25 | module.exports = (rawPath) => rt.generate(rawPath, `${getMeteorRoot()}/client/imports`); 26 | -------------------------------------------------------------------------------- /test/integration/generate.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const fs = require('fs'); 3 | const { assert } = require('chai'); 4 | const path = require('path'); 5 | const generate = require('../../lib/generate'); 6 | const { cd, rm, mkdir, touch } = require('shelljs'); 7 | 8 | describe('generate', () => { 9 | it('generate a component', () => { 10 | const examplesPath = path.resolve(__dirname, '../../examples'); 11 | cd(examplesPath); 12 | rm('-rf', 'example'); 13 | mkdir('-p', './example/.meteor/'); 14 | touch('./example/package.json'); 15 | cd('example'); 16 | 17 | generate('components/components/Signup'); 18 | 19 | assert(fs.statSync('client/imports/custom/index.js').isFile()); 20 | assert(fs.statSync('client/imports/custom/components/Signup.jsx').isFile()); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* List of validPlugins 3 | * Key is the argument ex: -a passed to enable this plugin 4 | * Value is the name of the folder this plugin is in 5 | * * */ 6 | const validPlugins = { 7 | A: 'apollo', 8 | }; 9 | 10 | /* runHook is passed a hookName 'afterCreate' and options from commander 11 | * it will search through all valid plugins and run the hookName passed 12 | * function passing in the options object */ 13 | module.exports = { 14 | runHook(hookName, options = {}) { 15 | Object.keys(validPlugins).forEach((plugin) => { 16 | if (options[plugin]) { 17 | const newPlugin = require(`./${validPlugins[plugin]}/index.js`); 18 | if (typeof(newPlugin[hookName]) === 'function') { 19 | newPlugin[hookName](options); 20 | } 21 | } 22 | }); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /bin/stanza.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | const program = require('commander'); 5 | const { version, description } = require('../package.json'); 6 | 7 | program 8 | .version(version) 9 | .description(description); 10 | 11 | program 12 | .command('create ', 'Create a meteor project with react and redux configured.') 13 | .alias('c'); 14 | 15 | program 16 | .command('update', 'Update a meteor project with design.zip from webflow.') 17 | .alias('u'); 18 | 19 | program 20 | .command('generate', 'Generate a custom file and update custom/index.js.') 21 | .alias('g'); 22 | 23 | program.on('--help', () => { 24 | console.log(' Examples:'); 25 | console.log(''); 26 | console.log(' $ stanza c my-project'); 27 | console.log(' $ stanza u'); 28 | console.log(''); 29 | }); 30 | 31 | program.parse(process.argv); 32 | -------------------------------------------------------------------------------- /lib/plugins/apollo/after-create.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { exec, cp } = require('../../helpers/shelljs'); 3 | const path = require('path'); 4 | const logTask = require('../../helpers/log-task'); 5 | 6 | module.exports = function afterCreate() { 7 | console.log('Installing apollo dependencies'); 8 | const meteorDependencies = ['apollo']; 9 | 10 | exec(`meteor add ${meteorDependencies.join(' ')}`); 11 | 12 | // npm dependencies 13 | const npmDependencies = [ 14 | 'apollo-client', 15 | 'apollo-server@^0.1.1', 16 | 'http-proxy-middleware@^0.15.0', 17 | 'express', 18 | 'graphql', 19 | 'graphql-tag', 20 | 'invariant', 21 | 'react-apollo', 22 | ]; 23 | exec(`meteor npm install --save ${npmDependencies.join(' ')}`); 24 | 25 | logTask('Copy Apollo boilerplate'); 26 | const templatesPath = path.resolve(__dirname, './templates'); 27 | cp('-R', `${templatesPath}/.`, './'); 28 | }; 29 | -------------------------------------------------------------------------------- /lib/create/add-script-commands.js: -------------------------------------------------------------------------------- 1 | const logTask = require('../helpers/log-task'); 2 | const path = require('path'); 3 | const _ = require('lodash'); 4 | const fs = require('fs'); 5 | 6 | module.exports = function addScriptCommands() { 7 | // npm script commands 8 | logTask('Add npm scripts'); 9 | const packageJSONPath = path.resolve('./package.json'); 10 | const packageJSONObject = require(packageJSONPath); // eslint-disable-line global-require 11 | const npmScripts = { 12 | test: 'npm run lint && chimp --mocha --path=tests --browser=phantomjs', 13 | lint: 'eslint . --ext .jsx,.js', 14 | 'lint:quiet': 'eslint . --ext .jsx,.js || true', 15 | fix: 'eslint . --ext .jsx,.js --fix', 16 | watch: 'chimp --ddp=http://localhost:3000 --watch --mocha --path=tests', 17 | }; 18 | 19 | _.extend(packageJSONObject.scripts, npmScripts); 20 | 21 | fs.writeFileSync( 22 | packageJSONPath, `${JSON.stringify(packageJSONObject, null, 2)}\n` 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/create/install-npm-dependencies.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('../helpers/shelljs'); 2 | 3 | module.exports = function installNpmDependencies() { 4 | // dependencies 5 | const npmDependencies = [ 6 | 'lodash', 7 | 'react', 8 | 'react-dom', 9 | 'react-addons-pure-render-mixin', // react-meteor-data depends on this 10 | 'redux', 11 | 'react-redux', 12 | 'redux-thunk', 13 | 'param-store', 14 | 'react-super-components', 15 | ]; 16 | exec(`meteor npm install --save ${npmDependencies.join(' ')}`); 17 | 18 | // dev dependencies 19 | const npmDevDependencies = [ 20 | 'eslint-config-poetic', 21 | 'react-addons-test-utils', 22 | 'mocha', 23 | 'faker', 24 | ]; 25 | exec(`meteor npm install --save-dev ${npmDevDependencies.join(' ')}`); 26 | 27 | // chimp 28 | exec('npm list -g chimp || npm install --global chimp'); 29 | 30 | // meteor-multi-deploy 31 | exec('npm install -g meteor-multi-deploy'); 32 | exec('mmd setup'); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/update/copy-css.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const logTask = require('../helpers/log-task'); 4 | const { mkdir, cp } = require('../helpers/shelljs'); 5 | const glob = require('glob'); 6 | const fs = require('fs'); 7 | const _ = require('lodash'); 8 | const cheerio = require('cheerio'); 9 | const readOneHtml = require('../helpers/read-one-html'); 10 | 11 | module.exports = function copyCss() { 12 | logTask('Regenerate css'); 13 | mkdir('-p', 'client/css'); 14 | mkdir('-p', 'client/css/lib'); 15 | const cssFiles = glob.sync('.design/css/*.css'); 16 | cssFiles.forEach((name) => { 17 | const libCssFiles = [ 18 | '.design/css/normalize.css', 19 | '.design/css/webflow.css', 20 | ]; 21 | 22 | if (_.includes(libCssFiles, name)) { 23 | cp(name, 'client/css/lib'); 24 | } else { 25 | cp(name, 'client/css/'); 26 | } 27 | }); 28 | // extract css from head to main.css 29 | console.log('create client/css/main.css from html head'); 30 | const styleFromHead = cheerio.load(readOneHtml())('head style').html(); 31 | if (styleFromHead) { 32 | fs.writeFileSync('client/css/main.css', styleFromHead); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /test/integration/update.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const fs = require('fs'); 3 | const { assert } = require('chai'); 4 | const path = require('path'); 5 | const update = require('../../lib/update'); 6 | const { cd, rm, mkdir, cp } = require('shelljs'); 7 | 8 | describe('update', () => { 9 | it('update a meteor app', () => { 10 | const examplesPath = path.resolve(__dirname, '../../examples'); 11 | cd(examplesPath); 12 | rm('-rf', 'example'); 13 | mkdir('./example'); 14 | cp('-R', 'design', 'example/.design'); 15 | cd('example'); 16 | mkdir('client'); 17 | cp( 18 | path.resolve(examplesPath, '../lib/create/templates/client/main.html'), 19 | 'client/main.html' 20 | ); 21 | 22 | update(); 23 | 24 | assert(fs.statSync('client/imports/generated/components/ComponentA.jsx').isFile()); 25 | assert(fs.statSync('public/images/webflow.jpg').isFile()); 26 | assert(fs.statSync('client/css/lib/webflow.css').isFile()); 27 | assert(fs.statSync('public/fonts/Avenir-Book.ttf').isFile()); 28 | const mainHtml = fs.readFileSync('client/main.html', 'utf-8'); 29 | assert.include(mainHtml, 'WebFont.load'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/design/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poetic-stanza", 3 | "engines": { 4 | "node": ">=6.0.0" 5 | }, 6 | "description": "Poetic meteor-react-webflow project generator.", 7 | "bin": { 8 | "stanza": "bin/stanza.js", 9 | "st": "bin/stanza.js" 10 | }, 11 | "scripts": { 12 | "test": "rm -rf examples/example && npm run lint && istanbul cover _mocha && coveralls < coverage/lcov.info", 13 | "lint": "eslint . --ext .jsx,.js", 14 | "fix": "eslint . --ext .jsx,.js --fix", 15 | "watch": "rm -rf examples/example && mocha --watch", 16 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/poetic/stanza.git" 21 | }, 22 | "keywords": [ 23 | "meteor", 24 | "react", 25 | "webflow", 26 | "generator", 27 | "html" 28 | ], 29 | "author": "Chun-Yang", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/poetic/stanza/issues" 33 | }, 34 | "homepage": "https://github.com/poetic/stanza", 35 | "devDependencies": { 36 | "chai": "^3.5.0", 37 | "coveralls": "^2.11.8", 38 | "eslint-config-poetic": "^1.0.2", 39 | "istanbul": "^1.0.0-alpha.2", 40 | "mocha": "^2.4.5", 41 | "semantic-release": "^4.3.5" 42 | }, 43 | "dependencies": { 44 | "chalk": "^1.1.1", 45 | "cheerio": "^0.20.0", 46 | "commander": "git://github.com/tj/commander.js.git#c6236d9504b60d9a2e6aa7fc3ce17a12f48f4a3e", 47 | "glob": "^7.0.3", 48 | "lodash": "^4.6.1", 49 | "reacterminator": "^0.15.1", 50 | "shelljs": "^0.6.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/update/copy-fonts.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const cheerio = require('cheerio'); 3 | const readOneHtml = require('../helpers/read-one-html'); 4 | const fs = require('fs'); 5 | const regenerateFolderInPublic = require('../helpers/regenerate-folder-in-public'); 6 | 7 | function removeFontScriptTags($, inverse) { 8 | $('head').children().each((index, element) => { 9 | const src = $(element).attr('src'); 10 | const isWebFontJs = src && _.endsWith(src, 'webfont.js'); 11 | 12 | const content = _.trim($(element).text()); 13 | const isWebFontLoad = content && _.startsWith(content, 'WebFont.load'); 14 | 15 | const isFontTag = (isWebFontJs || isWebFontLoad); 16 | const shouldRemove = inverse ? !isFontTag : isFontTag; 17 | 18 | if (shouldRemove) { 19 | $(element).remove(); 20 | } 21 | }); 22 | } 23 | 24 | function copyFontScriptsToMainHtml() { 25 | // get font script tags from head of a html file 26 | const $from = cheerio.load(readOneHtml()); 27 | removeFontScriptTags($from, true); 28 | const tagsString = _.trim($from('head').html()); 29 | 30 | // insert script tags into the the main.html 31 | const $to = cheerio.load(fs.readFileSync('client/main.html', 'utf-8')); 32 | removeFontScriptTags($to); 33 | if (tagsString) { 34 | $to('head').append(` ${tagsString}\n`); 35 | } 36 | fs.writeFileSync('client/main.html', $to.html().replace(/\n+\s*\n+/g, '\n')); 37 | } 38 | 39 | module.exports = function copyFonts() { 40 | // copy custom font files 41 | // TODO: we may need to change the css file to 42 | // import font files from an absolute url, 43 | // for now the paths are shallow, copy font files works 44 | regenerateFolderInPublic('fonts'); 45 | 46 | copyFontScriptsToMainHtml(); 47 | }; 48 | -------------------------------------------------------------------------------- /lib/create/templates/client/imports/custom/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import _ from 'lodash'; 3 | import { Meteor } from 'meteor/meteor'; 4 | import { createContainer } from 'meteor/react-meteor-data'; 5 | import ParamStore from 'param-store'; 6 | import subscribeAll from 'meteor/poetic:meteor-subscribe-all'; 7 | 8 | export default (Base) => { 9 | class App extends React.Component { 10 | constructor (props) { 11 | super(props); 12 | 13 | this.state = { 14 | path: ParamStore.get('path') 15 | } 16 | 17 | ParamStore.listen( 18 | 'path', 19 | ({changedParams}) => { 20 | this.setState({path: changedParams['path']}); 21 | } 22 | ); 23 | } 24 | 25 | path () { 26 | const { loading, loggedIn } = this.props; 27 | 28 | // TODO: make sure you have 'loading' path 29 | if (loading) { 30 | return 'loading'; 31 | } 32 | 33 | // TODO: put all routes that do not need authorization here 34 | const PUBLIC_ROUTES = [ 35 | 'login', 36 | ]; 37 | 38 | const isNotAuthorizedPath = 39 | !loggedIn && !_.includes(PUBLIC_ROUTES, this.state.path); 40 | 41 | // TODO: make sure you have 'login' path, or change login to the correct path name 42 | if (isNotAuthorizedPath) { 43 | return 'login'; 44 | } 45 | } 46 | 47 | render() { 48 | return ; 49 | } 50 | } 51 | 52 | // TODO: put all subscriptions you need before rendering the app 53 | const GLOBAL_SUBSCRIPTIONS = []; 54 | 55 | const allSubscriptionsReady = subscribeAll(GLOBAL_SUBSCRIPTIONS); 56 | 57 | const AppWithData = createContainer(() => { 58 | const loggedIn = Boolean(Meteor.user()) 59 | 60 | return { 61 | loading: Meteor.loggingIn() || (loggedIn && !allSubscriptionsReady.get()), 62 | loggedIn, 63 | } 64 | }, App); 65 | 66 | return AppWithData; 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stanza 2 | [![travis][travis-image]][travis-url] 3 | [![npm][npm-image]][npm-url] 4 | [![semantic-release][semantic-release-image]][semantic-release-url] 5 | [![coverall][coverall-image]][coverall-url] 6 | 7 | [travis-image]: https://travis-ci.org/poetic/stanza.svg 8 | [travis-url]: https://travis-ci.org/poetic/stanza 9 | [npm-image]: https://img.shields.io/npm/v/poetic-stanza.svg 10 | [npm-url]: https://npmjs.org/package/poetic-stanza 11 | [semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 12 | [semantic-release-url]: https://github.com/semantic-release/semantic-release 13 | [coverall-image]: https://coveralls.io/repos/github/poetic/stanza/badge.svg?branch=master 14 | [coverall-url]: https://coveralls.io/github/poetic/stanza 15 | 16 | ## Requirements 17 | * node >= 6.0.0 18 | * java development kit must be installed to run tests (chimp uses selenium) 19 | 20 | ## Usage 21 | 22 | ### Alias 23 | `st` 24 | 25 | ### Example 26 | ``` 27 | npm install -g poetic-stanza 28 | stanza create magic 29 | cd magic 30 | 31 | // dowload your webflow project as a zip file 32 | // copy into current directory as design.zip 33 | cp ~/Downloads/magic.zip ./design.zip 34 | 35 | stanza update 36 | ``` 37 | 38 | ### [Demo App](https://github.com/poetic/stanza-demo) 39 | 40 | ### CLI 41 | ``` 42 | Usage: stanza [options] [command] 43 | 44 | 45 | Commands: 46 | 47 | create|c Create a meteor project with react and redux configured. 48 | update|u Update a meteor project with design.zip from webflow. 49 | help [cmd] display help for [cmd] 50 | 51 | Poetic meteor-react-webflow project generator. 52 | 53 | Options: 54 | 55 | -h, --help output usage information 56 | -V, --version output the version number 57 | 58 | Examples: 59 | 60 | $ stanza c my-project 61 | $ stanza u 62 | ``` 63 | 64 | ### travis-ci 65 | Go to [travis-ci](https://travis-ci.com/) enable the repo. 66 | Remember to push a commit after you do this. 67 | 68 | ## Development 69 | 70 | ### [Trello](https://trello.com/b/WUNN44Dp/stanza) 71 | --------------------------------------------------------------------------------