bin/clever-init
#!/usr/bin/env node
var path = require('path')
, lib = GLOBAL.lib = require(path.join(__dirname, '..', 'lib'))
, program = GLOBAL.program = require('commander')
, utils = lib.utils
, fs = require('fs')
, mkdirp = require('mkdirp')
, rimraf = require('rimraf')
, async = require('async')
, exec = require('child_process').exec
, spawn = require('child_process').spawn
, os = require('os')
, isWin = /^win32/.test(os.platform())
, readline = require('readline')
, Promise = require('bluebird')
, singleSeed = true
, seedsToInstall = []
, installBackend = false
, installFrontend = false
, backendVersion = false
, frontendVersion = false
, projectFolder = null
, project = null
, args = null
, exists = false;
/** Define CLI Options
================================*/
program
.option('-f, --force', 'delete existing projects in your current directory ' + process.cwd())
.option('-v, --verbose', 'verbose output useful for debugging')
.option('-A, --allow-root', 'allow root for bower')
.option('-S, --skip-protractor', 'skips installing protractor (Frontend only)')
.option('-B, --bootstrap', 'will run `grunt bootstrap build` as part of the setup')
.version(lib.pkg.version);
/** Define CLI Command
================================*/
program
.command('<project>')
.description('creates a new project named <project>');
/** Define CLI Help
================================*/
program.on('--help', function() {
console.log(' Examples:');
console.log('');
console.log(' clever init my-project install the backend and frontend');
console.log(' clever init my-project clever-auth with the clever-auth module');
console.log(' clever init my-project backend frontend verbose way of running "clever init my-project"');
console.log(' clever init my-project frontend only install the frontend');
console.log(' clever init my-project backend clever-auth install the clever-auth module after installing the backend and frontend seeds');
console.log('');
console.log(' Installing specific versions:');
console.log('');
console.log(' clever init my-project backend@<version>');
console.log(' clever init my-project clever-auth@<version>');
console.log('');
});
/** Parse CLI Arguments
================================*/
program.parse(process.argv);
if (!program.args[0] || program.args[0].toString().trim() === '') {
program.help();
}
/** Utility functions
================================*/
/**
* Writes a local.json file within projectDir
* with the basic ORM values
*
* @todo we need to change this over to a template
* @param {String} projectDir
* @return {Promise}
* @api private
*/
function writeLocalJSON(projectDir) {
var configDir = path.resolve(path.join(projectDir, 'config'));
return new Promise(function(resolve, reject) {
var localJSONFile = require(path.join(configDir, 'local.example.json'));
utils
.info(' Creating local configuration file config/local.json...')
.running('Creating config/local.json...');
fs.writeFile(path.join(configDir, 'local.json'), JSON.stringify(localJSONFile, null, 2), function(err) {
if (!!err) {
return reject(err);
}
resolve();
});
});
}
/**
* Installs the NPM dependencies for the given projectDir
*
* @param {String} projectDir
* @return {Promise}
* @api private
*/
function installNPMPackages(projectDir) {
return new Promise(function(resolve, reject) {
utils
.info(' Installing NPM modules...')
.running('Installing NPM modules...');
var args = ['install']
, opts = { env: process.env, cwd: projectDir };
var proc = spawn(!isWin ? 'npm' : 'npm.cmd', args, opts)
, error = '';
// Create a readline interface because otherwise the buffer will
readline
.createInterface({
input : proc.stdout,
terminal : false
})
.on('line', function(line) {
if (!!program.verbose) {
console.log(' ' + line);
}
});
proc.on('error', function(err) {
error += err;
});
proc.on('close', function(code) {
if (code !== 0 || !!error && error !== 'Command failed: ') {
utils.fail(error);
reject(error);
} else {
resolve();
}
});
});
}
/**
* Installs the node-seed as 'backend'.
*
* @return {Promise}
* @api private
*/
function setupBackend() {
return new Promise(function(resolve, reject) {
var projectDir = (singleSeed === true) ? projectFolder : path.join(projectFolder, 'backend');
utils.info([ 'Installing Backend', (backendVersion ? '@' + backendVersion : ''),'...' ].join(''));
utils.running('Installing...');
if (projectDir !== projectFolder) {
utils.info([ ' Installation path is ', projectDir, '...' ].join(''));
}
mkdirp(projectDir, function backendProjectDirReady(err) {
var csPackage = {
owner: 'CleverStack',
name: 'node-seed' + (backendVersion ? '@' + backendVersion : '')
};
if (!!err) {
return reject(err);
}
utils
.info(' Downloading and extracting ' + csPackage.name + '...')
.running('Downloading and extracting ' + csPackage.name + '...');
lib.packages
.get(csPackage, projectDir)
.then(function() {
utils.progress();
return writeLocalJSON(projectDir);
})
.then(function() {
utils.progress();
return installNPMPackages(projectDir);
})
.then(function() {
utils
.info(' Installing module dependencies...', '└── ')
.running('Installing module dependencies...')
.progress();
return lib.util.dependencies.installPeerDependencies(projectDir);
})
.then(function() {
utils.progress();
utils.success('Backend installation has completed successfully!\n');
resolve();
})
.catch(reject);
});
});
}
function installBowerComponents(projectDir) {
return new Promise(function(resolve, reject) {
var bowerPath = path.resolve(path.join(projectDir, 'bower.json'));
utils.running('Installing bower components...');
utils.info([ ' Installing bower components...' ].join(''));
fs.exists(bowerPath, function(exists) {
if (!exists) {
return reject('Bower not found');
}
lib.project
.installBowerComponents({
moduleDir: projectDir,
modulePath: 'app/modules' // todo: Call locations() after population
})
.then(function() {
resolve();
})
.catch(function(err) {
reject(err);
});
});
});
}
/**
* Installs angular-seed as 'frontend'.
*
* @return {Promise}
* @api private
*/
function setupFrontend() {
return new Promise(function(finalResolve, finalReject) {
var projectDir = (singleSeed === true) ? projectFolder : path.join(projectFolder, 'frontend');
utils.info([ 'Installing Frontend', (frontendVersion ? '@' + frontendVersion : ''),'...' ].join(''));
utils.running('Installing...');
if (!!program.skipProtractor) {
utils.warn(' Skipping installation of protractor');
}
if (projectDir !== projectFolder) {
utils.info([ ' Installation path is ', projectDir, '...' ].join(''));
}
mkdirp(path.resolve(projectDir), function(err) {
var csPackage = {
owner: 'CleverStack',
name: 'angular-seed' + (frontendVersion ? '@' + frontendVersion : '')
};
if (!!err) {
return finalReject(err);
}
utils.info([ ' Downloading and extracting ', csPackage.name, '...' ].join(''));
utils.running('Downloading and extracting...');
lib.packages
.get(csPackage, projectDir)
.then(function() {
utils.progress();
return installNPMPackages(projectDir);
})
.then(function() {
utils.progress();
utils.info([ ' Installing bundled modules...' ].join(''));
utils.running('Installing bundled modules...');
return new Promise(function(resolve, reject) {
var modulesFolder = path.resolve(path.join(projectDir, 'app', 'modules'))
, modules = [];
if (fs.existsSync(modulesFolder)) {
modules = fs.readdirSync(modulesFolder);
var keep = modules.indexOf('.gitkeep');
if (keep > -1) {
modules.splice(keep, 1);
}
}
async.each(
modules,
function(m, callback) {
var installOptions = { moduleDir: projectDir, modulePath: path.join('modules', m) }
, module = path.resolve(path.join(modulesFolder, m));
lib.project
.installModule(installOptions, module)
.then(function() {
callback();
})
.catch(function(err) {
callback(err);
});
},
function(err) {
if (!err) {
resolve();
} else {
reject(err);
}
}
);
});
})
.then(function() {
utils.progress();
return installBowerComponents(projectDir);
})
.then(function() {
utils.progress();
return new Promise(function(resolve, reject) {
if (!!program.bootstrap) {
if (program.verbose) {
utils.info([ ' Building bootstrap for frontend...' ].join(''), !!program.skipProtractor ? '└── ' : '├── ');
}
utils.running('Building bootstrap for frontend...');
var proc = exec('grunt bootstrap build', { cwd: projectDir }, function(err) {
if (!err) {
utils.success([ 'Frontend installation has completed successfully.' ].join(''));
resolve();
} else {
reject(err);
}
});
// Pipe the output of exec if verbose has been specified
if (program.verbose) {
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stdout);
}
} else {
resolve();
}
});
})
.then(function() {
utils.progress();
return new Promise(function(resolve, reject) {
if (!!program.skipProtractor) {
return resolve();
} else {
utils.info([ ' Installing protractor...' ].join('') , '└──');
utils.running('Installing protractor (this might take awhile)...');
}
var proc = exec('npm run-script setup-protractor', { cwd: projectDir }, function(err) {
if (!err) {
utils.success([ ' Protractor successfully installed...' ].join(''));
utils.progress(2);
resolve();
} else {
reject(err);
}
});
// Pipe the output of exec if verbose has been specified
if (program.verbose) {
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stdout);
}
});
})
.then(function() {
utils.success('Frontend installation has completed successfully!\n');
finalResolve();
})
.catch(function(err) {
finalReject(err);
});
});
});
}
/** Start Installation
================================*/
async.waterfall(
[
function start(callback) {
utils
.info([ 'Preparing for installation...' ].join(' '))
.running('Preparing for installation...');
// Tell promise we want long stack traces for easier debugging
Promise.longStackTraces();
// Assign required variables
project = program.args[ 0 ];
args = program.args.slice(1);
installFrontend = args.indexOf('frontend') !== -1;
installBackend = args.indexOf('backend') !== -1;
projectFolder = path.join(process.cwd(), project);
exists = fs.existsSync(projectFolder);
if (args.length < 1 || (!installFrontend && !installBackend) || (installFrontend && installBackend)) {
singleSeed = false;
installFrontend = true;
installBackend = true;
}
// Output debugging information if verbose mode is enabled
if (program.verbose) {
utils.running('Outputting debugging information...');
utils.info('-----------------------------');
utils.info('args = ' + JSON.stringify(args));
utils.info('--force = ' + program.force);
utils.info('project = "' + project + '"');
utils.info('projectFolder = "' + projectFolder+ '"');
utils.info('singleSeed = ' + singleSeed);
utils.info('installFrontend = ' + installFrontend);
utils.info('installBackend = ' + installBackend);
utils.info('-----------------------------');
}
setTimeout(callback, 100);
},
function forceDeleteProjectFolder(callback) {
utils.running('Checking if project alredy exists...');
if (exists && program.force) {
utils.warn([ ' Deleting the installation path for', project, 'before we begin installing!' ].join(' '));
utils.running('Deleting old project...');
rimraf(projectFolder, function(err) {
if (!err) {
exists = false;
callback(null);
} else {
callback([ 'Unable to delete the', project, ' folder in', process.cwd(), 'because of', err ].join(' '));
}
});
} else {
callback(null);
}
},
function createProjectFolder(callback) {
utils
.info(' Creating project installation path...', !program.verbose ? '└── ' : '├── ')
.running('Creating project installation path...');
if (!exists) {
if (program.verbose) {
utils.info([ ' Creating installation path', projectFolder + '...' ].join(' '), '├── ');
}
utils.running([ ' Creating installation path', projectFolder + '...' ].join(' '));
mkdirp(projectFolder, function(err) {
callback(!err ? null : [ 'Cannot create', project, 'folder in', process.cwd(), 'because of', err ].join(' '));
});
} else {
callback([ project, 'folder already exists at', process.cwd() + ',', 'to force/overwrite (delete)', project, ' use -f or --force' ].join(' '));
}
},
function findTargets(callback) {
var progress = 0;
if (program.verbose) {
utils.info([ ' Preparing seed installation list...' ].join(' '), '└── ');
}
utils.running('Preparing seed installation list...');
if (installBackend) {
if (args.indexOf('backend') !== -1) {
backendVersion = args[ args.indexOf('backend') ].split('@')[ 1 ];
args.splice(args.indexOf('backend'), 1);
}
progress += 6;
seedsToInstall.push({ name: 'Backend', install: setupBackend } );
}
if (installFrontend) {
if (args.indexOf('frontend') !== -1) {
frontendVersion = args[ args.indexOf('frontend') ].split('@')[ 1 ];
args.splice(args.indexOf('frontend'), 1);
}
progress += 8;
if (!!program.skipProtractor) {
progress -= 2;
}
if (!!program.bootstrap) {
progress += 1;
}
seedsToInstall.push({ name: 'Frontend', install: setupFrontend } );
}
// Make progress longer based on how many modules we have to install
if (args.length) {
utils.expandProgress(args.length + progress);
callback(null);
} else {
utils.initBar();
utils.startBar(function() {
utils.expandProgress(progress);
callback(null);
});
}
},
function runActions(callback) {
utils.progress();
async.forEachSeries(
seedsToInstall,
function runAction(seed, cb) {
utils.installing(seed.name);
utils.running([ ' Preparing to install', seed.name + '...' ].join(' '));
seed.install()
.then(function() {
utils.running('Seeds successfully installed...');
utils.progress();
cb(null);
})
.catch(cb);
},
callback
);
},
function installModules(callback) {
utils
.installing('Modules')
.running('Preparing to install...');
if (args.length < 1) {
if (program.verbose) {
utils.running('No modules need to be installed, skipping...');
}
process.nextTick(function() {
callback(null);
});
} else {
utils.info([ 'Installing Modules...' ].join(' '));
utils.running('Installing...');
lib.project
.setupModules({ moduleDir: projectFolder }, args)
.then(function() {
utils.progress();
callback(null);
})
.catch(callback);
}
}
],
function finishedInstallation(err) {
if (!err) {
utils
.running('Installation completed')
.installing('Done')
.success('Project ' + project + ' has been created in ' + projectFolder + '\n');
lib.utils.finishProgress();
process.exit(0);
} else {
utils.fail(err.stack ? (err + '\n' + err.stack) : err);
}
}
);