truongld/yodajs

View on GitHub
lib/cli/create.js

Summary

Maintainability
D
1 day
Test Coverage
/*jshint -W003 */

/**
 * Module dependencies.
 */
var prompt = require('co-prompt')
  , os = require('os')
  , fs = require('fs')
  , mkdirp = require('mkdirp');


/**
 * Create Yodajs application at `path`.
 *
 * @param {String} path
 * @api private
 */
exports = module.exports = function create(path) {
  console.log('creating Yodajs application at : ' + path);
  
  (function createApplication(path) {
    emptyDirectory(path, function(empty) {
      if (empty) {
        createApplicationAt(path);
      } else {
        prompt.confirm('destination is not empty, continue? ')(function(err, ok) {
          if (err) { throw err; }
          if (ok) {
            process.stdin.destroy();
            createApplicationAt(path);
          } else {
            abort('aborting');
          }
        });
      }
    });
  })(path);
};


var eol = 'win32' == os.platform() ? '\r\n' : '\n';

var userModel = [
    'var mongoose = require(\'mongoose\');'
  , 'var Schema = mongoose.Schema;'
  , 'var bCrypt = require(\'../../node_modules/bcrypt-nodejs/bCrypt\');'
  , 'var querystring = require(\'querystring\');'
  , 'var http = require(\'http\');'
  , ''
  , 'var UserSchema = new Schema({'
  , '    firstname: String,'
  , '    lastname: String,'
  , '    username: {type: String, index: {unique: true, dropDups: true}},'
  , '    salt: { type: String},'
  , '    hash: { type: String},'
  , '    email: {type: String, index: {unique: true, dropDups: true}},'
  , '    birthday: Date,'
  , '    roles: [String],'
  , '    token: String'
  , '});'
  , ''
  , 'UserSchema.virtual(\'password\')'
  , '  .get(function() {'
  , '      return this._password;'
  , '  }).set(function(password) {'
  , '      this._password = password;'
  , '      var salt = this.salt = bCrypt.genSaltSync(10);'
  , '      this.hash = bCrypt.hashSync(password, salt);'
  , '  });'
  , ''
  , 'UserSchema.method(\'checkPassword\', function(password, callback) {'
  , '    bCrypt.compare(password, this.hash, callback);'
  , '});'
  , ''
  , 'UserSchema.static(\'authenticate\', function(username, password, callback) {'
  , '    var that = this;'
  , '    var processCheckPass = function(myuser, mypassword){'
  , '        myuser.checkPassword(mypassword, function(err, passwordCorrect) {'
  , '            if (err)'
  , '                return callback(err);'
  , ''
  , '            if (!passwordCorrect)'
  , '                return callback(null, false);'
  , ''
  , '            return callback(null, myuser);'
  , '        });'
  , '    };'
  , '    this.findOne({username: username}, function(err, user) {'
  , '        if (err)'
  , '            return callback(err);'
  , ''
  , '        if (!user){'
  , '            that.findOne({email: username}, function(err, user) {'
  , '                if (err)'
  , '                    return callback(err);'
  , ''
  , '                if (!user){'
  , '                    return callback(null, false);'
  , '                }'
  , '                processCheckPass(user,password);'
  , '            });'
  , '        }else{'
  , '            processCheckPass(user,password);'
  , '        }'
  , '    });'
  , '});'
  , ''
  , 'module.exports = mongoose.model(\'User\', UserSchema);'
  , ''
].join(eol);

var pagesController = [
    'var yodajs = require(\'yodajs\')'
  , '  , Controller = yodajs.Controller;'
  , ''
  , 'var pagesController = new Controller();'
  , ''
  , 'pagesController.main = function() {'
  , '  this.title = \'Yoda JS\';'
  , '  this.render();'
  , '}'
  , ''
  , 'module.exports = pagesController;'
  , ''
].join(eol);

var mainTemplate = [
    '<!DOCTYPE html>'
  , '<html>'
  , '  <head>'
  , '    <title><%= title %></title>'
  , '    <link rel="stylesheet" href="/stylesheets/screen.css" />'
  , '  </head>'
  , '  <body>'
  , '    <h1><%= title %></h1>'
  , '    <p>Welcome aboard! Visit the <a href="https://github.com/truongld/yodajs">GitHub project page</a> for details.</p>'
  , '  </body>'
  , '</html>'
].join(eol);

