0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

🛠 Improve existing gulp tasks (#7444)

refs #7427

- Deleting the `built` or `dist` directory doesn't result in an error, when running `gulp dev`
- Adds default task as `gulp dev`
- Adds abbreviations for the repositories in `gulp setup` task (`--ghost` -> `-g`, `--admin` -> `-a` and `--casper` -> `-c`) to type less 😊
- Installs dependencies on `gulp dev`, if called with `--deps` or `-d`
- Sets `verbose` option nodemon to default (=`false`) to have less unneccessary logging.
- Will do a delete of all dependencies and install them again ('FFS'), if `gulp deps` is called with a force flag.
- Will update submodules not matter what, if `gulp submodules` is called with a force flag. Calling `gulp submodules` without the force flag will only update/initiate the submodules, if the folders don't exist.
- Better logging messages and sequential task handling.
- Removes `gulp-shell` as dependency, as this is on gulp's blacklist
- Refactors code, that used `gulp-shell` to use `child_process.exec` or `spawn` instead.
- Exits properly if node crashes
-----------------------

Tasks available after this PR:

- `gulp server`: Starts a nodemon server with livereload of the ghost core only. No client build here.

- `gulp dev`: [ --deps | -d ] Starts development mode for Ghost, incl. client build and livereload for both. Will also update the submodules if the directories are missing. If called with with `--deps` flag, it will install client and core dependencies.

- `gulp submodules`: [ --force | -f ] Will update the submodules, if directories are missing. Will update no matter what, if called with force flag.

- `gulp deps`: [ --force | -f ] Will update client and core dependencies. Does a fresh install of both (delete, cache clear and install) if called with force flag.

- `gulp setup`: </br>[ --ghost | -g 'branch' or 'pr/1234' ]
		[ --admin | -a 'branch' or 'pr/1234' ]
		[ --casper | -c 'branch' or 'pr/1234' ]
		[ --force | -f ]
		Takes various - optional - parameters. If called without parameters, this task will update submodules first (if directory doesn't exist) and then install client and core dependendies.
		If called with branch 'master', submodules will be updated.
		Calling with the force flag will do a clean install of the dependencies.
		There's no autocomplete for branchnames at the moment 😔
		To check out a PR (e. g. `--ghost pr/7444`), it is necessary, that you add an additional fetch line 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.
This commit is contained in:
Aileen Nowak 2016-09-29 19:32:17 +02:00 committed by Katharina Irrgang
parent 02c7d039d8
commit 7f3876e456
2 changed files with 274 additions and 118 deletions

View file

@ -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']);

View file

@ -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",