diff --git a/gulpfile.js b/gulpfile.js index 1064d24016..cba29e4d57 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,39 +1,57 @@ var gulp = require('gulp'), livereload = require('gulp-livereload'), nodemon = require('gulp-nodemon'), - shell = require('gulp-shell'), + gutil = require('gulp-util'), chalk = require('chalk'), - runSequence = require('run-sequence'), + runSequence = require('run-sequence').use(gulp), argv = require('minimist')(process.argv.slice(2)), _ = require('lodash'), exec = require('child_process').exec, + spawn = require('child_process').spawn, submodule = require('gulp-git-submodule'), fs = require('fs'), - config, + paramConfig, gitBranches, + swallowError, nodemonServerInit, filterParams, - getGitBranch, - checkDirectoryExistance; + getGitCommand, + checkDirectoryExistance, + ember; -// This config is used to store constant values to check against -// called options as well as the paths for each repository -config = { +// paramConfig is used to store constant values to check against +// called parameter as well as the paths for each repository +paramConfig = { ghost: { type: 'string', regex: /pr\/\d+/i, path: 'core/server' }, + g: { + type: 'string', + regex: /pr\/\d+/i, + path: 'core/server' + }, admin: { type: 'string', regex: /pr\/\d+/i, path: 'core/client' }, + a: { + type: 'string', + regex: /pr\/\d+/i, + path: 'core/client' + }, casper: { type: 'string', regex: /pr\/\d+/i, path: 'content/themes/casper' }, + c: { + type: 'string', + regex: /pr\/\d+/i, + path: 'content/themes/casper' + }, force: { type: 'boolean' }, @@ -52,6 +70,13 @@ gitBranches = { submodule.registerTasks(gulp); +ember = null; + +swallowError = function swallowError(error, log) { + if (log) {gutil.log(chalk.red(error.toString()));} + gutil.beep(); +}; + nodemonServerInit = function () { livereload.listen(); @@ -59,29 +84,32 @@ nodemonServerInit = function () { script: 'index.js', ext: 'js,json,hbs', watch: [ - // TODO: these are the files we're watching. These need probably some - // adjustment as we go ahead with this. 'core/index.js', - config.ghost.path + '**/*.js', - config.ghost.path + '**/*.json', - config.ghost.path + '**/*.hbs', + paramConfig.ghost.path + '/', 'core/built/assets/*.js' ], ignore: [ 'core/client/*', 'core/server/test/*' - ], - verbose: true + ] }).on('restart', function () { - console.info(chalk.cyan('Restarting Ghost due to changes...')); - gulp.src('index.js') + gulp.src(paramConfig.ghost.path + '/') .pipe(livereload()); + }).on('crash', function () { + console.info(chalk.red('Stopping server due to an error 💥 ...')); + if (ember) {ember.kill();} + this.emit('quit'); + process.exit(); + }).on('exit', function () { + console.info(chalk.cyan('Shutting down 🏁 ...')); }); }; -// Filter against our config (checks the type and the allowed repos that can be +// Filter against our paramConfig (checks the type and the allowed repos that can be // used as parameters, e. b. `--admin`). // Returns an Object which contains only the valid arguments as well as their value +// TODO: Make this awesome and reuse it to have options and parameters all over our gulp +// tooling! filterParams = function (args) { var filteredOptions = {}; @@ -89,8 +117,11 @@ filterParams = function (args) { key = typeof key === 'string' ? key.toLowerCase().trim() : key; value = typeof value === 'string' ? value.toLowerCase().trim() : value; - if (config.hasOwnProperty(value)) { - if (config[value].type !== typeof key) { + if (paramConfig.hasOwnProperty(value)) { + if (paramConfig[value].type !== typeof key) { + // TODO: instead of forbidding the usage of a repo param, she should + // detect the current branch (that's no problem for the ghost repo, + // but detecting it for submodules?) console.info(chalk.red('Invalid usage of "--' + value + '" option.')); return; } @@ -104,20 +135,20 @@ filterParams = function (args) { }; // Creates the shell command to checkout the branch/pr and is verified against -// the regex in the config. -getGitBranch = function (branch, repo) { - if (branch && branch.match(config[repo].regex)) { +// the regex in the paramConfig. +getGitCommand = function (branchToCheckOut, repo) { + if (branchToCheckOut && branchToCheckOut.match(paramConfig[repo].regex)) { + // Case: branch param is a PR (e. g. `pr/1234`) _.assign(gitBranches[repo], { - gitCommand: 'f() { git fetch && git checkout ' + branch + '; }; f', - branch: branch + gitCommand: 'f() { git fetch && git checkout ' + branchToCheckOut + '; }; f', + branch: branchToCheckOut }); - } else if (branch) { + } else if (branchToCheckOut) { + // Case: branch param is a normal branch _.assign(gitBranches[repo], { - gitCommand: 'git fetch && git checkout ' + branch, - branch: branch + gitCommand: 'git fetch && git checkout ' + branchToCheckOut, + branch: branchToCheckOut }); - } else { - return null; } }; @@ -130,110 +161,224 @@ checkDirectoryExistance = function (directory) { if (e.code === 'ENOENT') { return false; } else { - throw e; + return swallowError(e); } } }; -// Delete all dependencies and installs npm modules again (necessary to make gulp -// work again 😛). -gulp.task('_FFS', shell.task(['rm -rf node_modules && rm -rf core/client/node_modules ' + - '&& rm -rf core/client/bower_components && npm cache clean ' + - '&& bower cache clean && npm install'])); +// ***************************************************************************** +// ------------ Utility tasks -------------------------------------------------- +// ***************************************************************************** -gulp.task('_checkout_ghost', function (cb) { - exec(gitBranches.ghost.gitCommand, function (err, stdout, stderr) { - console.info(chalk.red(stderr)); - cb(err); +gulp.task('_admin:build', function () { + var env = Object.create(process.env); + + env.CI = false; + + console.info(chalk.cyan('Starting Ghost-Admin engines 🚗 ...')); + + ember = spawn('ember', ['build', '--watch'], { + cwd: paramConfig.admin.path, + env: env + }); + + ember.stdout.on('data', function (data) { + console.info(chalk.green(data)); + }); + + ember.stderr.on('data', function (data) { + console.info(chalk.green(data)); + }); + + ember.on('close', function (code) { + console.info(chalk.red('Shutting down Ghost-Admin with ' + code)); }); }); -gulp.task('_checkout_admin', function (cb) { - // Check first, if submodule exists and update it, if not. - if (!checkDirectoryExistance(config.admin.path)) { - exec('gulp submodules', function (err, stdout, stderr) { - console.info(chalk.red(stderr)); - exec('cd ' + config.admin.path + ' && ' + gitBranches.admin.gitCommand, function (err, stdout, stderr) { - console.info(chalk.green(stdout)); +// Deletes all dependencies and installs npm modules for ghost again, to +// make gulp work again 😛). +gulp.task('_FFS', function (cb) { + console.info(chalk.cyan('Please be patient my young Padawan. This will take a little while ⏲ ...')); + exec('rm -rf node_modules && rm -rf core/client/node_modules ' + + '&& rm -rf core/client/bower_components && npm cache clean ' + + '&& bower cache clean && npm install', function (err, stdout, stderr) { + if (stdout) {console.info(chalk.green(stdout));} + if (stderr) {console.info(chalk.red(stderr));} + if (err) {swallowError(err, false);} + cb(); + }); +}); + +gulp.task('_checkout:ghost', function (cb) { + if (gitBranches.ghost.gitCommand) { + console.info(chalk.cyan('Checking out ') + chalk.red('"' + gitBranches.ghost.branch + '" ') + chalk.cyan('on Ghost...')); + exec(gitBranches.ghost.gitCommand, function (err, stdout, stderr) { + if (!stdout) { console.info(chalk.red(stderr)); - cb(err); - }); + } else { + console.info(chalk.green(stdout) + '\n ' + chalk.red(stderr)); + } + if (err) {swallowError(err, false);} + cb(); }); } else { - exec('cd ' + config.admin.path + ' && ' + gitBranches.admin.gitCommand, function (err, stdout, stderr) { - console.info(chalk.green(stdout)); - console.info(chalk.red(stderr)); - cb(err); - }); + cb(); } }); -gulp.task('_checkout_casper', function (cb) { - // Check first, if submodule exists and update it, if not. - if (!checkDirectoryExistance(config.casper.path)) { - exec('gulp submodules', function (err, stdout, stderr) { - console.info(chalk.red(stderr)); - exec('cd ' + config.casper.path + ' && ' + gitBranches.casper.gitCommand, function (err, stdout, stderr) { - console.info(chalk.green(stdout)); +gulp.task('_checkout:admin', function (cb) { + if (gitBranches.admin.gitCommand) { + console.info(chalk.cyan('Checking out ') + chalk.red('"' + gitBranches.admin.branch + '" ') + chalk.cyan('on Ghost-Admin...')); + exec('cd ' + paramConfig.admin.path + ' && ' + gitBranches.admin.gitCommand, function (err, stdout, stderr) { + if (!stdout) { console.info(chalk.red(stderr)); - cb(err); - }); + } else { + console.info(chalk.green(stdout) + '\n ' + chalk.red(stderr)); + } + if (err) {swallowError(err, false);} + cb(); }); } else { - exec('cd ' + config.casper.path + ' && ' + gitBranches.casper.gitCommand, function (err, stdout, stderr) { - console.info(chalk.green(stdout)); - console.info(chalk.red(stderr)); - cb(err); - }); + cb(); } }); -gulp.task('_client_deps', shell.task(['npm install && bower install'], { - cwd: config.admin.path, - env: { - FORCE_COLOR: true +gulp.task('_checkout:casper', function (cb) { + if (gitBranches.casper.gitCommand) { + console.info(chalk.cyan('Checking out ') + chalk.red('"' + gitBranches.casper.branch + '" ') + chalk.cyan('on Casper...')); + exec('cd ' + paramConfig.casper.path + ' && ' + gitBranches.casper.gitCommand, function (err, stdout, stderr) { + if (!stdout) { + console.info(chalk.red(stderr)); + } else { + console.info(chalk.green(stdout) + '\n ' + chalk.red(stderr)); + } + if (err) {swallowError(err, false);} + cb(); + }); + } else { + cb(); } -})); +}); + +gulp.task('_checkout:branches', function (cb) { + runSequence('_checkout:ghost', '_checkout:admin', '_checkout:casper', function (err) { + if (err) { + swallowError(err, true); + } else { + cb(); + } + }); +}); + +gulp.task('_deps:client', function (cb) { + console.info(chalk.cyan('Updating Ghost-Admin dependencies 🛠 ...')); + exec('cd ' + paramConfig.admin.path + ' && npm install && bower install', function (err, stdout, stderr) { + if (stdout) {console.info(chalk.green(stdout));} + if (stderr) {console.info(chalk.red(stderr));} + if (err) {swallowError(err, false);} + cb(); + }); +}); + +gulp.task('_deps:ghost', function (cb) { + console.info(chalk.cyan('Updating Ghost dependencies 🛠 ...')); + exec('npm install', function (err, stdout, stderr) { + if (stdout) {console.info(chalk.green(stdout));} + if (stderr) {console.info(chalk.red(stderr));} + if (err) {swallowError(err, false);} + cb(); + }); +}); + +gulp.task('_setup:basic', ['submodules'], function (cb) { + runSequence('_checkout:branches', 'deps', function (err) { + if (err) { + swallowError(err, true); + } else { + cb(); + } + }); +}); + +gulp.task('_setup:force', ['submodules'], function (cb) { + runSequence('_FFS', '_checkout:branches', 'deps', function (err) { + if (err) { + swallowError(err, true); + } else { + cb(); + } + }); +}); + +// ***************************************************************************** +// ------------ Begin public tasks --------------------------------------------- +// ***************************************************************************** // Starting the 🚗 to run the server only // No client watch here. gulp.task('server', function () { + console.info(chalk.cyan('Starting Ghost engines 🚗 ...')); nodemonServerInit(); }); // Run `gulp dev` to enter development mode // Filechanges in client will force a livereload -gulp.task('dev', ['server'], shell.task(['ember build --watch'], { - cwd: config.admin.path, - env: { - FORCE_COLOR: true +// Call it with `--deps` or `-d` to install dependencies as well` +gulp.task('dev', function (cb) { + console.info(chalk.cyan('Development mode for Ghost will start right meow 👻 ...')); + if (argv.deps || argv.d) { + runSequence( + 'submodules', + 'deps', + '_admin:build', + 'server', + cb + ); + } else { + runSequence( + 'submodules', + '_admin:build', + 'server', + cb + ); } -})); +}); // Update the submodules with gulp-git-submodule // Will update only for these cases: -// 1. admin param is set to master (`--admin master`) -// 2. submodule doesn't exist, even if admin param is given -// Can be called directly, but will checkout the master branch +// 1. submodule param is set to master (`--admin master`, or `-a master`) +// 2. submodule doesn't exist, even if submodule param is given +// Can be called directly, but will only update, if submodule doesn't exist. +// Can be called with `--force` or `-f` to force and update of the submodules gulp.task('submodules', function (cb) { var adminBranch = gitBranches.admin.branch || undefined, - casperBranch = gitBranches.casper.branch || undefined; + casperBranch = gitBranches.casper.branch || undefined, + force = (argv.force || argv.f) || undefined; - if ((!checkDirectoryExistance(config.admin.path) || adminBranch === 'master') || - (!checkDirectoryExistance(config.casper.path) || casperBranch === 'master')) { - console.info(chalk.cyan('Updating submodules...')); + if ((!checkDirectoryExistance(paramConfig.admin.path) || adminBranch === 'master') || + (!checkDirectoryExistance(paramConfig.casper.path) || casperBranch === 'master') || force) { exec('gulp sm:install', function (err, stdout, stderr) { - console.info(chalk.red(stderr)); - cb(err); + console.info(chalk.cyan('Updating Ghost submodules 🛠 ...')); + if (stderr) {console.info(chalk.red(stderr));} + if (err) {swallowError(err, false);} + cb(); }); } else { - console.info(chalk.cyan('Nothing to update...')); + console.info(chalk.cyan('No need to update Ghost submodules 🏄🏼 ...')); cb(); } }); // Task to update dependencies for ghost and admin -gulp.task('deps', ['_client_deps'], shell.task(['npm install'])); +// Can be called with `--force` or `-f` to force a delete of the dependencies and +// fresh install afterwards +gulp.task('deps', function (cb) { + if (argv.force || argv.f) { + runSequence('_FFS', '_deps:client', '_deps:ghost', cb); + } else { + runSequence('_deps:client', '_deps:ghost', cb); + } +}); // Task to make repositories ready for development. Can be used in mutliple ways: // @@ -258,45 +403,56 @@ gulp.task('deps', ['_client_deps'], shell.task(['npm install'])); // in the .git/config file for each repository: `fetch = +refs/pull/*/head:refs/remotes/upstream/pr/*`. // See https://dev.ghost.org/easy-git-pr-test/ for further information. // +// `gulp setup -a some-branch -c pr/123 -g pr/1234` +// ^ The parameters work fine with their abbreviations. +// // All the combinations above can be executed with the `--force` or `-f` flag, which // will delete the dependencies and install them again, but for the chosen branches. -gulp.task('setup', function (done) { +gulp.task('setup', function (cb) { var options = filterParams(argv) || {}, - force = (options.force || options.f) || undefined; + force = (options.force || options.f) || undefined, + branchToCheckOut; - if (options.ghost) { - getGitBranch(options.ghost, 'ghost'); - runSequence( - '_checkout_ghost' - ); + // We have to set argv back, otherwise they might be used for further called + // task, which we don't want + argv = {}; + + if (options.ghost || options.g) { + branchToCheckOut = options.ghost || options.g; + getGitCommand(branchToCheckOut, 'ghost'); } - if (options.admin) { - getGitBranch(options.admin, 'admin'); - runSequence( - '_checkout_admin' - ); + if (options.admin || options.a) { + branchToCheckOut = options.admin || options.a; + getGitCommand(branchToCheckOut, 'admin'); } - if (options.casper) { - getGitBranch(options.casper, 'casper'); - runSequence( - '_checkout_casper' - ); + if (options.casper || options.c) { + branchToCheckOut = options.casper || options.c; + getGitCommand(branchToCheckOut, 'casper'); } if (force) { - runSequence( - 'submodules', - '_FFS', - 'deps', - done - ); + runSequence('_setup:force', function (err) { + if (err) { + swallowError(err, true); + } else { + cb(); + } + }); } else { - runSequence( - 'submodules', - 'deps', - done - ); + runSequence('_setup:basic', function (err) { + if (err) { + swallowError(err, true); + } else { + cb(); + } + }); } }); + +// Default task at the moment is development. +// TODO: As soon as we have a production build task, we should +// check the current environment and use the production build as +// default pro prod environment. +gulp.task('default', ['dev']); diff --git a/package.json b/package.json index 7bc4b95940..69d98d6b76 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "gulp-git-submodule": "1.0.1", "gulp-livereload": "3.8.1", "gulp-nodemon": "2.1.0", - "gulp-shell": "0.5.2", + "gulp-util": "3.0.7", "istanbul": "0.4.5", "matchdep": "1.0.1", "minimist": "1.2.0",