var cssStylesheet = [
    'body {'
  , '  padding: 50px;'
  , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
  , '}'
  , ''
  , 'a {'
  , '  color: #00B7FF;'
  , '}'
].join(eol);

var oauthConfig = [
  'module.exports = {'
  , ' facebook: {'
  , '   clientID: \'xxxxxxxxxxx\','
  , '   clientSecret: \'xxxxxxxxxxxxxxxxxxxxxx\','
  , '   callbackURL: \'/auth/facebook/callback\''
  , ' },'
  , ' twitter: {'
  , '   consumerKey: \'xxxxxxxxxxx\','
  , '   consumerSecret: \'xxxxxxxxxxxxxxxxxxxxxx\','
  , '   callbackURL: "/auth/twitter/callback"'
  , ' },'
  , ' github: {'
  , '   clientID: \'xxxxxxxxxxx\','
  , '   clientSecret: \'xxxxxxxxxxxxxxxxxxxxxx\','
  , '   callbackURL: "/auth/github/callback"'
  , ' },'
  , ' google: {'
  , '   clientID: \'xxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\','
  , '   clientSecret: \'xxxxxxxxxxx\','
  , '   returnURL: \'/auth/google/callback\''
  , ' }'
  , '}'
].join(eol);

var passportInitializer = [
  'var passport = require(\'passport\');'
  , 'var config = require(\'../oauth\');'
  , 'var BasicStrategy = require(\'passport-http\').BasicStrategy;'
  , 'var LocalStrategy = require(\'passport-local\').Strategy;'
  , 'var FacebookStrategy = require(\'passport-facebook\').Strategy;'
  , 'var TwitterStrategy = require(\'passport-twitter\').Strategy;'
  , 'var GithubStrategy = require(\'passport-github\').Strategy;'
  , 'var GoogleStrategy = require(\'passport-google-oauth\').OAuth2Strategy;'
  , 'var User = require(\'../../app/models/User\');'
  , ''
  , 'passport.use(new BasicStrategy('
  , '  function(userid, password, done) {'
  , '    if (userid == \'YODA\' && password == \'12345678\'){'
  , '      return done(null, {id: \'54d231e57ddb0019b723c023\', username: userid});'
  , '    }else{'
  , '      return done(\'authenticate has failed !\');'
  , '    }'
  , '    // User.findOne({ username: userid }, function (err, user) {'
  , '    //   if (err) { return done(err); }'
  , '    //   if (!user) { return done(null, false); }'
  , '    //   if (!user.verifyPassword(password)) { return done(null, false); }'
  , '    //   return done(null, user);'
  , '    // });'
  , '  }'
  , '));'
  , ''
  , '// Use the LocalStrategy within Passport.'
  , 'passport.use(new LocalStrategy({'
  , '    usernameField: \'username\''
  , '  },'
  , '  function(username, password, done) {'
  , '    // Find the user by username.  If there is no user with the given'
  , '    // username, or the password is not correct, set the user to `false` to'
  , '    // indicate failure.  Otherwise, return the authenticated `user`.'
  , '    User.authenticate(username, password, function(err, user) {'
  , '      return done(err, user);'
  , '    });'
  , '  }'
  , '));'
  , ''
  , '// Use the FacebookStrategy Passport'
  , 'passport.use(new FacebookStrategy({'
  , '    clientID: config.facebook.clientID,'
  , '    clientSecret: config.facebook.clientSecret,'
  , '    callbackURL: config.facebook.callbackURL'
  , '  },'
  , '  function(accessToken, refreshToken, profile, done) {'
  , '    profile = profile._json;'
  , '    console.log(profile);'
  , '    if (profile.email == \'\'){'
  , '      profile.email = \'fb_\' + profile.id;'
  , '    }'
  , '    User.findOne({email:profile.email}, function(err, user){'
  , '      if (err || !user){'
  , '        var user = new User();'
  , '        user.username = \'fb_\' + profile.id;'
  , '        user.email = profile.email;'
  , '        user.cellphone = profile.email;'
  , '        user.firstname = profile.first_name;'
  , '        user.lastname = profile.last_name;'
  , '        user.birthday = new Date(profile.birthday);'
  , '        user.gender = profile.gender;'
  , '        user.save(function(err) {'
  , '            if (err) {'
  , '                console.log(\'error: \' + err);'
  , '                done(err);'
  , '            } else {'
  , '                console.log(\'success\');'
  , '                done(null,user);'
  , '            }'
  , '        });'
  , '      }else{'
  , '        done(null,user);'
  , '      }'
  , '    });'
  , '  }'
  , '));'
  , ''
  , 'passport.use(new TwitterStrategy({'
  , '    consumerKey: config.twitter.consumerKey,'
  , '    consumerSecret: config.twitter.consumerSecret,'
  , '    callbackURL: config.twitter.callbackURL'
  , '  },'
  , '  function(accessToken, refreshToken, profile, done) {'
  , '    console.log(profile);'
  , '    params = { username: \'tw_\' + profile.id };'
  , '    profile.email = \'tw_\' + profile.id;'
  , '    User.findOne(params, function(err, user){'
  , '      if (err || !user){'
  , '        var user = new User();'
  , '        user.username = \'tw_\' + profile.id;'
  , '        user.email = profile.email;'
  , '        user.cellphone = profile.email;'
  , '        user.firstname = profile.name;'
  , '        user.lastname = profile.screen_name;'
  , '        user.save(function(err) {'
  , '            if (err) {'
  , '                console.log(\'error: \' + err);'
  , '                done(err);'
  , '            } else {'
  , '                console.log(\'success\');'
  , '                done(null,user);'
  , '            }'
  , '        });'
  , '      }else{'
  , '        done(null,user);'
  , '      }'
  , '    });'
  , '  }'
  , '));'
  , 'passport.use(new GithubStrategy({'
  , '    clientID: config.github.clientID,'
  , '    clientSecret: config.github.clientSecret,'
  , '    callbackURL: config.github.callbackURL'
  , '  },'
  , '  function(accessToken, refreshToken, profile, done) {'
  , '    console.log(profile);'
  , '    if (profile.emails[0].value){'
  , '      params = { email: profile.emails[0].value };'
  , '    }else{'
  , '      params = { username: \'gh_\' + profile.username };'
  , '      profile.emails[0].value = \'gh_\' + profile.username;'
  , '    }'
  , '    User.findOne(params, function(err, user){'
  , '      if (err || !user){'
  , '        var user = new User();'
  , '        user.username = \'gb_\' + profile.username;'
  , '        user.email = profile.emails[0].value;'
  , '        user.cellphone = profile.emails[0].value;'
  , '        user.firstname = profile.username;'
  , '        user.lastname = profile.displayName;'
  , '        user.save(function(err) {'
  , '            if (err) {'
  , '                console.log(\'error: \' + err);'
  , '                done(err);'
  , '            } else {'
  , '                console.log(\'success\');'
  , '                done(null,user);'
  , '            }'
  , '        });'
  , '      }else{'
  , '        done(null,user);'
  , '      }'
  , '    });'
  , '  }'
  , '));'
  , 'passport.use(new GoogleStrategy({'
  , '    clientID: config.google.clientID,'
  , '    clientSecret: config.google.clientSecret,'
  , '    callbackURL: config.google.returnURL'
  , '  },'
  , '  function(accessToken, refreshToken, profile, done) {'
  , '    profile = profile._json;'
  , '    console.log(profile);'
  , '    User.findOne({email:profile.email}, function(err, user){'
  , '      if (err || !user){'
  , '        var user = new User();'
  , '        user.username = \'gb_\' + profile.id;'
  , '        user.email = profile.email;'
  , '        user.cellphone = profile.email;'
  , '        user.firstname = profile.given_name;'
  , '        user.lastname = profile.family_name;'
  , '        if (typeof(profile.birthday) != \'undefined\'){'
  , '          user.birthday = new Date(profile.birthday);'
  , '        }'
  , '        user.gender = profile.gender;'
  , '        user.save(function(err) {'
  , '            if (err) {'
  , '                console.log(\'error: \' + err);'
  , '                done(err);'
  , '            } else {'
  , '                console.log(\'success\');'
  , '                done(null,user);'
  , '            }'
  , '        });'
  , '      }else{'
  , '        done(null,user);'
  , '      }'
  , '    });'
  , '  }'
  , '));'
  , ''
  , '// Passport session setup.'
  , 'passport.serializeUser(function(user, done) {'
  , '  done(null, user.id);'
  , '});'
  , ''
  , 'passport.deserializeUser(function(id, done) {'
  , '  User.findById(id, function (err, user) {'
  , '    done(err, user);'
  , '  });'
  , '});'
].join(eol);

