yoctore/yoctopus-generator

View on GitHub
app/index copie.js

Summary

Maintainability
F
1 mo
Test Coverage
'use strict';

// tricks for hinter
var jsbeautyKey     = 'js_beautify';
// dependencies
var generators      = require('yeoman-generator');
var _               = require('lodash');
var n               = require('n-api');
var request         = require('request');
var chalk           = require('chalk');
var logger          = require('yocto-logger');
var spdx            = require('spdx');
var isEmail         = require('isemail');
var isUrl           = require('is-url');
var path            = require('path');
var uuid            = require('uuid');
var async           = require('async');
var fs              = require('fs-extra');
var Spinner         = require('cli-spinner').Spinner;
var time            = require('time');
var GruntfileEditor = require('gruntfile-editor');
var jsbeauty        = require('js-beautify')[jsbeautyKey];
var yosay           = require('yosay');

/**
 * Default export
 */
module.exports = generators.Base.extend({
  /**
   * Default constructor
   */
  constructor   :  function () {
    // Calling the super constructor is important so our generator is correctly set up
    generators.Base.apply(this, arguments);
    /**
     * Current grunt file editor. we dont use yeaoman generator beacuse
     * we can't remove autoloading of Gruntfile.js
     */
    this.gruntEditor  = new GruntfileEditor();

    /**
     * Default spinner instance
     */
    this.spinner      = new Spinner('%s');
    /**
     * Default internal config
     *
     * @property {Object}
     */
    this.cfg = {
      angular     : {
        url         : 'https://code.angularjs.org/',
        resolution  : 'latest',
        versions    : [ 'latest' ]
      },
      delay       : 2000,
      name        : 'YoctopusJs',
      nVersions   : n.ls(),
      debug       : true,
      paths       : {
        dependencies  : [ this.sourceRoot(), 'config/dependencies.json' ].join('/'),
        folders       : [ this.sourceRoot(), 'config/folders.json' ].join('/'),
        grunt         : [ this.sourceRoot(), 'config/gruntfile.json' ].join('/')
      },
      generate    : {
        node    : false,
        angular : false
      },
      codes       : {
        eManual   : 500,
        dFailed   : 501,
        fFailed   : 502,
        gFailed   : 503
      },
      ascii       : {
        coffee    : 'coffee',
        debug     : 'debug-mode',
        bye       : 'goodbye',
        welcome   : 'welcome',
        error     : 'octopus-error',
        file      : 'remove-file-required'
      }
    };

    /**
     * Default ascii method to display content to ascii
     *
     * @param {String} name ascii name to get
     * @param {Boolean} exit if true en current program
     */
    this.asciiMessage = function (name, exit) {
      // normalize exit process
      exit = _.isBoolean(exit) ? exit : false;

      // normalize path
      var p = this.normalizePath([ [ this.sourceRoot() , 'ascii', name ].join('/'),
                                   'txt' ].join('.'));

      // file exists
      if (this.fs.exists(p)) {
        // save current content
        var content = this.fs.read(p);

        // is debug ?
        if (name === this.cfg.ascii.debug) {
          content = chalk.blue(content);
        }

        // is coffee ?
        if (name === this.cfg.ascii.coffee) {
          content = chalk.green(content);
        }

        // is welcome or bye ?
        if (name === this.cfg.ascii.welcome || name === this.cfg.ascii.bye) {
          content = chalk.cyan(content).replace('%s', this.getElapsedTime());
        }

        // is error ?
        if (name === this.cfg.ascii.error) {
          content = chalk.req(content);
        }

        // is file ?
        if (name === this.cfg.ascii.file) {
          content = chalk.yellow(content);
        }

        // log message
        this.log(content);
      }
      // exit ?
      if (exit) {
        process.exit(this.cfg.codes.eManual);
      }
    };
    /**
     * Utility method to normalize given
     *
     * @param {String} p current path to use
     * @return {String} generated path
     */
    this.normalizePath = function (p) {
      // default statement
      return path.normalize(p);
    };
    /**
     * Utility method to prefix path for debug mode
     *
     * @param {String} path current path to use
     * @return {String} generated path
     */
    this.prefixPath = function (path) {
      // is debug ?
      if (this.cfg.debug) {
        // normalize process
        return this.normalizePath([ this.destinationRoot(), '/debug', path ].join('/'));
      }

      // default statement
      return this.normalizePath([ this.destinationRoot(), path ].join('/'));
    };
    /**
     * Return elasped time label
     *
     * @return {String} elapsed time
     */
    this.getElapsedTime = function () {
      // process diff
      var diff = time.time() - this.time;
      // t to process
      var t    = time.localtime(diff);
      // build time
      t        = _.compact(
                  _.flatten([
                   [ (t.minutes > 0 ? [ t.minutes, 'minutes' ].join(' ') : '') ],
                   [ (t.seconds > 0 ? [ t.seconds, 'seconds' ].join(' ') : '') ]
                  ])
                 );
      // build items
      return !_.isEmpty(t) ? _.compact([ '(Time elapsed : ', [ t ].join(''), ')' ]).join('') : '';
    };
    /**
     * Default utility method to display a banner message
     *
     * @param {String} message message to display
     */
    this.banner = function (message) {
      // process banner
      logger.banner([ '[', this.cfg.name, '] -', message].join(' '));
    };
    /**
     * Default utility method to log message on generator
     *
     * @param {String} color default color to use for logging
     * @param {String} message message to display
     */
    this.logger =  function (color, message) {
      // normalize color
      color = _.isString(color) && !_.isEmpty(color) ? color : 'green';
      // default process
      this.log([ chalk[color]('>>'), message ].join(' '));
    };
    /**
     * Default utility method for error message
     *
     * @param {String} message message to display
     */
    this.error  = function (message) {
      // default proess
      this.logger('red', message);
    };
    /**
     * Default utility method for warning message
     *
     * @param {String} message message to display
     */
    this.warning  = function (message) {
      // default proess
      this.logger('yellow', message);
    };
    /**
     * Default utility method for warning message
     *
     * @param {String} message message to display
     */
    this.info  = function (message) {
      // default proess
      this.logger('green', message);
    };
  },
  /**
   * Initializing part
   */
  initializing  : {
    /**
     * Catch exit method
     */
    catchExit     : function () {
      // We need to catch exit error
      process.on('exit', function (code) {
        // error code ?
        if (code >= this.cfg.codes.dFailed && code <= code >= this.cfg.codes.gFailed) {
          // ascii message
          this.asciiMessage(this.cfg.ascii.error);
        } else {
          // ascii Message
          this.asciiMessage(this.cfg.ascii.bye);
        }
      }.bind(this));
    },
    /**
     * Default banner
     */
    welcome       : function () {
      // process welcome message
      this.log(yosay('Hello, and welcome to YoctopusJs generator.'));
    },
    /**
     * Default debug choices
     */
    debugMode     : function () {
      // create an async process
      var done = this.async();

      // prompt message
      this.prompt([ {
        name    : 'debug',
        type    : 'confirm',
        message : [ 'Do you want run this process in debug mode ?',
                   '(we will create and use debug directory for',
                    'data generation process)' ].join(' '),
        default : false
      }], function (props) {
        // enable debug mode
        this.cfg.debug = props.debug;
        // is debug ?
        if (this.cfg.debug) {
          // log ascii debug
          this.asciiMessage(this.cfg.ascii.debug);
        }
        // end process
        done();
      }.bind(this));
    },

    /**
     * Default Ready function
     */
    ready         : function () {
      // create an async process
      var done = this.async();

      // prompt message
      this.prompt([ {
        name    : 'ready',
        message : 'This process will take ~5 <-> 10 minutes. Are your ready ?',
        type    : 'confirm'
      }], function (props) {
        // ready ?
        if (props.ready) {
          // set start time
          this.time = time.time();
          // ascii message
          this.asciiMessage('welcome');
          // next process
          done();
        } else {
          // exit with correct code
          process.exit(this.cfg.codes.eManual);
        }

      }.bind(this));
    },

    /**
     * Retreive here dependencies / folders and angular versions
     */
    init          : function () {
      // default banner
      this.banner('We are initializing some data. Take a cofee and wait a few moment.');
      // create async process
      var done    = this.async();

      // require dependencies
      this.dependencies = require(this.cfg.paths.dependencies);
      // message
      this.info('Retreive dependencies config succeed.');
      // require folders
      this.folders      = require(this.cfg.paths.folders);
      // message
      this.info('Retreive folders structures succeed.');
      // require grunt config
      this.gruntConfig  = require(this.cfg.paths.grunt);
      // message
      this.info('Retreive grunt config succeed.');
      // message
      this.warning([ 'Try to connect on', this.cfg.angular.url,
                     'to retreive angular available versions' ].join(' '));
      // start spinner
      this.spinner.start();
      // process request
      request(this.cfg.angular.url, function (error, response, body) {
        // has error
        if (!error && response.statusCode === 200) {
          // default resolution
          var m;
          // default rules
          var re = /href="([0-9]\.[0-9]\.[0-9])\/\"/gm;

          // parse all
          while ((m = re.exec(body)) !== null) {
            // add version to list
            this.cfg.angular.versions.push(m[1]);
          }
          // reverse array
          this.cfg.angular.versions      = _(this.cfg.angular.versions).reverse().value();
          // and set default resolution
          this.cfg.angular.resolution    = _.first(this.cfg.angular.versions);
          // stop spinner
          this.spinner.stop(true);
          // message
          this.info('Retreive angular version succeed.');
          // end process
          done();
        } else {
          // stop spinner
          this.spinner.stop(true);
          // error message
          this.error([ 'Cannot retreive angular version :', error].join(' '));
          // done
          done();
        }
      }.bind(this));
    },
    /**
     * Choose which type of app we need
     */
    chooseAppType : function () {
      // default banner
      this.banner('Now it\'s time to tell us which type of application you want');
      // create an async process
      var done = this.async();

      // define app choises
      var appChoices = [ 'An application only based on NodeJs',
                         'An application only based on AngularJs',
                         'An application based on NodeJs & AngularJs' ];
      // prompt message
      this.prompt([ {
        name    : 'typeApp',
        message : 'What type of application you want to generate ?',
        type    : 'list',
        choices : appChoices
      }], function (props) {
        // generate node app state
        this.cfg.generate.node    = (props.typeApp === _.first(appChoices) ||
                                     props.typeApp === _.last(appChoices));
        // generate node app state
        this.cfg.generate.angular = (props.typeApp === _.last(appChoices) ||
                                     props.typeApp === appChoices[1]);
        // end process
        done();
      }.bind(this));
    }
  },
  /**
   * Do more complex user interact
   */
  prompting     : {
    /**
     * Open source process ?
     */
    openSourceProject                 : function () {
      // create an async process
      var done = this.async();
      // banner process
      this.banner('Now tell us if this project will be open source in the future');

      // process prompting
      this.prompt([ {
        name        : 'opensource',
        type        : 'confirm',
        message     : 'Your project will be open source ?',
        default     : true
      } ], function (props) {
        // add answer
        _.extend(this.cfg, props);
        // end process
        done();
      }.bind(this));
    },

    /**
     * Process node package configuration choices
     */
    nodeBasePackage                   : function () {
      // process node package ?
      if (this.cfg.generate.node) {
        // banner message
        this.banner('Now tell us some informations about your NodeJs configuration.');
        // create an async process
        var done = this.async();

        /**
         * Default base object to prefill if we are in debug mode
         */
        var baseObj = [ {
          name        : 'name',
          message     : 'What is your application name ?',
          validate    : function (input) {
            // default statement
            return !_.isEmpty(input) ? true : 'Please enter a valid application name.';
          }
        },
        {
          name        : 'description',
          message     : 'What is your application description ?',
          validate    : function (input) {
            // default statement
            return !_.isEmpty(input) && input.length > 10 ? true :
                   'Please enter a valid description with at least 10 chars';
          }
        } ];

        // is debug ?
        if (this.cfg.debug) {
          _.each(baseObj, function (value) {
            // add a random value for debug mode
            _.extend(value, { default : uuid.v4() });
          }, this);
        }

        // define prompts here
        var prompts = _.flatten([ baseObj,
        {
          name        : 'version',
          message     : 'What is your application version (x.x.x) ?',
          default     : '0.1.0',
          validate    : function (input) {
            // default rules
            var reg = /^(\d+)\.(\d+)\.(\d+)$/;
            // default statement
            return !_.isNull(reg.exec(input));
          }
        },
        {
          name        : 'private',
          type        : 'confirm',
          default     : this.cfg.opensource === true,
          message     : 'Your application is private ?',
        },
        {
          name        : 'license',
          type        : 'list',
          default     : (this.cfg.opensource ? 'Apache-2.0' : 'Unlicense'),
          message     : 'Which licence for your application ?',
          choices     : spdx.licenses
        },
        {
          name        : 'authorName',
          message     : 'What is the author for this app ?',
          default     : this.user.git.name()
        },
        {
          name        : 'authorEmail',
          message     : 'What is the author email for this app ?',
          default     : this.user.git.email(),
          validate    : function (input) {
            // default statement
            return isEmail.validate(input) ? true : 'Please enter a valid email';
          }
        },
        {
          name        : 'authorUrl',
          message     : 'What is the author url for this app ? (Optional)',
          validate    : function (input) {
            // default statement
            return _.isEmpty(input) ? true : (isUrl(input) ? true : 'Please enter a valid url');
          }
        },
        {
          name        : 'engines',
          type        : 'list',
          message     : 'Which version of node your application must depend ?',
          choices     : _(this.cfg.nVersions).reverse().value(),
          default     : n.current()
        } ]);

        // process prompting
        this.prompt(prompts, function (props) {
          // format engines process
          props.engines = { node : [ '>=', props.engines ].join('') };
          // process name
          props.name = _.deburr(_.snakeCase(props.name)).replace('_', '-');

          // normalize author
          _.extend(props, {
            author : {
              name  : props.authorName,
              email : props.authorEmail,
              url   : props.authorUrl
            }
          });

          // delete non needed data
          delete props.authorName;
          delete props.authorEmail;
          delete props.authorUrl;

          // extend object
          this.node = _.extend({}, props);

          // end process
          done();
        }.bind(this));
      }
    },
    /**
     * Process bower package configuration choices
     */
    bowerBasePackage                  : function () {
      // process node package ?
      if (this.cfg.generate.angular) {
        // banner message
        this.banner('Now tell us some informations about your AngularJS configuration.');
        // create an async process
        var done    = this.async();
        // define prompts here
        var prompts = [];

        /**
         * Default base object to prefill if we are in debug mode
         */
        var baseObj = [ {
          name        : 'name',
          message     : 'What is your application name ?',
          validate    : function (input) {
            // default statement
            return !_.isEmpty(input) ? true : 'Please enter a valid application name.';
          }
        },
        {
          name        : 'description',
          message     : 'What is your application description ?',
          validate    : function (input) {
            // default statement
            return !_.isEmpty(input) && input.length > 10 ? true :
                   'Please enter a valid description with at least 10 chars';
          }
        } ];

        // is debug ?
        if (this.cfg.debug) {
          _.each(baseObj, function (value) {
            // add a random value for debug mode
            _.extend(value, { default : uuid.v4() });
          }, this);
        }

        // default obj to use if nodeja app is not defined
        var defaultObj = _.flatten([ baseObj,
        {
          name        : 'version',
          message     : 'What is your application version (x.x.x) ?',
          default     : '0.1.0',
          validate    : function (input) {
            // default rules
            var reg = /^(\d+)\.(\d+)\.(\d+)$/;
            // default statement
            return !_.isNull(reg.exec(input));
          }
        },
        {
          name        : 'private',
          type        : 'confirm',
          message     : 'Your application is private ?'
        },
        {
          name        : 'license',
          type        : 'list',
          default     : 'Apache-2.0',
          message     : 'Which licence for your application ?',
          choices     : spdx.licenses
        },
        {
          name        : 'authorName',
          message     : 'What is the author for this app ?',
          default     : this.user.git.name()
        },
        {
          name        : 'authorEmail',
          message     : 'What is the author email for this app ?',
          default     : this.user.git.email(),
          validate    : function (input) {
            // default statement
            return isEmail.validate(input) ? true : 'Please enter a valid email';
          }
        },
        {
          name        : 'authorUrl',
          message     : 'What is the author url for this app (Optional) ?',
          validate    : function (input) {
            // default statement
            return _.isEmpty(input) ? true : (isUrl(input) ? true : 'Please enter a valid url');
          }
        } ]);

        // si node app is not defined add default prompt data
        if (!this.cfg.generate.node) {
          // parse all item
          _.each(defaultObj, function (obj) {
            // add item
            prompts.push(obj);
          });
        }

        // add default choices of angular app
        prompts.push({
          name    : 'angularVersions',
          type    : 'list',
          message : 'Which version of angular your app must depend ?',
          choices : this.cfg.angular.versions,
          default : this.cfg.angular.resolution
        });

        // process prompting
        this.prompt(prompts, function (props) {

          // process name
          props.name = _.deburr(_.snakeCase(props.name)).replace('_', '-');

          // normalize author
          _.extend(props, {
            author      : {
              name  : props.authorName,
              email : props.authorEmail,
              url   : props.authorUrl
            },
            resolutions : {
              angular : props.angularVersions
            }
          });

          // delete non needed data
          delete props.authorName;
          delete props.authorEmail;
          delete props.authorUrl;
          delete props.angularVersions;

          // extend object
          this.angular = _.extend({}, props);
          // node exits ?
          if (this.node) {
            var n = _.clone(this.node);
            // remove non needed data
            delete n.engines;
            // merge data
            this.angular = _.merge(this.angular, n);
          }
          // end process
          done();
        }.bind(this));
      }
    },
    /**
     * Process choice for structure generation
     */
    generateFolders                   : function () {
      // create async process
      var done    = this.async();
      // banner message
      this.banner('So maybe you want to generate a file structure for your app');

      // prompt process
      this.prompt([ {
        name    : 'structure',
        type    : 'confirm',
        default : true,
        message : [ 'Do confirm that you want generate',
                    'project structure based on your app type choice ?' ].join(' ')
      }], function (props) {
        // extend config
        _.extend(this.cfg, {
          structure : {
            enable : props.structure
          }
        });
        // end process
        done();
      }.bind(this));
    },
    /**
     * A message to inform next process
     */
    infoRemoveFolder                  : function () {
      // ascii message
      this.asciiMessage(this.cfg.ascii.file);
    },
    /**
     * Process choice for structure generation
     */
    forceRemoveExistingFolders        : function () {
      // create async process
      var done    = this.async();
      // banner message
      this.banner([ 'We need to know if you allow us',
                    'to remove existing project stucture is exists' ].join(' '));

      // list of prompts
      this.prompt([ {
        name    : 'erase',
        type    : 'confirm',
        default : true,
        message : [ 'Do confirm that you allow us to remove existing',
                    'directory structure if exists ?',
                    chalk.red('(Yes to continue)') ].join(' '),
      } ], function (props) {
        // is ok
        if (props.erase) {
          // extend config with confirm ?
          _.extend(this.cfg, { erase : props.erase });
          // end process
          done();
        } else {
          // exit
          process.exit(this.cfg.codes.eManual);
        }
      }.bind(this));
    },
    /**
     * Confirm erase existing folders ?
     */
    confirmForceRemoveExistingFolders : function () {
      // create async process
      var done    = this.async();

      // erase ?
      if (this.cfg.erase) {
        // list of prompts
        this.prompt([ {
          name    : 'eraseConfirm',
          type    : 'confirm',
          default : false,
          message : [ chalk.yellow('Do you confirm your previous action ?'),
                      chalk.red('(Yes to continue)') ].join(' ')
        }], function (props) {
          // erase ?
          if (props.eraseConfirm) {
            // extend config with confirm ?
            this.cfg.erase = props.eraseConfirm;
            // end process
            done();
          } else {
            // exit
            process.exit(this.cfg.codes.eManual);
          }
        }.bind(this));
      } else {
        // end process
        done();
      }
    }
  },
  /**
   * configuring process
   */
  configuring   : {
    /**
     * Generate folders if is set to true
     */
    generateFolders             : function () {
      // default file struture
      var structure = [];
      // build structure ?
      if (this.cfg.structure.enable) {
        // is node ?
        if (this.cfg.generate.node) {
          structure.push(this.folders.node);
        }
        // angular structure ?
        if (this.cfg.generate.angular) {
          structure.push(this.folders.angular);
        }
        // extra structure ??
        structure.push(this.folders.extra);
        // fatten data to have one level depth of data
        structure = _.flatten(structure);
        // map each structure
        structure = _.map(structure, function (s) {
          // default statement
          return this.prefixPath(s);
        }, this);

        // extend structure
        _.extend(this.cfg.structure, { directory : structure });
      }
    },
    /**
     * Generate config for you app
     */
    generateExtraDependencies   : function () {
      // create async process
      var done = this.async();

      // to execute
      async.eachSeries([ 'dependencies', 'devDependencies' ], function (type, next) {
        // prompt list
        var prompt = [];

        // generate node ?
        if (this.cfg.generate.node && !_.isEmpty(this.dependencies.extra.node[type])) {
          // add in prompt dependencies
          prompt.push({
            name    : 'nDependencies',
            type    : 'checkbox',
            message : [ 'Choosed extra', type,
                        'for your NodeJs application (Optional) :' ].join(' '),
            choices : this.dependencies.extra.node[type]
          });
        }
        // generate angular ?
        if (this.cfg.generate.angular && !_.isEmpty(this.dependencies.extra.angular[type])) {
          // add in prompt dependencies
          prompt.push({
            name    : 'aDependencies',
            type    : 'checkbox',
            message : [ 'Choosed extra', type,
                        'for your AngularJs application (Optional) :' ].join(' '),
            choices : this.dependencies.extra.angular[type]
          });
        }
        // Is empty ?
        if (!_.isEmpty(prompt)) {
          // banner message
          this.banner([ 'Maybe you want install extra', type ].join(' '));
          // prompt elements
          this.prompt(prompt, function (props) {
            // node ?
            if (this.cfg.generate.node) {
              // change dependencies for ndoe
              this.dependencies.node[type] = _.compact(
                                                _.flatten([ this.dependencies.node[type],
                                                            props.nDependencies ]));
            }

            // angular ?
            if (this.cfg.generate.angular) {
              // change dependencies for angular
              this.dependencies.angular[type] = _.compact(
                                                  _.flatten([ this.dependencies.angular[type],
                                                              props.aDependencies ]));
            }
            // next process
            next();
          }.bind(this));
        } else {
          // next process
          next();
        }
      }.bind(this), function () {
        // end process
        done();
      });
    }
  },
  /**
   * Writing process
   */
  writing       : {
    /**
     * Default coffee message
     */
    coffeeMsg           : function () {
      // create async process
      var done = this.async();
      // process welcome message
      this.asciiMessage(this.cfg.ascii.coffee);
      // start spinner
      this.spinner.start();
      // start a timeout here
      var timeout = setTimeout(function () {
        // clear timeout
        clearTimeout(timeout);
        // stop spinner
        this.spinner.stop(true);
        // next process
        done();
      }.bind(this), this.cfg.delay);
    },
    /**
     * Deleting existing file
     */
    deleteExistingFile  : function () {
      // create async process
      var done = this.async();
      // erase mode
      if (this.cfg.erase) {
        // banner message
        this.banner('We will check and erase your existing project structure');
        // get dirname dirname
        var dirname = this.prefixPath('/');
        // start spinner
        this.spinner.start();
        // directory is empty ?
        fs.emptyDir(dirname, function (err) {
          // has error ?
          if (!err) {
            // start a timeout here
            var timeout = setTimeout(function () {
              // stop spinner
              this.spinner.stop(true);
              // clear timeout
              clearTimeout(timeout);
              // info message
              this.info('Your project directory was cleaned. Processing next step.');
              // end async
              done();
            }.bind(this), this.cfg.delay);
          } else {
            // message
            this.error([ 'Cannot clean your directory :', err ].join(' '));
            // stop we cannot continue
            process.exit(this.cfg.codes.dFailed);
          }
        }.bind(this));
      } else {
        // end async
        done();
      }
    },
    /**
     * Build template files for you app
     */
    generateTemplates   : function () {
      // create async process
      var done = this.async();

      // to execute
      async.eachSeries([ 'node', 'angular' ], function (type, next) {

        // default config auto process
        var config = {
          node    : {
            template  : {
              source      : '_package.json',
              destination : 'package.json'
            },
            name      : 'NodeJs'
          },
          angular : {
            template  : {
              source      : '_bower.json',
              destination : 'bower.json'
            },
            name      : 'AngularJs'
          },
        };

        // find type
        var current = config[type];

        // is undefined ?
        if (!_.isUndefined(current)) {
          // generate node ?
          if (this.cfg.generate[type]) {
            // banner message
            this.banner([ 'We will build your',current.template.destination,
                          'for your', current.name, 'configuration' ].join(' '));

            // start spinner
            this.spinner.start();
            // file exists ?
            fs.stat(this.prefixPath(current.template.destination), function (err) {
              if (err) {
                // write file
                fs.writeJson(this.prefixPath(current.template.destination), this[type],
                function (err) {
                  // need to force generation on package.json on angular only type
                  if (type === 'angular') {
                    // write
                    fs.writeJson(this.prefixPath('package.json'), this[type]);
                  }
                  // has no error ?
                  if (!err) {
                    // start a timeout here
                    var timeout = setTimeout(function () {
                      // stop spinner
                      this.spinner.stop(true);
                      // clear timeout
                      clearTimeout(timeout);
                      // success message
                      this.info([ 'File', current.template.destination,
                                  'was correctly created & builded.' ].join(' '));
                      // next process
                      next();
                    }.bind(this), this.cfg.delay);
                  } else {
                    // success message
                    this.error([ 'Cannot create', current.template.destination,
                                'file :', err ].join(' '));
                    // exit cannot continue
                    process.exit(this.cfg.codes.fFailed);
                  }
                }.bind(this));
              } else {
                // start a timeout here
                var timeout = setTimeout(function () {
                  // stop spinner
                  this.spinner.stop(true);
                  // clear timeout
                  clearTimeout(timeout);
                  // error message
                  this.error([ [ 'Cannot create & build file',
                                 current.template.destination ].join(' '),
                                 '. this file must be remove first'
                               ].join('')
                            );
                  // next process
                  next();
                }.bind(this), this.cfg.delay);
              }
            }.bind(this));
          } else {
            // next process
            next();
          }
        } else {
          // set error
          this.error([ 'Cannot find config for', type ].join(' '));
          next();
        }
      }.bind(this), function () {
        // end process
        done();
      });
    },
    /**
     * Build directory files for you app
     */
    generateDirectory   : function () {
      // create async process
      var done = this.async();

      // to execute
      async.eachSeries([ 'node', 'angular' ], function (type, next) {

        // default config auto process
        var config = { node     : { name : 'NodeJs' },
                       angular  : { name : 'AngularJs' }
                     };

        // find type
        var current = config[type];

        // is undefined ?
        if (!_.isUndefined(current)) {
          // generate with tyoe ?
          if (this.cfg.generate[type]) {
            // banner message
            this.banner([ 'We will build your folders',
                          'for your', current.name, 'configuration' ].join(' '));

            // empty dir ?
            if (!_.isEmpty(this.folders[type])) {
              // start spinner
              this.spinner.start();
              // parse all folders
              async.eachSeries(this.folders[type], function (folder, nextFolder) {
                // create item
                fs.mkdirs(this.prefixPath(folder), function (err) {
                  // has error ?
                  if (!err) {
                    // check if is last item
                    if (_.last(this.folders[type]) === folder) {
                      // start a timeout here
                      var timeout = setTimeout(function () {
                        // stop spinner
                        this.spinner.stop(true);
                        // clear timeout
                        clearTimeout(timeout);
                        // info
                        this.info([ 'Folders for your', current.name,
                                    'configuration was created.' ].join(' '));
                        // next process
                        next();
                      }.bind(this), this.cfg.delay);
                    } else {
                      // process next folder
                      nextFolder();
                    }
                  } else {
                    // stop spinner
                    this.spinner.stop(true);
                    // error message
                    this.error([ 'An error occured when we try to create the folder [', folder,
                                 '] :', err ].join(' '));
                    // next process
                    next();
                  }
                }.bind(this));
              }.bind(this));
            }
          } else {
            // next process
            next();
          }
        } else {
          // set error
          this.error([ 'Cannot find config for', type ].join(' '));
          // next process
          next();
        }
      }.bind(this), function () {
        // end process
        done();
      });
    },
    /**
     * Generate Gruntfile
     */
    generateGruntFile   : function () {
      // create async process
      var done = this.async();
      // default config array
      var list = {};
      // default list
      var registerList = {};

      // banner message
      this.banner('We will generate your Gruntfile.js configuration');

      // start spinner
      this.spinner.start();
      // reach each config
      async.eachSeries([ 'default', 'node', 'angular' ], function (type, next) {

        if (this.cfg.generate[type] || type === 'default') {
          // default
          var tconfig = this.gruntConfig.config[type];
          // async each config
          async.eachSeries(tconfig, function (config, tnext) {
            // parse each item
            async.eachSeries(config.value, function (c, cnext) {
              // is empty ?
              if (!_.isEmpty(c)) {
                // temp key
                var k = [ config.name, '_key' ].join('');
                // add config
                if (_.isUndefined(list[k])) {
                  _.set(list, k, []);
                }

                // default push
                list[k].push(c);
                cnext();
              }
            }.bind(this), function () {
              // next
              tnext();
            }.bind(this));
          }.bind(this), function () {
            // is empty ?
            if (!_.isEmpty(this.gruntConfig.load[type])) {
              // reach load
              this.gruntEditor.loadNpmTasks(this.gruntConfig.load[type]);
            }

            // each register
            async.eachSeries(this.gruntConfig.register[type], function (register, rnext) {
              // register task
              if (_.isUndefined(registerList[register.name])) {
                // default list
                registerList[register.name] = {
                  name        : [],
                  description : [],
                  value       : []
                };
              }
              // list register
              registerList[register.name].name.push(register.name);
              registerList[register.name].description.push(register.description);
              registerList[register.name].value.push(register.value);
              // next item
              rnext();
            }.bind(this) , function () {
              // next process
              next();
            });
          }.bind(this));
        } else {
          next();
        }
      }.bind(this), function () {

        // build each register
        _.each(registerList, function (list) {
          // sort value
          var value = _.sortBy(_.flatten(_.uniq(list.value)), function (i) {
            // default statement
            return [ i !== 'buildjs', i !== 'build', i ].join('|');
          });

          // register task
          this.gruntEditor.registerTask(_.uniq(list.name).join(' - '),
                                        _.uniq(list.description).join(' - '), value);
        }, this);

        // parse list
        _.each(list, function (item, key) {
          // noramlizekey
          var pkey = key.replace('_key', '');
          // default item
          item = item.join(', ');
          // pkg property ?
          if (pkey !== 'pkg') {
            // process
            item = [ '{', item , '}' ].join(' ');
          }

          // add config
          this.gruntEditor.insertConfig(pkey, item);
        }, this);
        // beautidy content
        var content = jsbeauty(this.gruntEditor.toString(), { 'indent_size' : 2 });
        // replace some brakets and ":"
        content = content.replace(/\[/g, '[ ').replace(/\]/g, ' ]');

        // load npm Task regex to replace after
        var lnpmTasksRegex  = /(grunt\.loadNpmTasks.*)/gm;
        // reg founded item for replacement after
        var regFounded      = [];

        // temp loop item
        var m;

        // get all item
        while ((m = lnpmTasksRegex.exec(content)) !== null) {
          // is last item ?
          if (m.index === lnpmTasksRegex.lastIndex) {
            // go to the next item
            lnpmTasksRegex.lastIndex++;
          }
          // add item to temp storage
          regFounded.push(_.first(m));
        }
        // push replace item
        regFounded.push('grunt.registerTask');
        // replace registerTask by new content new content
        content = content.replace(/(grunt\.loadNpmTasks.*)/gm, '');
        content = content.replace(/(grunt.registerTask)/, regFounded.join('\n  '));

        // write file
        fs.writeFile(this.prefixPath('Gruntfile.js'), content, function (err) {
          // start a timeout here
          var timeout = setTimeout(function () {
            // clear timeout
            clearTimeout(timeout);
            // stop spinner
            this.spinner.stop(true);
            // err ?
            if (!err) {
              // message
              this.info('Your Gruntfile.js was correctly created.');
              // end
              done();
            } else {
              // error message
              this.error([ 'Cannnot generate Gruntfile.js :', err ].join(' '));
              // exit cannot continue
              process.exit(this.cfg.codes.gFailed);
            }
          }.bind(this), this.cfg.delay);
        }.bind(this));
      }.bind(this));
    },
    /**
     * Generate files
     */
    generateFiles       : function () {
      // create async process
      var done = this.async();

      // types list
      var types = [ 'node', 'angular', 'default' ];

      if (this.cfg.opensource) {
        // push open source dans la queue
        types.push('open-source');
      }

      // start an async process
      async.eachSeries(types, function (type, next) {
        // generate this app ?
        if (this.cfg.generate[type] || type === 'default' || type === 'open-source') {
          // banner message
          this.banner([ 'We will generate base files for your', type, 'configuration' ].join(' '));
          // start spinner
          this.spinner.start();
          // current path
          var p = this.normalizePath([ this.sourceRoot(), 'applications', type ].join('/'));

          // walk on directory
          fs.walk(p).on('data', function (item) {
            // is file ????
            if (item.stats.isFile()) {
              // remove path on file to process next normalize action
              var nFile = item.path.replace(p, '');

              // parse file
              var parse = path.parse(item.path);

              // is a valid ext ?
              if (!_.isEmpty(path.extname(nFile)) || (type === 'default' &&
                (parse.base === '_.gitignore' || parse.base === '_.gitattributes'))) {

                // process file normalization
                nFile = this.prefixPath([
                  (type === 'angular' ? 'public/assets/js/src' : ''),
                  nFile
                ].join('/'));

                // replace _ before name file
                nFile = nFile.replace(/\/_/, '/');

                // map name
                var content = fs.readFileSync(item.path).toString();

                // has name ? if true replace
                if (_.has(this[type], 'name')) {
                  content = content.replace(/<%= name %>/g, this[type].name);
                }

                // is travis file ?
                if (_.includes(nFile, '.travis.yml')) {
                  // replace node version
                  content = content.replace(/<%= NODE_VERSION %>/g,
                    this.node.engines.node.replace('>=', ''));
                }

                // write file
                fs.outputFile(nFile, content);
              }
            }
          }.bind(this))
          .on('end', function () {
            // start a timeout here
            var timeout = setTimeout(function () {
              // clear timeout
              clearTimeout(timeout);
              // stop spinner
              this.spinner.stop(true);
              // message
              this.info([ 'Files was properly generated for your', type,
                          'application.' ].join(' '));
              // next process
              next();
            }.bind(this), this.cfg.delay);
          }.bind(this));
        } else {
          // next process
          next();
        }
      }.bind(this), function () {
        done();
      });
    }
  },
  /**
   * Install process
   */
  install       : {
    /**
     * Install node dependencies
     */
    packages : function () {
      // banner message
      this.banner('We will install needed packages');
      // process install for each type
      _.each([ 'node', 'angular'], function (type) {
        // node ?
        if (this.cfg.generate[type]) {
          // no dev
          var nDev    = this.dependencies[type].dependencies;
          // dev
          var dev     = this.dependencies[type].devDependencies;

          // defined method
          var method  = type === 'node' ? 'npmInstall' : 'bowerInstall';

          // normal item ?
          if (!_.isEmpty(nDev)) {
            // install
            this[method](nDev, { save : true }, function () {
              this.info([ 'Install', type, 'dependencies succeed.' ].join(' '));
            }.bind(this));
          }

          // have save dev ?
          if (!_.isEmpty(dev)) {
            // install
            this[method](dev, { saveDev : true }, function () {
              this.info([ 'Install', type, 'dev dependencies succeed.' ].join(' '));
            }.bind(this));
          }

          // has extra dev dep ?
          if (_.has(this.dependencies[type], 'node')) {
            // is empty ?
            if (!_.isEmpty(this.dependencies[type].node.devDependencies)) {
              // install node dev for angular app
              this.npmInstall(this.dependencies[type].node.devDependencies,
                              { saveDev : true }, function () {
                this.info([ 'Install', type, 'extra node dev dependencies succeed.' ].join(' '));
              }.bind(this));
            }
            // is empty ?
            if (!_.isEmpty(this.dependencies[type].node.dependencies)) {
              // install node dev for angular app
              this.npmInstall(this.dependencies[type].node.dependencies,
                              { save : true }, function () {
                this.info([ 'Install', type, 'extra node dependencies succeed.' ].join(' '));
              }.bind(this));
            }
          }
        }
      }.bind(this));
    }
  },
  /**
   * End process
   */
  end           : {
    /**
     * Process grunt generation rules for project init
     */
    processGrunt : function () {
      // banner message
      this.banner('We will process grunt task before ending.');
      this.spawnCommand('grunt');
    }
  }
});