├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── README.md ├── app ├── index.js └── templates │ ├── LICENSE.md │ ├── README.md │ ├── buildConfig.js │ ├── editorconfig │ ├── esdoc.json │ ├── eslintrc │ ├── gitignore │ ├── gulp │ ├── assets.js │ ├── build.js │ ├── custom-task.js │ ├── debug.js │ ├── index.js │ └── utils │ │ └── define-plugin.js │ ├── gulpfile.js │ ├── index.html │ ├── package.json │ ├── src │ ├── app.jsx │ ├── app │ │ └── index.js │ ├── components │ │ ├── chrome │ │ │ ├── index.js │ │ │ └── template.jsx │ │ └── navigation │ │ │ ├── index.js │ │ │ └── template.jsx │ ├── index.js │ ├── pages │ │ ├── index │ │ │ ├── index.js │ │ │ ├── page.js │ │ │ └── template.jsx │ │ ├── notfound │ │ │ ├── index.js │ │ │ ├── page.js │ │ │ └── template.jsx │ │ └── other │ │ │ ├── index.js │ │ │ ├── page.js │ │ │ └── template.jsx │ └── routes.js │ ├── style │ └── main.less │ ├── test │ ├── app.jsx │ ├── environment.js │ └── index.js │ ├── webpack.config.js │ └── webpack.config.prod.js ├── common └── util │ ├── alias.js │ └── header.js ├── component ├── index.js └── templates │ ├── index.js │ ├── store-postal.js │ ├── store-rxmq.js │ ├── style.less │ ├── template.jsx │ └── tests │ └── test.jsx ├── docker-deploy ├── index.js └── templates │ └── Dockerfile ├── docker ├── index.js └── templates │ ├── Dockerfile │ └── dockerignore ├── package.json ├── page ├── index.js └── templates │ ├── index.js │ ├── page.js │ ├── template.jsx │ └── tests │ └── test.jsx └── test ├── docker-deploy.js ├── test-app.js ├── test-component.js ├── test-docker.js └── test-page.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | # all files defaults 3 | [**] 4 | # Unix-style newlines with a newline ending 5 | end_of_line = lf 6 | insert_final_newline = true 7 | # Set default charset 8 | charset = utf-8 9 | # 4 space indentation 10 | indent_style = space 11 | indent_size = 2 12 | # trim whitespaces 13 | trim_trailing_whitespace = true 14 | # always insert final newline 15 | insert_final_newline = true 16 | 17 | # 2 spaces indent for config files 18 | [{package.json}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // I want to use babel-eslint for parsing! 3 | "parser": "babel-eslint", 4 | "env": { 5 | // I write for browser 6 | "browser": true, 7 | // in CommonJS 8 | "node": true, 9 | // use ES6 10 | "es6": true 11 | }, 12 | "ecmaProperties": { 13 | // enable JSX support 14 | "jsx": true 15 | }, 16 | "plugins": [ 17 | // enable react plugin 18 | "react" 19 | ], 20 | // To give you an idea how to override rule options 21 | "rules": { 22 | // Possible Errors 23 | "comma-dangle": 0, 24 | "no-console": 2, 25 | "no-debugger": 2, 26 | "no-dupe-keys": 2, 27 | "no-dupe-args": 2, 28 | "no-empty": 2, 29 | "no-extra-boolean-cast": 2, 30 | "no-extra-semi": 2, 31 | "no-invalid-regexp": 2, 32 | "no-irregular-whitespace": 2, 33 | "no-reserved-keys": 0, 34 | "no-sparse-arrays": 2, 35 | "no-unreachable": 2, 36 | "use-isnan": 2, 37 | "valid-jsdoc": 2, 38 | "valid-typeof": 2, 39 | // Best Practices 40 | "consistent-return": 1, 41 | "curly": 2, 42 | "default-case": 2, 43 | "dot-notation": 2, 44 | "eqeqeq": 2, 45 | "no-alert": 2, 46 | "no-caller": 2, 47 | "no-else-return": 2, 48 | "no-eq-null": 2, 49 | "no-eval": 2, 50 | "no-extend-native": 2, 51 | "no-floating-decimal": 2, 52 | "no-implied-eval": 2, 53 | "no-iterator": 2, 54 | "no-labels": 2, 55 | "no-loop-func": 1, 56 | "no-lone-blocks": 2, 57 | "no-multi-spaces": 2, 58 | "no-native-reassign": 2, 59 | "no-new": 2, 60 | "no-new-func": 2, 61 | "no-new-wrappers": 2, 62 | "no-proto": 2, 63 | "no-redeclare": 2, 64 | "no-return-assign": 2, 65 | "no-script-url": 2, 66 | "no-self-compare": 2, 67 | "no-sequences": 2, 68 | "no-throw-literal": 2, 69 | "no-unused-expressions": 2, 70 | "no-void": 2, 71 | "radix": 2, 72 | "yoda": 0, 73 | // Strict Mode 74 | "strict": 0, 75 | // Variables 76 | "no-catch-shadow": 2, 77 | "no-delete-var": 2, 78 | "no-shadow": 2, 79 | "no-shadow-restricted-names": 2, 80 | "no-undef": 2, 81 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 82 | "no-use-before-define": 2, 83 | // Node 84 | "handle-callback-err": 2, 85 | "no-new-require": 2, 86 | "no-path-concat": 2, 87 | // Stylistic Issues 88 | "indent": [2, 2], // 2 spaces 89 | "camelcase": 2, 90 | "comma-spacing": [2, {"before": false, "after": true}], 91 | "comma-style": [2, "last"], 92 | "eol-last": 2, 93 | "func-style": [2, "expression"], 94 | "max-nested-callbacks": [2, 3], 95 | "no-array-constructor": 2, 96 | "no-mixed-spaces-and-tabs": 2, 97 | "no-multiple-empty-lines": [1, {"max": 2}], 98 | "no-nested-ternary": 2, 99 | "no-new-object": 2, 100 | "no-space-before-semi": 2, 101 | "no-spaced-func": 2, 102 | "no-trailing-spaces": 2, 103 | "no-underscore-dangle": 2, 104 | "no-wrap-func": 2, 105 | "quote-props": [1, "as-needed"], 106 | "quotes": [1, "single"], 107 | "semi": [2, "always"], 108 | "semi-spacing": [2, {"before": false, "after": true}], 109 | "space-after-function-name": [1, "never"], 110 | "space-after-keywords": [1, "always"], 111 | "space-before-blocks": [1, "always"], 112 | "space-before-function-parentheses": [2, "always"], 113 | "space-in-brackets": [1, "never"], 114 | "space-in-parens": [1, "never"], 115 | "space-infix-ops": 2, 116 | // complexity rules 117 | "max-depth": [2, 2], 118 | "max-statements": [2, 8], 119 | "complexity": [2, 5], 120 | "max-len": [2, 120], 121 | "max-params": [2, 2], 122 | "max-nested-callbacks": [2, 1], 123 | // jsx rules 124 | "react/jsx-quotes": 1, 125 | "react/jsx-no-undef": 1, 126 | "react/jsx-uses-react": 1, 127 | "react/jsx-uses-vars": 1, 128 | "react/no-did-mount-set-state": 1, 129 | "react/no-did-update-set-state": 1, 130 | "react/no-multi-comp": 1, 131 | "react/react-in-jsx-scope": 1, 132 | "react/self-closing-comp": 1, 133 | "react/wrap-multilines": 1 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "strict": true 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": {} 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-turris 2 | 3 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 4 | 5 | > [Yeoman](http://yeoman.io) generator for ES6 React web applications 6 | 7 | ## About Turris.js 8 | 9 | Turris.js is a combination of existing libraries and tools that allows fast and simple creation of single page web applications using [ES6](http://www.ecma-international.org/publications/standards/Ecma-262.htm). 10 | 11 | Turris.js is built using [React.js](https://facebook.github.io/react/) with [react-router](https://github.com/rackt/react-router) and [Twitter Bootstrap CSS](http://getbootstrap.com/). 12 | All of the application code is managed and compiled by [Webpack](http://webpack.github.io/) and minified using [Uglify.js](https://github.com/mishoo/UglifyJS2) (when not in debug mode). 13 | [NPM](https://npmjs.org/) is used for the dependency management. 14 | 15 | [Gulp.js](http://gulpjs.com/) is used as a build-tool. 16 | Testing is done using [eslint](http://eslint.org/) for linting and code-style and [mocha.js](http://visionmedia.github.io/mocha/) with [should.js](https://github.com/visionmedia/should.js/) for automated testing. 17 | 18 | Standalone reusable components for your Turris application can be generated using separate [Turris-component](https://github.com/turrisjs/generator-turris-component) generator. 19 | 20 | ## Getting Started 21 | 22 | ### Requirements 23 | 24 | For Turris.js to function properly, you'll need to have following things installed: 25 | 26 | * [io.js](https://iojs.org/) v1.8+ 27 | * [NPM](https://npmjs.org/) v2.8+ 28 | 29 | Alternatively you can use [docker](https://www.docker.com/) environment provided by `turris:docker` generator. 30 | 31 | ### Installation 32 | 33 | ``` 34 | $ npm install -g yo generator-turris 35 | ``` 36 | 37 | ### Usage 38 | 39 | ``` 40 | $ yo turris 41 | ``` 42 | 43 | ### API 44 | 45 | `$ yo turris` 46 | Creates a new turris.js application. 47 | 48 | `$ yo turris:component myName` 49 | Generates a new inlined React component named *myName*. 50 | If you want to generate standalone/self-contained and more sophisticated components, look into using [Turris component generator](https://github.com/turrisjs/generator-turris-component). 51 | 52 | `$ yo turris:page myName` 53 | Generates a new page named *myName* and injects it into main app. 54 | The only difference between page and component is that page generates and exports a route. 55 | 56 | `$ yo turris:docker` 57 | Generates a [docker](https://www.docker.com/) environment for the project with all the requirements included. 58 | 59 | `$ yo turris:docker-deploy` 60 | Generates a separate [docker](https://www.docker.com/) environment for the project statical deployment via nginx. 61 | To deploy the app using it, you will need to: 62 | 1. Compile your app using `npm run deploy` command 63 | 2. Package it into docker container using `docker build -t app-name -f Dockerfile-deploy .` command 64 | 3. Run it on docker and forward port 80 65 | 66 | 67 | ## Learning Your Way Around 68 | 69 | Once installed, you can create a basic application by following the prompts. 70 | 71 | ```shell 72 | $ mkdir HelloWorld 73 | $ cd HelloWorld 74 | $ yo turris 75 | 76 | _-----_ 77 | | | .--------------------------. 78 | |--(o)--| | Welcome to the | 79 | `---------´ | breathtaking Turris | 80 | ( _´U`_ ) | generator! | 81 | /___A___\ '--------------------------' 82 | | ~ | 83 | __'.___.'__ 84 | ´ ` |° ´ Y ` 85 | 86 | ? Your project name: (HelloWorld) 87 | ... 88 | ``` 89 | 90 | To run your application, just type `npm start`. 91 | 92 | ### Project Structure 93 | 94 | Client: 95 | - **/style/** - LESS files 96 | - **/src/index.js** - App entry point 97 | - **/src/app.jsx** - React app bootstrapper 98 | - **/src/routes.js** - Routes for react-router 99 | - **/src/app/** - React app definition with react-router setup 100 | - **/src/components/** - React components folder 101 | - **/src/components/chrome** - Application chrome React component 102 | - **/src/components/navigation** - Application navigation bar React component 103 | - **/src/pages/** - React pages folder 104 | - **/src/pages/index** - Index page that's loaded by default 105 | - **/src/pages/other** - Other page that's added for convenience 106 | 107 | Components (can be generated with `turris:component`): 108 | - **/src/components/COMPONENT/index.js** - React component definition 109 | - **/src/components/COMPONENT/template.jsx** - React template 110 | 111 | Pages (can be generated with `turris:page`): 112 | - **/src/pages/PAGE/index.js** - React component and route definition 113 | - **/src/pages/PAGE/template.jsx** - React template 114 | 115 | Misc: 116 | - **/gulp/** - Custom Gulp tasks 117 | - **/tests/** - Unit and functional tests 118 | - **/buildConfig.js** - Build config 119 | - **/gulpfile.js** - Gulp bootstrapper 120 | - **/webpack.config.js** - Webpack config for development 121 | - **/webpack.config.prod.js** - Webpack config for production 122 | 123 | ### Docker 124 | 125 | You can as well quickly setup a [docker](https://www.docker.com/) container that includes all the required packages for Turris.js app to work (assuming you have docker installed). 126 | To do so, follow the steps below: 127 | 128 | 1. Run `yo turris:docker` to generate all the required docker files 129 | 2. Run `docker build -t appname .` to generate new docker container 130 | 3. Run `docker run -it -p 8080:8080 appname` to launch generated container 131 | 132 | ### TODO 133 | 134 | Add more docs, subgenerators and a proper test suite. 135 | 136 | ## License 137 | 138 | [MIT](http://opensource.org/licenses/MIT) 139 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | var chalk = require('chalk'); 4 | var yosay = require('yosay'); 5 | 6 | module.exports = yeoman.generators.Base.extend({ 7 | initializing: function () { 8 | this.pkg = require('../package.json'); 9 | }, 10 | 11 | prompting: function () { 12 | var done = this.async(); 13 | 14 | // Have Yeoman greet the user. 15 | this.log(yosay( 16 | 'Welcome to the breathtaking ' + chalk.red('Turris') + ' generator!' 17 | )); 18 | 19 | var prompts = [{ 20 | type: 'input', 21 | name: 'name', 22 | message: 'Your project name', 23 | default: this.appname, 24 | }, { 25 | type: 'input', 26 | name: 'description', 27 | message: 'Your project description', 28 | default: 'No description yet', 29 | }, { 30 | type: 'list', 31 | name: 'style', 32 | message: 'Do you want to use less, scss, styl or plain css?', 33 | choices: ['less', 'scss', 'styl', 'css'], 34 | default: 'less', 35 | store: true, // save for future 36 | }]; 37 | 38 | this.prompt(prompts, function (props) { 39 | this.props = props; 40 | done(); 41 | }.bind(this)); 42 | }, 43 | 44 | writing: { 45 | app: function () { 46 | this.fs.copyTpl( 47 | this.templatePath('package.json'), 48 | this.destinationPath('package.json'), 49 | this.props 50 | ); 51 | this.fs.copyTpl( 52 | this.templatePath('README.md'), 53 | this.destinationPath('README.md'), 54 | this.props 55 | ); 56 | }, 57 | 58 | projectfiles: function () { 59 | this.fs.copy( 60 | this.templatePath('editorconfig'), 61 | this.destinationPath('.editorconfig') 62 | ); 63 | this.fs.copy( 64 | this.templatePath('eslintrc'), 65 | this.destinationPath('.eslintrc') 66 | ); 67 | this.fs.copy( 68 | this.templatePath('gitignore'), 69 | this.destinationPath('.gitignore') 70 | ); 71 | this.fs.copy( 72 | this.templatePath('buildConfig.js'), 73 | this.destinationPath('buildConfig.js') 74 | ); 75 | this.fs.copy( 76 | this.templatePath('esdoc.json'), 77 | this.destinationPath('esdoc.json') 78 | ); 79 | this.fs.copy( 80 | this.templatePath('gulpfile.js'), 81 | this.destinationPath('gulpfile.js') 82 | ); 83 | this.fs.copyTpl( 84 | this.templatePath('index.html'), 85 | this.destinationPath('index.html'), 86 | this.props 87 | ); 88 | this.fs.copy( 89 | this.templatePath('webpack.config.js'), 90 | this.destinationPath('webpack.config.js') 91 | ); 92 | this.fs.copy( 93 | this.templatePath('webpack.config.prod.js'), 94 | this.destinationPath('webpack.config.prod.js') 95 | ); 96 | 97 | // folders 98 | this.fs.copy( 99 | this.templatePath('gulp'), 100 | this.destinationPath('gulp') 101 | ); 102 | this.fs.copyTpl( 103 | this.templatePath('src'), 104 | this.destinationPath('src'), 105 | this.props 106 | ); 107 | this.fs.copy( 108 | this.templatePath('style/main.less'), 109 | this.destinationPath('style/main.' + this.props.style) 110 | ); 111 | this.fs.copy( 112 | this.templatePath('test'), 113 | this.destinationPath('test') 114 | ); 115 | this.fs.copy( 116 | this.templatePath('LICENSE.md'), 117 | this.destinationPath('LICENSE.md') 118 | ); 119 | } 120 | }, 121 | 122 | install: function () { 123 | this.npmInstall(); 124 | } 125 | }); 126 | -------------------------------------------------------------------------------- /app/templates/LICENSE.md: -------------------------------------------------------------------------------- 1 | Your license text here 2 | -------------------------------------------------------------------------------- /app/templates/README.md: -------------------------------------------------------------------------------- 1 | # Awesome <%= name %> application 2 | 3 | <%= description %> 4 | 5 | Made with [Turris.js](https://github.com/turrisjs) 6 | -------------------------------------------------------------------------------- /app/templates/buildConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpackConfig: { 3 | debug: require('./webpack.config.js'), 4 | production: require('./webpack.config.prod.js'), 5 | }, 6 | devServer: { 7 | // settings for webpack-dev-server 8 | proxy: { 9 | // put your custom proxy routes here, e.g.: 10 | // '/api/': 'http://localhost:8081' 11 | }, 12 | headers: { 13 | // put your custom headers here, e.g.: 14 | // 'X-TEST': 1, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | # all files defaults 3 | [**] 4 | # Unix-style newlines with a newline ending 5 | end_of_line = lf 6 | insert_final_newline = true 7 | # Set default charset 8 | charset = utf-8 9 | # 4 space indentation 10 | indent_style = space 11 | indent_size = 4 12 | # trim whitespaces 13 | trim_trailing_whitespace = true 14 | # always insert final newline 15 | insert_final_newline = true 16 | 17 | # 2 spaces indent for config files 18 | [{package.json}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /app/templates/esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./esdoc", 4 | "coverage": true 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // I want to use babel-eslint for parsing! 3 | "parser": "babel-eslint", 4 | "env": { 5 | // I write for browser 6 | "browser": true, 7 | // in CommonJS 8 | "node": true, 9 | // use ES6 10 | "es6": true 11 | }, 12 | "ecmaProperties": { 13 | // enable JSX support 14 | "jsx": true 15 | }, 16 | "plugins": [ 17 | // enable react plugin 18 | "react" 19 | ], 20 | "globals": { 21 | "__WEBPACK__": true 22 | }, 23 | // To give you an idea how to override rule options 24 | "rules": { 25 | // Possible Errors 26 | "comma-dangle": 0, 27 | "no-console": 2, 28 | "no-debugger": 2, 29 | "no-dupe-keys": 2, 30 | "no-dupe-args": 2, 31 | "no-empty": 2, 32 | "no-extra-boolean-cast": 2, 33 | "no-extra-semi": 2, 34 | "no-invalid-regexp": 2, 35 | "no-irregular-whitespace": 2, 36 | "quote-props": [2, "consistent-as-needed", {"keywords": true}], 37 | "no-sparse-arrays": 2, 38 | "no-unreachable": 2, 39 | "use-isnan": 2, 40 | "valid-jsdoc": 2, 41 | "valid-typeof": 2, 42 | // Best Practices 43 | "consistent-return": 1, 44 | "curly": 2, 45 | "default-case": 2, 46 | "dot-notation": 2, 47 | "eqeqeq": 2, 48 | "no-alert": 2, 49 | "no-caller": 2, 50 | "no-else-return": 2, 51 | "no-eq-null": 2, 52 | "no-eval": 2, 53 | "no-extend-native": 2, 54 | "no-floating-decimal": 2, 55 | "no-implied-eval": 2, 56 | "no-iterator": 2, 57 | "no-labels": 2, 58 | "no-loop-func": 1, 59 | "no-lone-blocks": 2, 60 | "no-multi-spaces": 2, 61 | "no-native-reassign": 2, 62 | "no-new": 2, 63 | "no-new-func": 2, 64 | "no-new-wrappers": 2, 65 | "no-proto": 2, 66 | "no-redeclare": 2, 67 | "no-return-assign": 2, 68 | "no-script-url": 2, 69 | "no-self-compare": 2, 70 | "no-sequences": 2, 71 | "no-throw-literal": 2, 72 | "no-unused-expressions": 2, 73 | "no-void": 2, 74 | "radix": 2, 75 | "yoda": 0, 76 | // Strict Mode 77 | "strict": 0, 78 | // Variables 79 | "no-catch-shadow": 2, 80 | "no-delete-var": 2, 81 | "no-shadow": 2, 82 | "no-shadow-restricted-names": 2, 83 | "no-undef": 2, 84 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 85 | "no-use-before-define": 2, 86 | // Node 87 | "handle-callback-err": 2, 88 | "no-new-require": 2, 89 | "no-path-concat": 2, 90 | // Stylistic Issues 91 | "indent": 2, // 4 spaces 92 | "camelcase": 2, 93 | "comma-spacing": [2, {"before": false, "after": true}], 94 | "comma-style": [2, "last"], 95 | "eol-last": 2, 96 | "func-style": [2, "expression"], 97 | "max-nested-callbacks": [2, 3], 98 | "no-array-constructor": 2, 99 | "no-mixed-spaces-and-tabs": 2, 100 | "no-multiple-empty-lines": [1, {"max": 2}], 101 | "no-nested-ternary": 2, 102 | "no-new-object": 2, 103 | "semi-spacing": [2, {"before": false, "after": true}], 104 | "no-spaced-func": 2, 105 | "no-trailing-spaces": 2, 106 | "no-underscore-dangle": 2, 107 | "no-extra-parens": [2, "functions"], 108 | "quote-props": [1, "as-needed"], 109 | "quotes": [1, "single"], 110 | "semi": [2, "always"], 111 | "semi-spacing": [2, {"before": false, "after": true}], 112 | "space-before-function-paren": [1, {"anonymous": "never", "named": "never"}], 113 | "space-after-keywords": [1, "always"], 114 | "space-before-blocks": [1, "always"], 115 | "object-curly-spacing": [1, "never"], 116 | "array-bracket-spacing": [1, "never"], 117 | "computed-property-spacing": [1, "never"], 118 | "space-in-parens": [1, "never"], 119 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}], 120 | "object-curly-spacing": [1, "never"], 121 | "space-infix-ops": 2, 122 | // complexity rules 123 | "max-depth": [2, 3], 124 | "max-statements": [1, 20], 125 | "complexity": [1, 3], 126 | "max-len": [2, 120], 127 | "max-params": [2, 2], 128 | // jsx rules 129 | "react/jsx-quotes": 1, 130 | "react/jsx-no-undef": 1, 131 | "react/jsx-uses-react": 1, 132 | "react/jsx-uses-vars": 1, 133 | "react/no-did-mount-set-state": 1, 134 | "react/no-did-update-set-state": 1, 135 | "react/no-multi-comp": 1, 136 | "react/react-in-jsx-scope": 1, 137 | "react/self-closing-comp": 1, 138 | "react/wrap-multilines": 1, 139 | // ES6 140 | "prefer-const": 2, 141 | "object-shorthand": [2, "always"], 142 | "no-var": 2 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | # ignore node deps 2 | node_modules/ 3 | 4 | # ignore compiled 5 | dist/ 6 | 7 | # ignore coverage report 8 | coverage/ 9 | 10 | # ignore docs 11 | esdoc/ 12 | -------------------------------------------------------------------------------- /app/templates/gulp/assets.js: -------------------------------------------------------------------------------- 1 | export default (gulp) => { 2 | gulp.task('assets', function() { 3 | return gulp.src('./index.html').pipe(gulp.dest('./dist')); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /app/templates/gulp/build.js: -------------------------------------------------------------------------------- 1 | import gutil from 'gulp-util'; 2 | import webpack from 'webpack'; 3 | import definePlugin from './utils/define-plugin.js'; 4 | 5 | // create optimizations array 6 | const optimizations = [ 7 | definePlugin, 8 | new webpack.optimize.DedupePlugin(), 9 | new webpack.optimize.UglifyJsPlugin({ 10 | output: { 11 | comments: false, 12 | }, 13 | compress: { 14 | warnings: false, 15 | }, 16 | }), 17 | ]; 18 | 19 | // get build config 20 | const buildConfig = require('../buildConfig'); 21 | // get webpack config 22 | const wpConfig = buildConfig.webpackConfig.production; 23 | 24 | // export initilizer 25 | export default (gulp) => { 26 | gulp.task('build', function(callback) { 27 | if (wpConfig.plugins) { 28 | wpConfig.plugins = wpConfig.plugins.concat(optimizations); 29 | } else { 30 | wpConfig.plugins = optimizations; 31 | } 32 | // run webpack 33 | webpack(wpConfig, function(err, stats) { 34 | if (err) { 35 | throw new gutil.PluginError('webpack', err); 36 | } 37 | // only log when errors 38 | gutil.log('[webpack]: ', stats.toString({ 39 | chunks: false, 40 | modules: false, 41 | colors: true, 42 | })); 43 | callback(); 44 | }); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /app/templates/gulp/custom-task.js: -------------------------------------------------------------------------------- 1 | module.exports = function(gulp) { 2 | gulp.task('custom-task', function() { 3 | console.log('custom task done!'); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /app/templates/gulp/debug.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | import webpack from 'webpack'; 3 | import WebpackDevServer from 'webpack-dev-server'; 4 | import definePlugin from './utils/define-plugin.js'; 5 | 6 | // create optimizations array 7 | const optimizations = [definePlugin]; 8 | 9 | // get build config 10 | const buildConfig = require('../buildConfig'); 11 | // get webpack config 12 | const wpConfig = buildConfig.webpackConfig.debug; 13 | 14 | // export initilizer 15 | export default (gulp) => { 16 | gulp.task('debug', function() { 17 | // append our define plugin 18 | wpConfig.plugins = wpConfig.plugins ? wpConfig.plugins.concat(optimizations) : optimizations; 19 | // get server config 20 | let proxy = {}; 21 | if (buildConfig.devServer && buildConfig.devServer.proxy) { 22 | proxy = buildConfig.devServer.proxy; 23 | } 24 | let headers = {}; 25 | if (buildConfig.devServer && buildConfig.devServer.headers) { 26 | headers = buildConfig.devServer.headers; 27 | } 28 | // run webpack 29 | const compiler = webpack(wpConfig); 30 | const server = new WebpackDevServer(compiler, { 31 | // webpack-dev-server options 32 | contentBase: wpConfig.context, 33 | 34 | hot: true, 35 | // Enable special support for Hot Module Replacement 36 | // Page is no longer updated, but a "webpackHotUpdate" message is send to the content 37 | // Use "webpack/hot/dev-server" as additional module in your entry point 38 | // Note: this does _not_ add the `HotModuleReplacementPlugin` like the CLI option does. 39 | 40 | // webpack-dev-middleware options 41 | quiet: false, 42 | noInfo: false, 43 | watchOptions: { 44 | aggregateTimeout: 300, 45 | poll: true 46 | }, 47 | headers, 48 | stats: { 49 | chunks: false, 50 | colors: true, 51 | }, 52 | // Set this as true if you want to access dev server from arbitrary url. 53 | // This is handy if you are using a html5 router. 54 | historyApiFallback: true, 55 | // Set this if you want webpack-dev-server to delegate a single path to an arbitrary server. 56 | // Use "*" to proxy all paths to the specified server. 57 | // This is useful if you want to get rid of 'http://localhost:8080/' in script[src], 58 | // and has many other use cases (see https://github.com/webpack/webpack-dev-server/pull/127 ). 59 | proxy, 60 | }); 61 | server.listen(8080, 'localhost', function() { 62 | console.log('Webpack-Dev-Server: started on port 8080'); 63 | }); 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /app/templates/gulp/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import gulp from 'gulp'; 3 | 4 | // blacklist self and utils folder 5 | const blacklist = ['index.js', 'utils']; 6 | // get local files 7 | const files = fs.readdirSync('./gulp').filter(f => !blacklist.includes(f)); 8 | 9 | // load custom tasks 10 | files.forEach(function(file) { 11 | require('./' + file)(gulp); 12 | }); 13 | 14 | gulp.task('default', ['debug']); 15 | gulp.task('deploy', ['build', 'assets']); 16 | -------------------------------------------------------------------------------- /app/templates/gulp/utils/define-plugin.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | 3 | // definePlugin takes raw strings and inserts them, so you can put strings of JS if you want. 4 | const definePlugin = new webpack.DefinePlugin({ 5 | __WEBPACK__: true, // say we're the webpack 6 | __DEV__: process.env.BUILD_DEV, // dev environment indication 7 | }); 8 | 9 | export default definePlugin; 10 | -------------------------------------------------------------------------------- /app/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | // enable babel 2 | require('babel/register'); 3 | // require gulp entry 4 | require('./gulp'); 5 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= name %> app 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.camelCase(name) %>", 3 | "version": "1.0.0", 4 | "description": "<%= description %>", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape ./test | faucet", 8 | "cover": "istanbul cover tape ./test", 9 | "docs": "esdoc -c esdoc.json", 10 | "start": "gulp", 11 | "build": "gulp build", 12 | "deploy": "gulp deploy" 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "bootstrap": "^3.3.4", 18 | "react": "^0.13.2", 19 | "react-router": "^0.13.3" 20 | }, 21 | "devDependencies": { 22 | "babel": "^5.8.19", 23 | "babel-core": "^5.8.19", 24 | "babel-eslint": "^4.0.5", 25 | "babel-loader": "^5.3.2", 26 | "css-loader": "^0.17.0", 27 | "eslint": "^1.1.0", 28 | "eslint-loader": "^1.0.0", 29 | "eslint-plugin-react": "^3.1.0", 30 | "extract-text-webpack-plugin": "^0.8.2", 31 | "faucet": "0.0.1", 32 | "file-loader": "^0.8.4", 33 | "gulp": "^3.9.0", 34 | "gulp-util": "^3.0.6", 35 | "istanbul": "^0.3.17", 36 | "jsdom": "^6.3.0",<% if (style === 'less') { %> 37 | "less-loader": "^2.2.0",<% } %> 38 | "localStorage": "^1.0.3", 39 | "react-hot-loader": "^1.3.0", 40 | "style-loader": "^0.12.3",<% if (style === 'scss') { %> 41 | "sass-loader": "^1.0.4",<% } else if (style === 'styl') { %> 42 | "stylus-loader": "^1.2.1",<% } %> 43 | "tape": "^4.0.1", 44 | "url-loader": "^0.5.6", 45 | "webpack": "^1.10.5", 46 | "webpack-dev-server": "^1.10.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/templates/src/app.jsx: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import ReactRouter from 'react-router'; 4 | // app core 5 | import App from './app/index.js'; 6 | // user routes 7 | import routes from './routes.js'; 8 | 9 | const appInstance = ( 10 | 11 | {routes} 12 | 13 | ); 14 | 15 | const Bootstrapper = { 16 | start() { 17 | ReactRouter.run(appInstance, ReactRouter.HistoryLocation, function(Handler) { 18 | React.render(, document.getElementById('mainContainer')); 19 | }); 20 | }, 21 | }; 22 | 23 | export default Bootstrapper; 24 | -------------------------------------------------------------------------------- /app/templates/src/app/index.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import {RouteHandler} from 'react-router'; 4 | 5 | // main class 6 | const App = React.createClass({ 7 | render() { 8 | return ; 9 | }, 10 | }); 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /app/templates/src/components/chrome/index.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Template from './template.jsx'; 4 | 5 | const Chrome = React.createClass({ 6 | render: Template, 7 | }); 8 | 9 | export default Chrome; 10 | -------------------------------------------------------------------------------- /app/templates/src/components/chrome/template.jsx: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Navigation from '../navigation'; 4 | 5 | const render = function() { 6 | return ( 7 |
8 | 9 | 10 |
11 |
12 | {this.props.children} 13 |
14 |
15 |
16 | ); 17 | }; 18 | 19 | export default render; 20 | -------------------------------------------------------------------------------- /app/templates/src/components/navigation/index.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Template from './template.jsx'; 4 | 5 | const Navbar = React.createClass({ 6 | render: Template, 7 | }); 8 | 9 | export default Navbar; 10 | -------------------------------------------------------------------------------- /app/templates/src/components/navigation/template.jsx: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import ReactRouter from 'react-router'; 4 | 5 | const render = function() { 6 | return ( 7 |
8 |
9 |
10 | 16 | <%= name %> 17 |
18 | 19 |
20 |
    21 |
  • Home
  • 22 |
  • Other
  • 23 |
24 |
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default render; 31 | -------------------------------------------------------------------------------- /app/templates/src/index.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore if */ 2 | if (__WEBPACK__) { 3 | require('bootstrap/dist/css/bootstrap.css'); 4 | require('../style/main.<%= style %>'); 5 | } 6 | import App from './app.jsx'; 7 | 8 | App.start(); 9 | -------------------------------------------------------------------------------- /app/templates/src/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {DefaultRoute} from 'react-router'; 3 | import IndexPage from './page'; 4 | 5 | const IndexRoute = React.createElement(DefaultRoute, {name: 'home', key: 'route_default', handler: IndexPage}); 6 | 7 | export default IndexRoute; 8 | -------------------------------------------------------------------------------- /app/templates/src/pages/index/page.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Template from './template.jsx'; 4 | 5 | const IndexPage = React.createClass({ 6 | getInitialState() { 7 | // init state 8 | return { 9 | // your stuff here 10 | }; 11 | }, 12 | render: Template, 13 | }); 14 | 15 | export default IndexPage; 16 | -------------------------------------------------------------------------------- /app/templates/src/pages/index/template.jsx: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Chrome from '../../components/chrome/index.js'; 4 | 5 | const render = function() { 6 | return ( 7 | 8 |

I am index page

9 |
10 | ); 11 | }; 12 | 13 | export default render; 14 | -------------------------------------------------------------------------------- /app/templates/src/pages/notfound/index.js: -------------------------------------------------------------------------------- 1 | // imports 2 | import React from 'react'; 3 | import {NotFoundRoute} from 'react-router'; 4 | import NotFoundPage from './page'; 5 | 6 | const route = React.createElement(NotFoundRoute, {name: 'notfound', key: 'route_notfound', handler: NotFoundPage}); 7 | 8 | export default route; 9 | -------------------------------------------------------------------------------- /app/templates/src/pages/notfound/page.js: -------------------------------------------------------------------------------- 1 | // imports 2 | import React from 'react'; 3 | import Template from './template.jsx'; 4 | 5 | const NotFoundPage = React.createClass({ 6 | render: Template, 7 | }); 8 | 9 | export default NotFoundPage; 10 | -------------------------------------------------------------------------------- /app/templates/src/pages/notfound/template.jsx: -------------------------------------------------------------------------------- 1 | // imports 2 | import React from 'react'; 3 | import Chrome from '../../components/chrome/index.js'; 4 | 5 | const render = function() { 6 | return ( 7 | 8 |

404 - page not found!

9 |
10 | ); 11 | }; 12 | 13 | export default render; 14 | -------------------------------------------------------------------------------- /app/templates/src/pages/other/index.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import {Route} from 'react-router'; 4 | import OtherPage from './page'; 5 | 6 | const route = React.createElement(Route, {name: 'other', key: 'route_other', handler: OtherPage}); 7 | 8 | export default route; 9 | -------------------------------------------------------------------------------- /app/templates/src/pages/other/page.js: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Template from './template.jsx'; 4 | 5 | const OtherPage = React.createClass({ 6 | render: Template, 7 | }); 8 | 9 | export default OtherPage; 10 | -------------------------------------------------------------------------------- /app/templates/src/pages/other/template.jsx: -------------------------------------------------------------------------------- 1 | // react 2 | import React from 'react'; 3 | import Chrome from '../../components/chrome/index.js'; 4 | 5 | const render = function() { 6 | return ( 7 | 8 |

I am other page

9 |
10 | ); 11 | }; 12 | 13 | export default render; 14 | -------------------------------------------------------------------------------- /app/templates/src/routes.js: -------------------------------------------------------------------------------- 1 | // import all routes 2 | import Index from './pages/index/index.js'; 3 | import Other from './pages/other/index.js'; 4 | import NotFound from './pages/notfound/index.js'; 5 | 6 | // create route array 7 | const routes = [ 8 | Index, 9 | Other, 10 | NotFound, 11 | ]; 12 | 13 | export default routes; 14 | -------------------------------------------------------------------------------- /app/templates/style/main.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/test/app.jsx: -------------------------------------------------------------------------------- 1 | // import helpers 2 | import test from 'tape'; 3 | 4 | // import app 5 | import App from '../src/app.jsx'; 6 | 7 | test('App suite', function(it) { 8 | it.test('# should render', function(t) { 9 | // render 10 | App.start(); 11 | // verify it exists 12 | t.equal(1, document.getElementById('mainContainer').children.length); 13 | t.end(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/templates/test/environment.js: -------------------------------------------------------------------------------- 1 | import localStorage from 'localStorage'; 2 | import {jsdom} from 'jsdom'; 3 | 4 | // say we're not in webpack environment 5 | // this is required to skip including styles 6 | global.__WEBPACK__ = false; // eslint-disable-line no-underscore-dangle 7 | 8 | // init jsdom 9 | global.document = jsdom('
'); 10 | global.window = global.document.defaultView; 11 | global.navigator = global.window.navigator; 12 | 13 | // mock location 14 | global.window.location.href = 'http://localhost/'; 15 | 16 | // local storage polyfill 17 | global.window.localStorage = localStorage; 18 | global.localStorage = localStorage; 19 | -------------------------------------------------------------------------------- /app/templates/test/index.js: -------------------------------------------------------------------------------- 1 | require('babel/register'); 2 | require('./environment'); 3 | require('./app.jsx'); 4 | -------------------------------------------------------------------------------- /app/templates/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'inline-source-map', 6 | debug: true, 7 | context: path.resolve(__dirname), 8 | entry: [ 9 | 'webpack-dev-server/client?http://0.0.0.0:8080', // WebpackDevServer host and port 10 | 'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors 11 | './src/index.js', 12 | ], 13 | output: { 14 | path: path.resolve(__dirname, 'dist'), 15 | publicPath: '/', 16 | filename: 'app.min.js', 17 | }, 18 | resolve: { 19 | root: path.resolve(__dirname), 20 | extensions: ['', '.js', '.jsx'], 21 | modulesDirectories: ['node_modules'], 22 | }, 23 | node: { 24 | fs: 'empty', 25 | }, 26 | eslint: { 27 | configFile: path.join(__dirname, '.eslintrc'), 28 | }, 29 | module: { 30 | preLoaders: [ 31 | { 32 | test: /\.jsx?$/, 33 | exclude: /node_modules/, 34 | loader: 'eslint' 35 | }, 36 | ], 37 | loaders: [ 38 | { 39 | test: /\.css$/, 40 | loaders: ['style', 'css'], 41 | }, 42 | { 43 | test: /\.json$/, 44 | loader: 'json', 45 | }, 46 | { 47 | test: /\.less$/, 48 | loaders: ['style', 'css', 'less'], 49 | }, 50 | { 51 | test: /\.scss$/, 52 | loaders: ['style', 'css', 'sass'], 53 | }, 54 | { 55 | test: /\.styl$/, 56 | loaders: ['style', 'css', 'stylus'], 57 | }, 58 | { 59 | test: /\.jsx?$/, 60 | exclude: /node_modules/, 61 | loaders: ['react-hot', 'babel'], 62 | }, 63 | { 64 | test: /\.woff\d?(\?.+)?$/, 65 | loader: 'url?limit=10000&minetype=application/font-woff', 66 | }, 67 | { 68 | test: /\.ttf(\?.+)?$/, 69 | loader: 'url?limit=10000&minetype=application/octet-stream', 70 | }, 71 | { 72 | test: /\.eot(\?.+)?$/, 73 | loader: 'url?limit=10000', 74 | }, 75 | { 76 | test: /\.svg(\?.+)?$/, 77 | loader: 'url?limit=10000&minetype=image/svg+xml', 78 | }, 79 | { 80 | test: /\.png$/, 81 | loader: 'url?limit=10000&mimetype=image/png', 82 | }, 83 | { 84 | test: /\.gif$/, 85 | loader: 'url?limit=10000&mimetype=image/gif' 86 | } 87 | ], 88 | }, 89 | plugins: [ 90 | new webpack.HotModuleReplacementPlugin(), 91 | ], 92 | }; 93 | -------------------------------------------------------------------------------- /app/templates/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | module.exports = { 5 | context: path.resolve(__dirname), 6 | entry: './src/index.js', 7 | output: { 8 | path: path.join(__dirname, 'dist'), 9 | filename: 'app.min.js', 10 | }, 11 | resolve: { 12 | root: path.resolve(__dirname), 13 | packageMains: ['style', 'main'], 14 | extensions: ['', '.js', '.jsx'], 15 | modulesDirectories: ['node_modules'], 16 | }, 17 | node: { 18 | fs: 'empty', 19 | }, 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.css$/, 24 | loader: ExtractTextPlugin.extract('style', 'css'), 25 | }, 26 | { 27 | test: /\.json$/, 28 | loader: 'json', 29 | }, 30 | { 31 | test: /\.less$/, 32 | loader: ExtractTextPlugin.extract('style', 'css!less'), 33 | }, 34 | { 35 | test: /\.scss$/, 36 | loader: ExtractTextPlugin.extract('style', 'css!sass'), 37 | }, 38 | { 39 | test: /\.styl$/, 40 | loader: ExtractTextPlugin.extract('style', 'css!stylus'), 41 | }, 42 | { 43 | test: /\.jsx?$/, 44 | exclude: /node_modules/, 45 | loader: 'babel', 46 | }, 47 | { 48 | test: /\.woff\d?(\?.+)?$/, 49 | loader: 'url?limit=1000&minetype=application/font-woff', 50 | }, 51 | { 52 | test: /\.ttf(\?.+)?$/, 53 | loader: 'url?limit=1000&minetype=application/octet-stream', 54 | }, 55 | { 56 | test: /\.eot(\?.+)?$/, 57 | loader: 'url?limit=1000', 58 | }, 59 | { 60 | test: /\.svg(\?.+)?$/, 61 | loader: 'url?limit=1000&minetype=image/svg+xml', 62 | }, 63 | { 64 | test: /\.png$/, 65 | loader: 'url?limit=1000&mimetype=image/png', 66 | }, 67 | { 68 | test: /\.gif$/, 69 | loader: 'url?limit=1000&mimetype=image/gif' 70 | } 71 | ], 72 | }, 73 | plugins: [ 74 | new ExtractTextPlugin('style.css', {allChunks: true}), 75 | ], 76 | }; 77 | -------------------------------------------------------------------------------- /common/util/alias.js: -------------------------------------------------------------------------------- 1 | var rxNode = { 2 | type: 'Property', 3 | key: { 4 | type: 'Identifier', 5 | name: 'rx' 6 | }, 7 | computed: false, 8 | value: { 9 | type: 'Literal', 10 | value: 'rx/dist/rx.lite.js' 11 | }, 12 | kind: 'init', 13 | method: false, 14 | shorthand: false, 15 | }; 16 | 17 | var aliasNode = { 18 | type: 'Property', 19 | key: { 20 | type: 'Identifier', 21 | name: 'alias' 22 | }, 23 | computed: false, 24 | value: { 25 | type: 'ObjectExpression', 26 | properties: [rxNode] 27 | }, 28 | kind: 'init', 29 | method: false, 30 | shorthand: false, 31 | }; 32 | 33 | module.exports = function(data) { 34 | var exists = false; 35 | var added = false; 36 | 37 | data.body.forEach(function(it) { 38 | if (it.type === 'ExpressionStatement') { 39 | var props = it.expression.right.properties; 40 | props.forEach(function(prop) { 41 | if (prop.key.name === 'resolve' && prop.value.properties) { 42 | prop.value.properties.forEach(function(resolve) { 43 | if (resolve.key.name === 'alias' && resolve.value.properties) { 44 | resolve.value.properties.forEach(function(res) { 45 | if (res.key.name === 'rx') { 46 | exists = true; 47 | } 48 | }); 49 | 50 | if (!exists) { 51 | if (resolve.value.properties) { 52 | resolve.value.properties.push(rxNode); 53 | } else { 54 | resolve.value.properties = [rxNode]; 55 | } 56 | added = true; 57 | } 58 | } 59 | }); 60 | 61 | if (!exists && !added) { 62 | prop.value.properties.push(aliasNode); 63 | added = true; 64 | } 65 | } 66 | }); 67 | } 68 | }); 69 | 70 | return data; 71 | }; 72 | -------------------------------------------------------------------------------- /common/util/header.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var getHeader = function (license) { 4 | if (license.toLowerCase() === 'no' || !license || !license.length) { 5 | return ''; 6 | } 7 | 8 | if (license.toLowerCase() === 'yes') { 9 | return fs.readFileSync('./LICENSE.md', 'utf8'); 10 | } 11 | 12 | return fs.readFileSync(license, 'utf8'); 13 | }; 14 | 15 | module.exports = { 16 | getHeader: getHeader, 17 | }; 18 | -------------------------------------------------------------------------------- /component/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | var _ = require('lodash'); 4 | var esprima = require('esprima'); 5 | var escodegen = require('escodegen'); 6 | var addAlias = require('../common/util/alias'); 7 | var getHeader = require('../common/util/header').getHeader; 8 | 9 | module.exports = yeoman.generators.Base.extend({ 10 | initializing: function () { 11 | var done = this.async(); 12 | 13 | this.argument('name', { 14 | required: true, 15 | type: String, 16 | desc: 'The subgenerator name' 17 | }); 18 | 19 | this.log('You called the Turris component subgenerator with the argument ' + this.name + '.'); 20 | 21 | var prompts = [{ 22 | type: 'input', 23 | name: 'license', 24 | message: 'Add header license? - no / yes (use LICENSE.md) / your file path', 25 | default: 'no', 26 | store: true, // save for future 27 | }, { 28 | type: 'list', 29 | name: 'store', 30 | message: 'Do you want to include postal, rxmq or no store?', 31 | choices: ['postal', 'rxmq', 'none'], 32 | default: 'none', 33 | store: true, 34 | }, { 35 | type: 'list', 36 | name: 'style', 37 | message: 'Do you want to use less, scss, styl or plain css?', 38 | choices: ['less', 'scss', 'styl', 'css'], 39 | default: 'less', 40 | store: true, // save for future 41 | }]; 42 | 43 | this.prompt(prompts, function (props) { 44 | this.props = props; 45 | done(); 46 | }.bind(this)); 47 | }, 48 | 49 | writing: function () { 50 | var header = getHeader(this.props.license); 51 | var cleanedName = _.kebabCase(this.name); 52 | this.fs.copyTpl( 53 | this.templatePath('index.js'), 54 | this.destinationPath('src/components/' + cleanedName + '/index.js'), 55 | {name: this.name, addStore: this.props.store, header: header, style: this.props.style} 56 | ); 57 | this.fs.copyTpl( 58 | this.templatePath('template.jsx'), 59 | this.destinationPath('src/components/' + cleanedName + '/template.jsx'), 60 | {name: this.name, header: header} 61 | ); 62 | this.fs.copyTpl( 63 | this.templatePath('style.less'), 64 | this.destinationPath('src/components/' + cleanedName + '/style.' + this.props.style), 65 | {name: this.name, header: header} 66 | ); 67 | 68 | // add tests 69 | this.fs.copyTpl( 70 | this.templatePath('tests/test.jsx'), 71 | this.destinationPath('test/component-' + cleanedName + '.jsx'), 72 | {name: this.name, header: header} 73 | ); 74 | // update test entry point 75 | var path = this.destinationPath('test/index.js'); 76 | this.fs.copy(path, path, { 77 | process: function (content) { 78 | return content + 79 | 'require(\'./component-' + cleanedName + '.jsx\');' + 80 | '\n'; 81 | } 82 | }); 83 | 84 | if (this.props.store && this.props.store !== 'none') { 85 | this.npmInstall([this.props.store], {save: true}); 86 | this.fs.copyTpl( 87 | this.templatePath('store-' + this.props.store + '.js'), 88 | this.destinationPath('src/components/' + cleanedName + '/store.js'), 89 | {name: this.name, header: header} 90 | ); 91 | 92 | // if we're using Rx, patch up webpack config 93 | if (this.props.store === 'rxmq') { 94 | // update routes 95 | var degugPath = this.destinationPath('webpack.config.js'); 96 | var prodPath = this.destinationPath('webpack.config.prod.js'); 97 | var updateWpConfig = function (content) { 98 | // check if it's already there 99 | content = content.toString(); 100 | // load as module for simpler parsing 101 | var data = esprima.parse(content); 102 | // append rx resolution 103 | data = addAlias(data); 104 | // generate new text 105 | var newContent = escodegen.generate(data); 106 | // return 107 | return newContent; 108 | }; 109 | this.fs.copy(degugPath, degugPath, {process: updateWpConfig}); 110 | this.fs.copy(prodPath, prodPath, {process: updateWpConfig}); 111 | } 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /component/templates/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import React from 'react'; 5 | import Template from './template.jsx';<% if (addStore !== 'none') { %> 6 | import <%= _.camelCase(name) %>Channel from './store.js'; 7 | <% } %> 8 | // only load style when using webpack 9 | /* istanbul ignore if */ 10 | if (__WEBPACK__) { 11 | require('./style.<%= style %>'); 12 | } 13 | 14 | const <%= _.capitalize(_.camelCase(name)) %>Component = React.createClass({ 15 | render: Template, 16 | }); 17 | 18 | export default <%= _.capitalize(_.camelCase(name)) %>Component; 19 | -------------------------------------------------------------------------------- /component/templates/store-postal.js: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import postal from 'postal'; 5 | 6 | const <%= _.camelCase(name) %>Channel = postal.channel('<%= _.camelCase(name) %>'); 7 | 8 | <%= _.camelCase(name) %>Channel.subscribe('action', ({option}, envelope) => { 9 | // ... 10 | }); 11 | 12 | export default <%= _.camelCase(name) %>Channel; 13 | -------------------------------------------------------------------------------- /component/templates/store-rxmq.js: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import rxmq from 'rxmq'; 5 | 6 | const <%= _.camelCase(name) %>Channel = rxmq.channel('<%= _.camelCase(name) %>'); 7 | 8 | <%= _.camelCase(name) %>Channel.subject('action').subscribe(({option}, envelope) => { 9 | // ... 10 | }); 11 | 12 | export default <%= _.camelCase(name) %>Channel; 13 | -------------------------------------------------------------------------------- /component/templates/style.less: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | .<%= _.kebabCase(name) %>-component { 5 | // put you styles here 6 | } 7 | -------------------------------------------------------------------------------- /component/templates/template.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import React from 'react'; 5 | 6 | const render = function() { 7 | return ( 8 |
9 | I am new <%= _.camelCase(name) %> component 10 |
11 | ); 12 | }; 13 | 14 | export default render; 15 | -------------------------------------------------------------------------------- /component/templates/tests/test.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | /* global describe, it */ 5 | // import helpers 6 | import test from 'tape'; 7 | import React from 'React/addons'; 8 | const {TestUtils} = React.addons; 9 | 10 | // import page 11 | import Component from '../src/components/<%= _.camelCase(name) %>/index.js'; 12 | 13 | test('<%= _.capitalize(name) %> component suite', function(it) { 14 | it.test('# should render', function(t) { 15 | // render 16 | const comp = TestUtils.renderIntoDocument(); 17 | 18 | // check if link and name are correct 19 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(comp, 'div'); 20 | t.equal(1, divs.length); 21 | t.end(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /docker-deploy/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | 4 | module.exports = yeoman.generators.Base.extend({ 5 | initializing: function () { 6 | this.log('You called the Turris docker-deploy subgenerator.'); 7 | }, 8 | 9 | writing: function () { 10 | this.fs.copy( 11 | this.templatePath('Dockerfile'), 12 | this.destinationPath('Dockerfile-deploy') 13 | ); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docker-deploy/templates/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | # add compiled files 4 | COPY dist /usr/share/nginx/html 5 | -------------------------------------------------------------------------------- /docker/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | 4 | module.exports = yeoman.generators.Base.extend({ 5 | initializing: function () { 6 | this.log('You called the Turris docker subgenerator.'); 7 | }, 8 | 9 | writing: function () { 10 | this.fs.copy( 11 | this.templatePath('Dockerfile'), 12 | this.destinationPath('Dockerfile') 13 | ); 14 | this.fs.copy( 15 | this.templatePath('dockerignore'), 16 | this.destinationPath('.dockerignore') 17 | ); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /docker/templates/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM iojs 2 | 3 | # pre-cache node_modules 4 | RUN mkdir /opt/tmp 5 | ADD package.json /opt/tmp/package.json 6 | WORKDIR /opt/tmp 7 | RUN npm install --silent 8 | 9 | # add app files 10 | ADD . /opt/application 11 | WORKDIR /opt/application 12 | RUN cp -R /opt/tmp/node_modules /opt/application/ 13 | 14 | # run 15 | CMD ["npm", "start"] 16 | -------------------------------------------------------------------------------- /docker/templates/dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-turris", 3 | "version": "0.9.0", 4 | "description": "Yeoman generator for Turris.js applications done with React and ES6", 5 | "license": "MIT", 6 | "main": "app/index.js", 7 | "repository": "turrisjs/generator-turris", 8 | "author": { 9 | "name": "Tim Ermilov", 10 | "email": "yamalight@gmail.com", 11 | "url": "https://github.com/yamalight" 12 | }, 13 | "scripts": { 14 | "test": "mocha" 15 | }, 16 | "files": [ 17 | "app", 18 | "page", 19 | "component", 20 | "docker", 21 | "common" 22 | ], 23 | "keywords": [ 24 | "yeoman-generator", 25 | "turrisjs", 26 | "react", 27 | "react-router", 28 | "es6", 29 | "babel" 30 | ], 31 | "dependencies": { 32 | "chalk": "^1.0.0", 33 | "escodegen": "^1.6.1", 34 | "esprima": "^2.5.0", 35 | "lodash": "^3.8.0", 36 | "yeoman-generator": "^0.19.0", 37 | "yosay": "^1.0.2" 38 | }, 39 | "devDependencies": { 40 | "mocha": "*" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /page/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | var _ = require('lodash'); 4 | var getHeader = require('../common/util/header').getHeader; 5 | 6 | module.exports = yeoman.generators.Base.extend({ 7 | initializing: function () { 8 | var done = this.async(); 9 | 10 | this.argument('name', { 11 | required: true, 12 | type: String, 13 | desc: 'The subgenerator name' 14 | }); 15 | 16 | this.log('You called the Turris page subgenerator with the argument ' + this.name + '.'); 17 | 18 | var prompts = [{ 19 | type: 'input', 20 | name: 'license', 21 | message: 'Add header license? - no / yes (use LICENSE.md) / your file path', 22 | default: 'no', 23 | store: true, // save for future 24 | }]; 25 | 26 | this.prompt(prompts, function (props) { 27 | this.props = props; 28 | done(); 29 | }.bind(this)); 30 | }, 31 | 32 | writing: function () { 33 | var header = getHeader(this.props.license); 34 | var camelcaseName = _.camelCase(this.name); 35 | this.fs.copyTpl( 36 | this.templatePath('index.js'), 37 | this.destinationPath('src/pages/' + camelcaseName + '/index.js'), 38 | {name: this.name, header: header} 39 | ); 40 | this.fs.copyTpl( 41 | this.templatePath('page.js'), 42 | this.destinationPath('src/pages/' + camelcaseName + '/page.js'), 43 | {name: this.name, header: header} 44 | ); 45 | this.fs.copyTpl( 46 | this.templatePath('template.jsx'), 47 | this.destinationPath('src/pages/' + camelcaseName + '/template.jsx'), 48 | {name: this.name, header: header} 49 | ); 50 | // update routes 51 | var path = this.destinationPath('src/routes.js'); 52 | var capitalizedName = _.capitalize(camelcaseName); 53 | this.fs.copy(path, path, { 54 | process: function (content) { 55 | // add new import 56 | var re = /import(.+?);/; 57 | var newString = 'import ' + capitalizedName; 58 | newString += ' from \'./pages/' + camelcaseName + '/index.js\';\nimport$1;'; 59 | var newContent = content.toString().replace(re, newString); 60 | // add new route to array 61 | newString = 'const routes = [\n ' + capitalizedName + ','; 62 | re = /const routes = \[/; 63 | newContent = newContent.replace(re, newString); 64 | // return new file 65 | return newContent; 66 | } 67 | }); 68 | 69 | // add tests 70 | this.fs.copyTpl( 71 | this.templatePath('tests/test.jsx'), 72 | this.destinationPath('test/page-' + camelcaseName + '.jsx'), 73 | {name: this.name, header: header} 74 | ); 75 | // update test entry point 76 | var testPath = this.destinationPath('test/index.js'); 77 | this.fs.copy(testPath, testPath, { 78 | process: function (content) { 79 | return content + 80 | 'require(\'./page-' + camelcaseName + '.jsx\');' + 81 | '\n'; 82 | } 83 | }); 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /page/templates/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import React from 'react'; 5 | import {Route} from 'react-router'; 6 | import <%= _.capitalize(_.camelCase(name)) %>Page from './page.js'; 7 | 8 | const route = React.createElement(Route, {name: '<%= _.camelCase(name) %>', key: 'route_<%= _.camelCase(name) %>', handler: <%= _.capitalize(_.camelCase(name)) %>Page}); 9 | 10 | export default route; 11 | -------------------------------------------------------------------------------- /page/templates/page.js: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import React from 'react'; 5 | import Template from './template.jsx'; 6 | 7 | const <%= _.capitalize(_.camelCase(name)) %>Page = React.createClass({ 8 | render: Template, 9 | }); 10 | 11 | export default <%= _.capitalize(_.camelCase(name)) %>Page; 12 | -------------------------------------------------------------------------------- /page/templates/template.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | import React from 'react'; 5 | 6 | const render = function() { 7 | return ( 8 |