var mongooseInitializer = [
  'var mongoose = require("mongoose");'
  , 'module.exports = function() {'
  , '  switch (this.env) {'
  , '    case \'development\':'
  , '      mongoose.connect(\'mongodb://127.0.0.1/db\');'
  , '      break;'
  , '    case \'production\':'
  , '      mongoose.connect(\'mongodb://user:pass@host.com:port/db\');'
  , '      break;'
  , '  }'
  , '}'
].join(eol);

var routesTemplate = [
    '// Draw routes.  Yodajs\'s router provides expressive syntax for drawing'
  , '// routes, including support for resourceful routes, namespaces, and nesting.'
  , '// MVC routes can be mapped to controllers using convenient'
  , '// `controller#action` shorthand.  Standard middleware in the form of'
  , '// `function(req, res, next)` is also fully supported.  Consult the Yodajs'
  , '// Guide on [routing](http://maglevjs.org/guide/routing.html) for additional'
  , '// information.'
  , 'module.exports = function routes() {'
  , '  this.root(\'pages#main\');'
  , '}'
  , ''
].join(eol);

var allEnvironments = [
    'var express = require(\'express\');'
  , 'var passport = require(\'passport\');'
  , ''
  , 'module.exports = function() {'
  , '  // Warn of version mismatch between global "lcm" binary and local installation'
  , '  // of Yodajs.'
  , '  if (this.version !== require(\'yodajs\').version) {'
  , '    console.warn(\'version mismatch between local (%s) and global (%s) Yodajs module\', require(\'yodajs\').version, this.version);'
  , '  }'
  , ''
  , '  this.use(passport.initialize());'
  , '  this.use(passport.session());'
  , '  this.datastore(require(\'locomotive-mongoose\'));'
  , '}'
  , ''
].join(eol);

