diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..beffa30
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4638240
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,138 @@
+
+# Created by https://www.gitignore.io/api/gitbook,osx,webstorm,node
+
+### GitBook ###
+# Node rules:
+## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+## Dependency directory
+## Commenting this out is preferred by some people, see
+## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
+node_modules
+
+# Book build output
+_book
+
+# eBook build output
+*.epub
+*.mobi
+*.pdf
+
+
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+
+*.iml
+
+## Directory-based project format:
+.idea/
+# if you remove the above rule, at least ignore the following:
+
+# User-specific stuff:
+# .idea/workspace.xml
+# .idea/tasks.xml
+# .idea/dictionaries
+# .idea/shelf
+
+# Sensitive or high-churn files:
+# .idea/dataSources.ids
+# .idea/dataSources.xml
+# .idea/sqlDataSources.xml
+# .idea/dynamic.xml
+# .idea/uiDesigner.xml
+
+# Gradle:
+# .idea/gradle.xml
+# .idea/libraries
+
+# Mongo Explorer plugin:
+# .idea/mongoSettings.xml
+
+## File-based project format:
+*.ipr
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
+node_modules
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+.tmp
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f2f84e8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - v6
+ - v5
+ - v4
+ - '0.12'
+ - '0.10'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bd35f4e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2016 WalmartLabs
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
index b1b4f40..a2b239b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,36 @@
-# generator-electrode
-Yeoman generator for Electrode App
+# generator-electrode [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]
+> Generate Electrode Isomorphic React App with NodeJS backend.
+
+## Installation
+
+First, install [Yeoman](http://yeoman.io) and generator-electrode using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
+
+```bash
+npm install -g yo
+npm install -g generator-electrode
+```
+
+Then generate your new project:
+
+```bash
+yo electrode
+```
+
+## Getting To Know Yeoman
+
+ * Yeoman has a heart of gold.
+ * Yeoman is a person with feelings and opinions, but is very easy to work with.
+ * Yeoman can be too opinionated at times but is easily convinced not to be.
+ * Feel free to [learn more about Yeoman](http://yeoman.io/).
+
+## License
+
+Apache-2.0 © WalmartLabs
+
+
+[npm-image]: https://badge.fury.io/js/generator-electrode.svg
+[npm-url]: https://npmjs.org/package/generator-electrode
+[travis-image]: https://travis-ci.org/electrode-io/generator-electrode.svg?branch=master
+[travis-url]: https://travis-ci.org/electrode-io/generator-electrode
+[daviddm-image]: https://david-dm.org/electrode-io/generator-electrode.svg?theme=shields.io
+[daviddm-url]: https://david-dm.org/electrode-io/generator-electrode
diff --git a/generators/app/index.js b/generators/app/index.js
new file mode 100644
index 0000000..449062a
--- /dev/null
+++ b/generators/app/index.js
@@ -0,0 +1,292 @@
+'use strict';
+
+/* eslint-disable arrow-parens */
+
+var generators = require('yeoman-generator');
+var chalk = require('chalk');
+var yosay = require('yosay');
+var path = require('path');
+var _ = require('lodash');
+var extend = _.merge;
+var parseAuthor = require('parse-author');
+var githubUsername = require('github-username');
+
+module.exports = generators.Base.extend({
+ constructor: function () {
+ generators.Base.apply(this, arguments);
+
+ this.option('generateInto', {
+ type: String,
+ required: false,
+ defaults: '',
+ desc: 'Relocate the location of the generated files.'
+ });
+
+ this.option('travis', {
+ type: Boolean,
+ required: false,
+ defaults: true,
+ desc: 'Include travis config'
+ });
+
+ this.option('license', {
+ type: Boolean,
+ required: false,
+ defaults: true,
+ desc: 'Include a license'
+ });
+
+ this.option('name', {
+ type: String,
+ required: false,
+ desc: 'Project name'
+ });
+
+ this.option('githubAccount', {
+ type: String,
+ required: false,
+ desc: 'GitHub username or organization'
+ });
+
+ this.option('projectRoot', {
+ type: String,
+ required: false,
+ defaults: 'lib',
+ desc: 'Relative path to the project code root'
+ });
+
+ this.option('readme', {
+ type: String,
+ required: false,
+ desc: 'Content to insert in the README.md file'
+ });
+ },
+
+ initializing: function () {
+ this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {});
+
+ if (this.pkg.keywords) {
+ this.pkg.keywords = this.pkg.keywords.filter((x) => x);
+ }
+
+ // Pre set the default props from the information we have at this point
+ this.props = {
+ name: this.pkg.name,
+ description: this.pkg.description,
+ version: this.pkg.version,
+ homepage: this.pkg.homepage
+ };
+
+ if (_.isObject(this.pkg.author)) {
+ this.props.authorName = this.pkg.author.name;
+ this.props.authorEmail = this.pkg.author.email;
+ this.props.authorUrl = this.pkg.author.url;
+ } else if (_.isString(this.pkg.author)) {
+ var info = parseAuthor(this.pkg.author);
+ this.props.authorName = info.name;
+ this.props.authorEmail = info.email;
+ this.props.authorUrl = info.url;
+ }
+ },
+
+ prompting: {
+ greeting: function () {
+ this.log(yosay(
+ 'Welcome to the phenomenal ' + chalk.red('Electrode App') + ' generator!'
+ ));
+ },
+
+ askFor: function () {
+ if (this.pkg.name || this.options.name) {
+ this.props.name = this.pkg.name || _.kebabCase(this.options.name);
+ }
+
+ var prompts = [
+ {
+ name: 'name',
+ message: 'Application Name',
+ when: !this.props.name,
+ default: path.basename(process.cwd())
+ },
+ {
+ name: 'description',
+ message: 'Description',
+ when: !this.props.description
+ },
+ {
+ name: 'homepage',
+ message: 'Project homepage url',
+ when: !this.props.homepage
+ },
+ {
+ name: 'authorName',
+ message: 'Author\'s Name',
+ when: !this.props.authorName,
+ default: this.user.git.name(),
+ store: true
+ },
+ {
+ name: 'authorEmail',
+ message: 'Author\'s Email',
+ when: !this.props.authorEmail,
+ default: this.user.git.email(),
+ store: true
+ },
+ {
+ name: 'authorUrl',
+ message: 'Author\'s Homepage',
+ when: !this.props.authorUrl,
+ store: true
+ },
+ {
+ name: 'keywords',
+ message: 'Package keywords (comma to split)',
+ when: _.isEmpty(this.pkg.keywords),
+ filter: function (words) {
+ return words.split(/\s*,\s*/g).filter((x) => x);
+ }
+ }
+ ];
+
+ return this.prompt(prompts).then((props) => {
+ this.props = extend(this.props, props);
+ });
+ },
+
+ askForGithubAccount: function () {
+ if (this.options.githubAccount) {
+ this.props.githubAccount = this.options.githubAccount;
+ return;
+ }
+ var done = this.async();
+
+ githubUsername(this.props.authorEmail, (err, username) => {
+ if (err) {
+ username = username || '';
+ }
+ this.prompt({
+ name: 'githubAccount',
+ message: 'GitHub username or organization',
+ default: username
+ }).then((prompt) => {
+ this.props.githubAccount = prompt.githubAccount;
+ done();
+ });
+ });
+ }
+ },
+
+ writing: function () {
+ // Re-read the content at this point because a composed generator might modify it.
+ var currentPkg = this.fs.readJSON(this.destinationPath('package.json'), {});
+ var defaultPkg = require(this.templatePath('package.json'));
+
+ ['name', 'version', 'description', 'homepage', 'main', 'license'].forEach((x) => {
+ currentPkg[x] = currentPkg[x] || undefined;
+ });
+
+ var updatePkg = _.defaultsDeep(currentPkg, {
+ name: _.kebabCase(this.props.name),
+ version: '0.0.0',
+ description: this.props.description,
+ homepage: this.props.homepage,
+ author: {
+ name: this.props.authorName,
+ email: this.props.authorEmail,
+ url: this.props.authorUrl
+ },
+ files: [
+ this.options.projectRoot
+ ],
+ main: path.join(
+ this.options.projectRoot,
+ 'index.js'
+ ).replace(/\\/g, '/'),
+ keywords: []
+ });
+
+ var pkg = extend({}, defaultPkg, updatePkg);
+
+ // Combine the keywords
+ if (this.props.keywords) {
+ pkg.keywords = _.uniq(this.props.keywords.concat(pkg.keywords)).filter((x) => x);
+ }
+
+ // Let's extend package.json so we're not overwriting user previous fields
+ this.fs.writeJSON(this.destinationPath('package.json'), pkg);
+
+ this.fs.copy(
+ this.templatePath('babelrc'),
+ this.destinationPath(this.options.generateInto, '.babelrc')
+ );
+
+ ['gulpfile.js', 'client', 'config', 'server', 'test'].forEach((f) => {
+ this.fs.copy(
+ this.templatePath(f),
+ this.destinationPath(this.options.generateInto, f)
+ );
+ });
+ },
+
+ default: function () {
+ if (this.options.travis) {
+ this.composeWith('travis', {}, {
+ local: require.resolve('generator-travis/generators/app')
+ });
+ }
+
+ this.composeWith('electrode:editorconfig', {}, {
+ local: require.resolve('../editorconfig')
+ });
+
+ this.composeWith('electrode:git', {
+ options: {
+ name: this.props.name,
+ githubAccount: this.props.githubAccount
+ }
+ }, {
+ local: require.resolve('../git')
+ });
+
+ if (this.options.license && !this.pkg.license) {
+ this.composeWith('license', {
+ options: {
+ name: this.props.authorName,
+ email: this.props.authorEmail,
+ website: this.props.authorUrl
+ }
+ }, {
+ local: require.resolve('generator-license/app')
+ });
+ }
+
+ if (!this.fs.exists(this.destinationPath('README.md'))) {
+ this.composeWith('electrode:readme', {
+ options: {
+ name: this.props.name,
+ description: this.props.description,
+ githubAccount: this.props.githubAccount,
+ authorName: this.props.authorName,
+ authorUrl: this.props.authorUrl,
+ content: this.options.readme
+ }
+ }, {
+ local: require.resolve('../readme')
+ });
+ }
+
+ if (!this.fs.exists(this.destinationPath('server/plugins/webapp'))) {
+ this.composeWith('electrode:webapp', {
+ options: {
+ }
+ }, {
+ local: require.resolve('../webapp')
+ });
+ }
+
+ },
+
+ install: function () {
+ this.installDependencies();
+ }
+});
diff --git a/generators/app/templates/babelrc b/generators/app/templates/babelrc
new file mode 100644
index 0000000..e989ef9
--- /dev/null
+++ b/generators/app/templates/babelrc
@@ -0,0 +1,3 @@
+{
+ "extends": "./node_modules/electrode-archetype-react-app/config/babel/.babelrc"
+}
diff --git a/generators/app/templates/client/app.jsx b/generators/app/templates/client/app.jsx
new file mode 100644
index 0000000..992b6c6
--- /dev/null
+++ b/generators/app/templates/client/app.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { routes } from "./routes";
+import { Router } from "react-router";
+import { Resolver } from "react-resolver";
+import { createHistory } from "history";
+
+window.webappStart = () => {
+ Resolver.render(
+ () => {routes},
+ document.querySelector(".js-content")
+ );
+};
+
diff --git a/generators/app/templates/client/components/home.jsx b/generators/app/templates/client/components/home.jsx
new file mode 100644
index 0000000..f7e0298
--- /dev/null
+++ b/generators/app/templates/client/components/home.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export class Home extends React.Component {
+ render() {
+ return (
+
Hello React
+ );
+ }
+}
diff --git a/generators/app/templates/client/routes.jsx b/generators/app/templates/client/routes.jsx
new file mode 100644
index 0000000..568897a
--- /dev/null
+++ b/generators/app/templates/client/routes.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+import { Route, IndexRoute} from "react-router";
+import { Home } from "./components/home";
+
+export const routes = (
+
+);
diff --git a/generators/app/templates/config/default.json b/generators/app/templates/config/default.json
new file mode 100644
index 0000000..17ca742
--- /dev/null
+++ b/generators/app/templates/config/default.json
@@ -0,0 +1,16 @@
+{
+ "plugins": {
+ "webapp": {
+ "module": "./server/plugins/webapp",
+ "options": {
+ "paths": {
+ "/{args*}": {
+ "content": {
+ "module": "./server/views/index-view"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/generators/app/templates/config/development.json b/generators/app/templates/config/development.json
new file mode 100644
index 0000000..85e4e4c
--- /dev/null
+++ b/generators/app/templates/config/development.json
@@ -0,0 +1,13 @@
+{
+ "plugins": {
+ "inert": {
+ "enable": true
+ },
+ "staticPaths": {
+ "enable": true,
+ "options": {
+ "pathPrefix": "dist"
+ }
+ }
+ }
+}
diff --git a/generators/app/templates/config/production.json b/generators/app/templates/config/production.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/generators/app/templates/config/production.json
@@ -0,0 +1 @@
+{}
diff --git a/generators/app/templates/gulpfile.js b/generators/app/templates/gulpfile.js
new file mode 100644
index 0000000..a2ee8ce
--- /dev/null
+++ b/generators/app/templates/gulpfile.js
@@ -0,0 +1 @@
+require("electrode-archetype-react-app")();
diff --git a/generators/app/templates/package.json b/generators/app/templates/package.json
new file mode 100644
index 0000000..acab462
--- /dev/null
+++ b/generators/app/templates/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "application-name",
+ "version": "0.0.1",
+ "description": "Isomorphic React Application With NodeJS backend",
+ "homepage": "",
+ "author": {},
+ "contributors": [],
+ "files": [],
+ "main": "server/index.js",
+ "keywords": [],
+ "repository": {},
+ "license": "UNLICENSED",
+ "scripts": {
+ "test": "gulp test",
+ "coverage": "gulp check"
+ },
+ "dependencies": {
+ "bluebird": "^2.10.2",
+ "electrode-router-resolver-engine": "^1.0.0",
+ "electrode-server": "^1.0.0",
+ "lodash": "^4.10.1"
+ },
+ "devDependencies": {
+ "electrode-archetype-react-app": "^1.0.0",
+ "electrode-archetype-react-app-dev": "^1.0.0",
+ "gulp": "^3.9.1"
+ }
+}
diff --git a/generators/app/templates/server/index.js b/generators/app/templates/server/index.js
new file mode 100644
index 0000000..5265f1d
--- /dev/null
+++ b/generators/app/templates/server/index.js
@@ -0,0 +1,9 @@
+"use strict";
+process.on('SIGINT', function () {
+ process.exit(0);
+});
+require("babel-register")({
+ ignore: /node_modules\/(?!react\/)/
+});
+const config = require("electrode-confippet").config;
+require("electrode-server")(config);
diff --git a/generators/editorconfig/index.js b/generators/editorconfig/index.js
new file mode 100644
index 0000000..0accc2b
--- /dev/null
+++ b/generators/editorconfig/index.js
@@ -0,0 +1,22 @@
+'use strict';
+var generators = require('yeoman-generator');
+
+module.exports = generators.Base.extend({
+ constructor: function () {
+ generators.Base.apply(this, arguments);
+
+ this.option('generateInto', {
+ type: String,
+ required: false,
+ defaults: '',
+ desc: 'Relocate the location of the generated files.'
+ });
+ },
+
+ initializing: function () {
+ this.fs.copy(
+ this.templatePath('editorconfig'),
+ this.destinationPath(this.options.generateInto, '.editorconfig')
+ );
+ }
+});
diff --git a/generators/editorconfig/templates/editorconfig b/generators/editorconfig/templates/editorconfig
new file mode 100644
index 0000000..beffa30
--- /dev/null
+++ b/generators/editorconfig/templates/editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/generators/git/index.js b/generators/git/index.js
new file mode 100644
index 0000000..aa11970
--- /dev/null
+++ b/generators/git/index.js
@@ -0,0 +1,83 @@
+'use strict';
+var generators = require('yeoman-generator');
+var originUrl = require('git-remote-origin-url');
+
+module.exports = generators.Base.extend({
+ constructor: function () {
+ generators.Base.apply(this, arguments);
+
+ this.option('generateInto', {
+ type: String,
+ required: false,
+ defaults: '',
+ desc: 'Relocate the location of the generated files.'
+ });
+
+ this.option('name', {
+ type: String,
+ required: true,
+ desc: 'Module name'
+ });
+
+ this.option('github-account', {
+ type: String,
+ required: true,
+ desc: 'GitHub username or organization'
+ });
+ },
+
+ initializing: function () {
+ this.fs.copy(
+ this.templatePath('gitattributes'),
+ this.destinationPath(this.options.generateInto, '.gitattributes')
+ );
+
+ this.fs.copy(
+ this.templatePath('gitignore'),
+ this.destinationPath(this.options.generateInto, '.gitignore')
+ );
+
+ return originUrl(this.destinationPath(this.options.generateInto))
+ .then(function (url) {
+ this.originUrl = url;
+ }.bind(this), function () {
+ this.originUrl = '';
+ }.bind(this));
+ },
+
+ writing: function () {
+ this.pkg = this.fs.readJSON(this.destinationPath(this.options.generateInto, 'package.json'), {});
+
+ var repository = '';
+ if (this.originUrl) {
+ repository = this.originUrl;
+ } else {
+ repository = this.options.githubAccount + '/' + this.options.name;
+ }
+
+ this.pkg.repository = this.pkg.repository || {};
+ if (!this.pkg.repository.url) {
+ this.pkg.repository.type = 'git';
+ this.pkg.repository.url = repository;
+ }
+
+ this.fs.writeJSON(this.destinationPath(this.options.generateInto, 'package.json'), this.pkg);
+ },
+
+ end: function () {
+ this.spawnCommandSync('git', ['init'], {
+ cwd: this.destinationPath(this.options.generateInto)
+ });
+
+ if (!this.originUrl) {
+ var repoSSH = this.pkg.repository;
+ var url = this.pkg.repository && this.pkg.repository.url;
+ if (url && url.indexOf('.git') === -1) {
+ repoSSH = 'git@github.com:' + this.pkg.repository + '.git';
+ }
+ this.spawnCommandSync('git', ['remote', 'add', 'origin', repoSSH], {
+ cwd: this.destinationPath(this.options.generateInto)
+ });
+ }
+ }
+});
diff --git a/generators/git/templates/gitattributes b/generators/git/templates/gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/generators/git/templates/gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/generators/git/templates/gitignore b/generators/git/templates/gitignore
new file mode 100644
index 0000000..ad8ad8c
--- /dev/null
+++ b/generators/git/templates/gitignore
@@ -0,0 +1,157 @@
+
+
+# Created by https://www.gitignore.io/api/gitbook,node,webstorm,bower,osx
+
+### GitBook ###
+# Node rules:
+## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+## Dependency directory
+## Commenting this out is preferred by some people, see
+## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
+node_modules
+
+# Book build output
+_book
+
+# eBook build output
+*.epub
+*.mobi
+*.pdf
+
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
+node_modules
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+
+*.iml
+
+## Directory-based project format:
+.idea/
+# if you remove the above rule, at least ignore the following:
+
+# User-specific stuff:
+# .idea/workspace.xml
+# .idea/tasks.xml
+# .idea/dictionaries
+# .idea/shelf
+
+# Sensitive or high-churn files:
+# .idea/dataSources.ids
+# .idea/dataSources.xml
+# .idea/sqlDataSources.xml
+# .idea/dynamic.xml
+# .idea/uiDesigner.xml
+
+# Gradle:
+# .idea/gradle.xml
+# .idea/libraries
+
+# Mongo Explorer plugin:
+# .idea/mongoSettings.xml
+
+## File-based project format:
+*.ipr
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+
+### Bower ###
+bower_components
+.bower-cache
+.bower-registry
+.bower-tmp
+
+
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# others
+.hg
+.project
+.tmp
+
+# Build
+dist
+Procfile
+config/assets.json
+npm-shrinkwrap.json
+
+.isomorphic-loader-config.json
diff --git a/generators/readme/index.js b/generators/readme/index.js
new file mode 100644
index 0000000..7521b72
--- /dev/null
+++ b/generators/readme/index.js
@@ -0,0 +1,79 @@
+'use strict';
+var _ = require('lodash');
+var generators = require('yeoman-generator');
+
+module.exports = generators.Base.extend({
+ constructor: function () {
+ generators.Base.apply(this, arguments);
+
+ this.option('generateInto', {
+ type: String,
+ required: false,
+ defaults: '',
+ desc: 'Relocate the location of the generated files.'
+ });
+
+ this.option('name', {
+ type: String,
+ required: true,
+ desc: 'Project name'
+ });
+
+ this.option('description', {
+ type: String,
+ required: true,
+ desc: 'Project description'
+ });
+
+ this.option('githubAccount', {
+ type: String,
+ required: true,
+ desc: 'User github account'
+ });
+
+ this.option('authorName', {
+ type: String,
+ required: true,
+ desc: 'Author name'
+ });
+
+ this.option('authorUrl', {
+ type: String,
+ required: true,
+ desc: 'Author url'
+ });
+
+ this.option('coveralls', {
+ type: Boolean,
+ required: true,
+ desc: 'Include coveralls badge'
+ });
+
+ this.option('content', {
+ type: String,
+ required: false,
+ desc: 'Readme content'
+ });
+ },
+
+ writing: function () {
+ var pkg = this.fs.readJSON(this.destinationPath(this.options.generateInto, 'package.json'), {});
+ this.fs.copyTpl(
+ this.templatePath('README.md'),
+ this.destinationPath(this.options.generateInto, 'README.md'),
+ {
+ projectName: this.options.name,
+ safeProjectName: _.camelCase(this.options.name),
+ description: this.options.description,
+ githubAccount: this.options.githubAccount,
+ author: {
+ name: this.options.authorName,
+ url: this.options.authorUrl
+ },
+ license: pkg.license,
+ includeCoveralls: this.options.coveralls,
+ content: this.options.content
+ }
+ );
+ }
+});
diff --git a/generators/readme/templates/README.md b/generators/readme/templates/README.md
new file mode 100644
index 0000000..8fd620d
--- /dev/null
+++ b/generators/readme/templates/README.md
@@ -0,0 +1,37 @@
+# <%= projectName %> [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]<%
+if (includeCoveralls) { %> [![Coverage percentage][coveralls-image]][coveralls-url]<% } -%>
+
+> <%= description %>
+
+<% if (!content) { -%>
+## Installation
+
+```sh
+$ npm install --save <%= projectName %>
+```
+
+## Usage
+
+```js
+var <%= safeProjectName %> = require('<%= projectName %>');
+
+<%= safeProjectName %>('Rainbow');
+```
+<% } else { -%>
+<%= content %>
+<% } -%>
+## License
+
+<%= license %> © [<%= author.name %>](<%= author.url %>)
+
+
+[npm-image]: https://badge.fury.io/js/<%= projectName %>.svg
+[npm-url]: https://npmjs.org/package/<%= projectName %>
+[travis-image]: https://travis-ci.org/<%= githubAccount %>/<%= projectName %>.svg?branch=master
+[travis-url]: https://travis-ci.org/<%= githubAccount %>/<%= projectName %>
+[daviddm-image]: https://david-dm.org/<%= githubAccount %>/<%= projectName %>.svg?theme=shields.io
+[daviddm-url]: https://david-dm.org/<%= githubAccount %>/<%= projectName %>
+<% if (includeCoveralls) { -%>
+[coveralls-image]: https://coveralls.io/repos/<%= githubAccount %>/<%= projectName %>/badge.svg
+[coveralls-url]: https://coveralls.io/r/<%= githubAccount %>/<%= projectName %>
+<% } -%>
diff --git a/generators/webapp/index.js b/generators/webapp/index.js
new file mode 100644
index 0000000..c966d8c
--- /dev/null
+++ b/generators/webapp/index.js
@@ -0,0 +1,23 @@
+'use strict';
+var _ = require('lodash');
+var generators = require('yeoman-generator');
+
+module.exports = generators.Base.extend({
+ constructor: function () {
+ generators.Base.apply(this, arguments);
+
+ this.option('generateInto', {
+ type: String,
+ required: false,
+ defaults: '',
+ desc: 'Relocate the location of the generated files.'
+ });
+ },
+
+ writing: function () {
+ this.fs.copy(
+ this.templatePath('server'),
+ this.destinationPath(this.options.generateInto, 'server')
+ );
+ }
+});
diff --git a/generators/webapp/templates/server/plugins/webapp/index.html b/generators/webapp/templates/server/plugins/webapp/index.html
new file mode 100644
index 0000000..a826c42
--- /dev/null
+++ b/generators/webapp/templates/server/plugins/webapp/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ {{PAGE_TITLE}}
+ {{WEBAPP_BUNDLES}}
+ {{PREFETCH_BUNDLES}}
+
+
+{{SSR_CONTENT}}
+
+
+
diff --git a/generators/webapp/templates/server/plugins/webapp/index.js b/generators/webapp/templates/server/plugins/webapp/index.js
new file mode 100644
index 0000000..96e235d
--- /dev/null
+++ b/generators/webapp/templates/server/plugins/webapp/index.js
@@ -0,0 +1,187 @@
+"use strict";
+
+const _ = require("lodash");
+const Promise = require("bluebird");
+const fs = require("fs");
+const Path = require("path");
+const assert = require("assert");
+
+const HTTP_ERROR_500 = 500;
+const HTTP_REDIRECT = 302;
+
+/**
+ * Load stats.json which is created during build.
+ * The file contains bundle files which are to be loaded on the client side.
+ *
+ * @param {string} statsFilePath - path of stats.json
+ * @returns {Promise.