ember-rocks
Version:
An Em(ber) command line utility to help you build an ambitious web application
403 lines (358 loc) • 13.2 kB
JavaScript
;
var Promise = require('bluebird');
var path = require('path');
var fs = require('fs');
var exec = require('child_process').exec;
var tildify = require('tildify');
var gulp = require('gulp');
var rimraf = require('rimraf');
var gutil = require('gulp-util');
var rename = require('gulp-rename');
// Promises for each task...
// Fetching the "app/" content from any "git" protocol of github
function appContentFetchingFromGithub (appSrc, dest) {
var command = 'git clone ' + appSrc + ' app';
var rootPath = dest || process.cwd();
var clientPath = rootPath + '/client';
// if client/ folder is not existed, simply create one
if (!fs.existsSync(clientPath)) {
fs.mkdirSync(clientPath);
}
gutil.log(
gutil.colors.gray('[-log:]'),
'Going to fetch the app template from ',
gutil.colors.cyan(appSrc)
);
// change to the client folder to install app template
process.chdir(path.resolve(clientPath));
return new Promise(function (resolve, reject) {
// fetch the git url now
exec(command, function (err, stdout, stderr) {
if (err !== null) {
appContentFetchingError(stderr, reject, {appSrc: appSrc});
}
// After fetching a git repo, then remove the .git folder
rimraf(path.join(dest, 'client', 'app', '.git'), function (err) {
if (err !== null) {
appContentFetchingError(stderr, reject);
}
// this function has been decorated with options
appGenerationLogger(resolve, {remote: true})();
});
});
});
}
// Copying "app/" folder contents from Local file system bundled with "ember-rocks"
function appContentScaffoldFromLocal (appSrc, dest) {
return new Promise(function (resolve, reject) {
gulp.src(appSrc, {dot: true})
.on('error', reject)
.on('end', appGenerationLogger(resolve))
.pipe(gulp.dest(dest + '/client/app'));
});
}
// Copy "scaffold/core" files to the destination
function copyCoreContent (dest) {
// get the full path to the core of application. ( Server && Client )
var skeletonsCorePath = getSkeletonsCorePath();
var coreSrc = [skeletonsCorePath + '/**/*'];
return new Promise(function (resolve, reject) {
// Scaffold the "core/" of the application. ( Server && Client )
gulp.src(coreSrc, {dot: true})
.on('error', reject)
.on('end', coreGenerationLogger(resolve))
.pipe(gulp.dest(dest));
});
}
// "npm install" task to install project module dependencies
function npmInstaller (dest) {
return new Promise(function (resolve, reject) {
dest = dest || process.cwd();
gutil.log(gutil.colors.gray('[-log:]'), 'NPM is installing node packages...');
process.chdir(dest);
return exec('npm install', function (error, stdout, stderr) {
if (error !== null) {
stderr = typeof stderr === 'string' ? stderr : stderr.toString();
npmInstallationFailLogger();
reject(stderr);
}
resolve();
});
});
}
// "bower install" task to install client side libraries
function bowerInstaller (dest) {
return new Promise(function (resolve, reject) {
dest = dest || process.cwd();
gutil.log(gutil.colors.gray('[-log:]'), 'Bower is installing javascript packages...');
process.chdir(dest);
return exec('bower install', function (error, stdout, stderr) {
if (error !== null) {
stderr = typeof stderr === 'string' ? stderr : stderr.toString();
bowerInstallationFailLogger();
reject(stderr);
}
resolve();
});
});
}
// "git init" task to kick start a git project
function gitInitializer (dest) {
gutil.log(
gutil.colors.gray('[-log:]'),
gutil.colors.cyan('em-cli'),
'is doing REALLY hard to initialize your repo ...'
);
var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var today = new Date();
var todayDate = today.getDate();
var todayMonth = month[today.getMonth()];
var todayYear = today.getFullYear();
var command = 'git init && git add . && git commit -m \'Initial Commit @ ' +
todayMonth + ' ' + todayDate + ', ' + todayYear + '\'';
return new Promise(function (resolve, reject) {
dest = dest || process.cwd();
process.chdir(dest);
return exec(command, function (error, stdout, stderr) {
if (error !== null) {
stderr = typeof stderr === 'string' ? stderr : stderr.toString();
gitInitializerFailLogger();
reject(stderr);
}
resolve();
});
});
}
function isBinaryExist (bin) {
var command = bin + ' --help';
return new Promise(function (resolve, reject) {
return exec(command, function (error) {
if (error !== null) {
var err = '"' + bin + '" executable is not existed in your system!\n' +
' [-Error:] Application scaffolding process has failed!';
reject(err);
}
resolve();
});
});
}
// rename `gitignore` to `.gitignore`
// then remove the originial `gitignore`
function setupGitignore (dest) {
gulp.src('./gitignore')
.pipe(rename('.gitignore'))
.on('end', function () {
rimraf('./gitignore', function () {
});
})
.pipe(gulp.dest(dest));
}
// Second series of tasks
// Flow Control: execute serial tasks: npm install, bower install, git init
function installerTasks (newFolderName, dest) {
var tasks = [
isBinaryExist('git'),
isBinaryExist('bower'),
npmInstaller(dest),
bowerInstaller(dest),
gitInitializer(dest)
];
Promise.all(tasks)
.then(function () {
successInfoLogger(newFolderName);
// launch warning info if missing required executable
warningInfoLogger();
})
.catch(function (err) {
// output error for individual task
failureInfoLogger(err);
});
}
// executed by "runTasks" function, copy "app/" assets
function copyAppContent (dest, options) {
// get the full path to the ember application or take the generator from github or an URL
var appSrcPath = getSkeletonsAppPath(options);
var appSrc = ( appSrcPath.indexOf('http') !== -1 ) ? appSrcPath : [appSrcPath + '/**/*'];
var ret;
// if option.path exist and it is a git url, it will be fetched
// Otherwise, it will use the default scaffold folder app
// it should return an Promise
if (typeof appSrc === 'string') {
// fetching "app/" contents from github repo url
ret = appContentFetchingFromGithub(appSrc, dest);
} else {
// Scaffolding the "app/" folder from bundled "ember-rocks"
ret = appContentScaffoldFromLocal(appSrc, dest);
}
return ret;
}
function runTasks (newFolderName, options) {
var dest = path.resolve(newFolderName);
// check for the mode, is running test or not
var isRunningTest = options.test || false;
gutil.log(gutil.colors.gray('[-log:]'), 'Starting to generate an application at',
gutil.colors.magenta(tildify(dest))
);
var tasks = [
copyCoreContent(dest),
copyAppContent(dest, options)
];
Promise.all(tasks)
.then(function () {
// switch to the newly generated folder
process.chdir(dest);
// can be run in concurrency, since it won't affect other tasks
setupGitignore(dest);
})
.then(function () {
if (!isRunningTest) {
installerTasks(newFolderName, dest);
}
})
.catch(function () {
gutil.log(gutil.colors.red('[-Error:]'),
'ember-rocks could not generate an application due to the error above!');
process.exit(0);
});
}
// Create command entry point function
function create (generatorPath, options) {
if (!generatorPath || typeof generatorPath !== 'string') {
gutil.log(gutil.colors.red('[-Error:] Missing directory name.'), 'ex: em new my-app');
gutil.log(gutil.colors.red('[-Error:]'), 'See \'em new --help\'');
process.exit(0);
}
// Create the folder if it is not existed
// If existed, do what? maybe just empty it and start scaffolding???
if (fs.existsSync(generatorPath)) {
gutil.log(
gutil.colors.red('[-Error:]'), 'The folder name',
gutil.colors.red(generatorPath), 'has existed in this directory tree!'
);
process.exit(0);
} else {
// Create a new directory name what user passed in
fs.mkdirSync(generatorPath);
}
// Orchestration, copy the source files into the newly create folder
runTasks(generatorPath, options);
}
module.exports = create;
// Resolve project paths start...
function pathResolver (relativePath) {
return path.resolve(__dirname, '..', relativePath);
}
function getSkeletonsCorePath () {
var skeletonsCorePath = pathResolver('skeletons/core');
return skeletonsCorePath;
}
function getSkeletonsAppPath (options) {
// Pass in a valid git url for installing ember-application-template
var re = /^http(?:s)?:\/\//;
var userInputPath = options.path;
// check for remote URL path
var remoteUrl = (userInputPath) ?
(re.test(userInputPath)) ?
userInputPath : ('http://' + userInputPath) : undefined;
var skeletonsAppPath = (remoteUrl !== undefined) ? remoteUrl : pathResolver('skeletons/app');
return skeletonsAppPath;
}
// App content github fetching error handler start...
function appContentFetchingError (err, reject, options) {
return function () {
var log = err.toString();
gutil.log(gutil.colors.red('[-Error:] ' + log));
if (options.appSrc) {
gutil.log(gutil.colors.red('[-Error:] --path ' + options.appSrc + ' cannot be fetched!'));
}
reject(err);
};
}
// Logger functions start...
// Trigger when NPM installation fails, output error message in terminal
function npmInstallationFailLogger (err) {
gutil.log(gutil.colors.red('[-Error:] "npm install" failed dramatically.'));
gutil.log(gutil.colors.red('[-Error:] Need to manually fix the ERRORS, then do "npm install"'));
gutil.log(gutil.colors.red('[-Error:] Before the project is fully ready for development'));
}
// Trigger when Bower installation fails, output error message in terminal
function bowerInstallationFailLogger (err) {
gutil.log(gutil.colors.red('[-Error:] "bower install" failed dramatically.'));
gutil.log(gutil.colors.red('[-Error:] Need to manually fix the ERRORS, then do "bower install"'));
gutil.log(gutil.colors.red('[-Error:] Before the project is fully ready for development'));
}
// Trigger when Git initialization fails, output error message in terminal
function gitInitializerFailLogger (err) {
gutil.log(gutil.colors.red('[-Error:] initialize "git" repository failed dramatically.'));
gutil.log(gutil.colors.red('[-Error:] Need to manually do'));
gutil.log(gutil.colors.red('[-Error:] "git init && git add . && git commit -m"'));
}
// When `em new` command succeed and output successfully message to user
function successInfoLogger (newFolderName) {
gutil.log(
gutil.colors.green('[-done:] Initialized a new git repo and did a first commit')
);
gutil.log(
gutil.colors.bold('[-copy:] =>'),
gutil.colors.cyan('cd ' + newFolderName),
gutil.colors.gray('# navigate to the newly created application')
);
gutil.log(
gutil.colors.bold('[-copy:] =>'),
gutil.colors.cyan('em serve'),
gutil.colors.gray(' # kick start the server, open project in favorite browser,'),
gutil.colors.gray('auto watch file changes and rebuild the project')
);
}
function failureInfoLogger (err) {
gutil.log(gutil.colors.red('[-Error:] ' + err));
}
function warningInfoLogger (err) {
// Currently, only "sass" is required. If more, use Promise.all instead
// "sass" is required, alert user if it is not existed
isBinaryExist('sass')
.catch(function (err) {
gutil.log(gutil.colors.red('[-Warning:] Scaffolding is completed and App is ready to ' +
'rock. But you may not see the page with right styles.'));
gutil.log(
gutil.colors.red('[-Warning:] "sass" executable is used to pre-compile your "css"'));
gutil.log(
gutil.colors.red('[-Warning:] use "gen install sass", more info "http://sass-lang.com/"'));
});
}
// When "app/" folder has been inserted into "client/" for client side development
function appGenerationLogger (resolve, options) {
options = options || {};
return function () {
if (options.remote) {
gutil.log(
gutil.colors.green('[-done:] Successfully fetched and installed the app template ')
);
}
gutil.log(
gutil.colors.green('[-done:] A new'),
gutil.colors.cyan('Ember.js'),
gutil.colors.green('mvc application have been successfully created!')
);
resolve();
};
}
// When all root files and core files has been generated from the scaffold folder
function coreGenerationLogger (resolve) {
return function () {
gutil.log(
gutil.colors.green('[-done:] A new'),
gutil.colors.cyan('Node.js'),
gutil.colors.green('web server have been successfully created!')
);
gutil.log(
gutil.colors.gray('[-log:]'),
gutil.colors.magenta('It may take up to 1 minute and half!')
);
gutil.log(
gutil.colors.gray('[-log:]'),
gutil.colors.magenta('Be patient, fetching packages from internet ...')
);
resolve();
};
}