var developmentEnvironment = [
    'module.exports = function() {'
  , '}'
  , ''
].join(eol);

var productionEnvironment = [
    'module.exports = function() {'
  , '}'
  , ''
].join(eol);

var genericInitializer = [
    'module.exports = function() {'
  , '  // Any files in this directory will be `require()`\'ed when the application'
  , '  // starts, and the exported function will be invoked with a `this` context of'
  , '  // the application itself.  Initializers are used to connect to databases and'
  , '  // message queues, and configure sub-systems such as authentication.'
  , ''
  , '  // Async initializers are declared by exporting `function(done) { /*...*/ }`.'
  , '  // `done` is a callback which must be invoked when the initializer is'
  , '  // finished.  Initializers are invoked sequentially, ensuring that the'
  , '  // previous one has completed before the next one executes.'
  , '}'
  , ''
].join(eol);

var mimeInitializer = [
    'module.exports = function() {'
  , '  // Define custom MIME types.  Consult the mime module [documentation](https://github.com/broofa/node-mime)'
  , '  // for additional information.'
  , '  /*'
  , '  this.mime.define({'
  , '    \'application/x-foo\': [\'foo\']'
  , '  });'
  , '  */'
  , '}'
  , ''
].join(eol);

var viewsInitializer = [
    'module.exports = function() {'
  , '  // Configure view-related settings.  Consult the Express API Reference for a'
  , '  // list of the available [settings](http://expressjs.com/api.html#app-settings).'
  , '  this.set(\'views\', __dirname + \'/../../app/views\');'
  , '  this.set(\'view engine\', \'ejs\');'
  , ''
  , '  // Register EJS as a template engine.'
  , '  this.engine(\'ejs\', require(\'ejs\').__express);'
  , ''
  , '  // Override default template extension.  By default, Yodajs finds'
  , '  // templates using the `name.format.engine` convention, for example'
  , '  // `index.html.ejs`  For some template engines, such as Jade, that find'
  , '  // layouts using a `layout.engine` notation, this results in mixed conventions'
  , '  // that can cause confusion.  If this occurs, you can map an explicit'
  , '  // extension to a format.'
  , '  /* this.format(\'html\', { extension: \'.jade\' }) */'
  , ''
  , '  // Register formats for content negotiation.  Using content negotiation,'
  , '  // different formats can be served as needed by different clients.  For'
  , '  // example, a browser is sent an HTML response, while an API client is sent a'
  , '  // JSON or XML response.'
  , '  /* this.format(\'xml\', { engine: \'xmlb\' }); */'
  , '}'
  , ''
].join(eol);