I am new <%= _.camelCase(name) %> page

9 | ); 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /page/templates/tests/test.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | <%= header %> 3 | */ 4 | /* global describe, it */ 5 | // import helpers 6 | import test from 'tape'; 7 | import React from 'React/addons'; 8 | const {TestUtils} = React.addons; 9 | 10 | // import page 11 | import Page from '../src/pages/<%= _.camelCase(name) %>/page.js'; 12 | 13 | test('<%= _.capitalize(name) %> page suite', function(it) { 14 | it.test('# should render', function(t) { 15 | // render 16 | const comp = TestUtils.renderIntoDocument(); 17 | 18 | // check if link and name are correct 19 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(comp, 'h1'); 20 | t.equal(1, divs.length); 21 | t.end(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/docker-deploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | 7 | describe('Turris:docker-deploy', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../docker-deploy')) 10 | .withArguments('name') 11 | .withOptions({ skipInstall: true, force: true }) 12 | .on('end', done); 13 | }); 14 | 15 | it('creates files', function () { 16 | assert.file([ 17 | 'Dockerfile-deploy' 18 | ]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | var os = require('os'); 7 | 8 | describe('turris:app', function () { 9 | before(function (done) { 10 | helpers.run(path.join(__dirname, '../app')) 11 | .withOptions({ skipInstall: true }) 12 | .withPrompts({ someOption: true }) 13 | .on('end', done); 14 | }); 15 | 16 | it('creates files', function () { 17 | assert.file([ 18 | 'package.json', 19 | '.editorconfig', 20 | '.gitignore' 21 | ]); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/test-component.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | 7 | describe('Turris:component', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../component')) 10 | .withArguments('name') 11 | .withOptions({ skipInstall: true, force: true }) 12 | .on('end', done); 13 | }); 14 | 15 | it('creates files', function () { 16 | assert.file([ 17 | 'somefile.js' 18 | ]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-docker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | 7 | describe('Turris:docker', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../docker')) 10 | .withArguments('name') 11 | .withOptions({ skipInstall: true, force: true }) 12 | .on('end', done); 13 | }); 14 | 15 | it('creates files', function () { 16 | assert.file([ 17 | 'somefile.js' 18 | ]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | 7 | describe('Turris:page', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../page')) 10 | .withArguments('name') 11 | .withOptions({ skipInstall: true, force: true }) 12 | .on('end', done); 13 | }); 14 | 15 | it('creates files', function () { 16 | assert.file([ 17 | 'somefile.js' 18 | ]); 19 | }); 20 | }); 21 | --------------------------------------------------------------------------------