var middlewareInitializer = [
    'var express = require(\'express\')'
  , '  , poweredBy = require(\'connect-powered-by\')'
  , '  , bodyParser = require(\'body-parser\')'
  , '  , methodOverride = require(\'method-override\')'
  , '  , errorHandler = require(\'errorhandler\');'
  , ''
  , 'module.exports = function() {'
  , '  // Use middleware.  Standard [Connect](http://www.senchalabs.org/connect/)'
  , '  // middleware is built-in, with additional [third-party](https://github.com/senchalabs/connect/wiki)'
  , '  // middleware available as separate modules.'
  , '  if (\'development\' == this.env) {'
  , '    this.use(require(\'morgan\')());'
  , '  }'
  , ''
  , '  this.use(poweredBy(\'Yoda Solution\'));'
  , '  this.use(express.static(__dirname  + \'/../../public\'));'
  , '  this.use(methodOverride());'
  , '  this.use(bodyParser.json());'
  , '  this.use(bodyParser.urlencoded({}));'
  , '  this.use(this.router);'
  , '  this.use(errorHandler());'
  , '}'
  , ''
].join(eol);

var serverJS = [
    'var yodajs = require(\'yodajs\')'
  , '  , bootable = require(\'bootable\');'
  , ''
  , '// Create a new application and initialize it with *required* support for'
  , '// controllers and views.  Move (or remove) these lines at your own peril.'
  , 'var app = new yodajs.Application();'
  , 'app.phase(yodajs.boot.controllers(__dirname + \'/app/controllers\'));'
  , 'app.phase(yodajs.boot.views());'
  , ''
  , '// Add phases to configure environments, run initializers, draw routes, and'
  , '// start an HTTP server.  Additional phases can be inserted as needed, which'
  , '// is particularly useful if your application handles upgrades from HTTP to'
  , '// other protocols such as WebSocket.'
  , 'app.phase(require(\'bootable-environment\')(__dirname + \'/config/environments\'));'
  , 'app.phase(bootable.initializers(__dirname + \'/config/initializers\'));'
  , 'app.phase(yodajs.boot.routes(__dirname + \'/config/routes\'));'
  , 'app.phase(yodajs.boot.httpServer(process.env.PORT||3000, \'0.0.0.0\'));'
  , ''
  , '// Boot the application.  The phases registered above will be executed'
  , '// sequentially, resulting in a fully initialized server that is listening'
  , '// for requests.'
  , 'app.boot(function(err) {'
  , '  if (err) {'
  , '    console.error(err.message);'
  , '    console.error(err.stack);'
  , '    return process.exit(-1);'
  , '  }'
  , '});'
  , ''
].join(eol);

var packageJSON = [
    '{'
  , '  "name": "app-name",'
  , '  "version": "0.0.1",'
  , '  "private": true,'
  , '  "dependencies": {'
  , '    "yodajs": "truongld/yodajs#v0.7.0",'
  , '    "bootable": "0.2.x",'
  , '    "bootable-environment": "0.2.x",'
  , '    "express": "4.x.x",'
  , '    "connect-powered-by": "0.1.x",'
  , '    "ejs": "0.8.x",'
  , '    "morgan": "1.x.x",'
  , '    "body-parser": "1.x.x",'
  , '    "method-override": "1.x.x",'
  , '    "errorhandler": "1.x.x",'
  , '    "gm": "1.16.0",'
  , '    "imagemagick": "0.1.3",'
  , '    "mongoose": "3.8.x",'
  , '    "locomotive-mongoose": "0.1.x",'
  , '    "mv": "2.0.0",'
  , '    "passport": "0.2.x",'
  , '    "passport-facebook": "^1.0.3",'
  , '    "passport-github": "^0.1.5",'
  , '    "passport-google-oauth": "^0.1.5",'
  , '    "passport-http": "^0.2.2",'
  , '    "passport-local": "1.0.x",'
  , '    "passport-twitter": "^1.0.2",'
  , '    "querystring": "0.2.0",'
  , '    "request": "^2.65.0",'
  , '    "should": "3.1.x",'
  , '    "bcrypt-nodejs": "0.0.x",'
  , '    "redis": "^2.6.2"'
  , '  },'
  , '  "scripts": {'
  , '    "start": "node server.js"'
  , '  }'
  , '}'
  , ''
].join(eol);

/**
 * Create application at the given directory `path`.
 *
 * @param {String} path
 */
function createApplicationAt(path) {
  console.log();
  process.on('exit', function(){
    console.log();
    console.log('   install dependencies:');
    console.log('     $ cd %s && npm install', path);
    console.log();
    console.log('   run the app:');
    console.log('     $ node server or yoda server -p [PORT] -e [development|production]');
    console.log();
  });
  
  mkdir(path, function() {
    mkdir(path + '/app');
    mkdir(path + '/app/controllers', function(){
      write(path + '/app/controllers/pagesController.js', pagesController);
    });
    mkdir(path + '/app/models', function(){
      write(path + '/app/models/User.js', userModel);
    });
    mkdir(path + '/app/views');
    mkdir(path + '/app/views/pages', function(){
      write(path + '/app/views/pages/main.html.ejs', mainTemplate);
    });
    
    mkdir(path + '/config', function(){
      write(path + '/config/routes.js', routesTemplate);
      write(path + '/config/oauth.js', oauthConfig);
    });
    mkdir(path + '/config/environments', function(){
      write(path + '/config/environments/all.js', allEnvironments);
      write(path + '/config/environments/development.js', developmentEnvironment);
      write(path + '/config/environments/production.js', productionEnvironment);
    });
    mkdir(path + '/config/initializers', function(){
      write(path + '/config/initializers/00_generic.js', genericInitializer);
      write(path + '/config/initializers/01_mime.js', mimeInitializer);
      write(path + '/config/initializers/02_views.js', viewsInitializer);
      write(path + '/config/initializers/03_passport.js', passportInitializer);
      write(path + '/config/initializers/04_mongoose.js', mongooseInitializer);
      write(path + '/config/initializers/30_middleware.js', middlewareInitializer);
    });
    
    mkdir(path + '/public');
    mkdir(path + '/public/stylesheets', function(){
      write(path + '/public/stylesheets/screen.css', cssStylesheet);
    });
    
    write(path + '/package.json', packageJSON);
    write(path + '/server.js', serverJS);
  });
}

/**
 * Check if the given directory `path` is empty.
 *
 * @param {String} path
 * @param {Function} fn
 */
function emptyDirectory(path, fn) {
  fs.readdir(path, function(err, files){
    if (err && 'ENOENT' != err.code) { throw err; }
    fn(!files || !files.length);
  });
}

/**
 * Mkdir -p.
 *
 * @param {String} path
 * @param {Function} fn
 */
function mkdir(path, fn) {
  mkdirp(path, 0755, function(err){
    if (err) { throw err; }
    console.log('   \033[36mcreate\033[0m : ' + path);
    if (fn) {
      fn();
    }
  });
}

/**
 * echo str > path.
 *
 * @param {String} path
 * @param {String} str
 */
function write(path, str) {
  fs.writeFile(path, str);
  console.log('   \x1b[36mcreate\x1b[0m : ' + path);
}

/**
 * Exit with the given `str`.
 *
 * @param {String} str
 */
function abort(str) {
  console.error(str);
  process.exit(1);
}