Skip to content

Commit

Permalink
Support both Gulp 3.9.1 and 4.0.0
Browse files Browse the repository at this point in the history
Gulp 4.0.0 switched its task execution engine from `orchestrator` to
`undertaker`. As a result, certain methods and events from Gulp 3.9.1 upon which
`slush` depended disappeared:

  gulpjs/gulp#755

Supporting Gulp 4.0.0 is important because Node 10 broke the `graceful-fs`
package upon which Gulp 3.9.1 depends. While there's a workaround (updating the
`natives` package), it places a burden on users that still doesn't guarantee
that Gulp 3.9.1 will remain future-proof:

  gulpjs/gulp#2146 (comment)
  gulpjs/gulp#2162 (comment)
  nodejs/node#19786 (comment)

As it turned out, the changes required to support both versions were fairly
straighforward, and should ensure that Slush remains future-proof until the next
major Gulp update.

NOTE: The test tasks are now all asynchronous via a `done` callback, since Gulp
4 doesn't support synchronous tasks. Any synchronous slushfile task will need to
be updated.
  • Loading branch information
Bland, Mike committed May 29, 2018
1 parent c739af6 commit d92d227
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 16 deletions.
81 changes: 67 additions & 14 deletions bin/slush.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,31 @@ function handleArguments(env) {
process.nextTick(function() {
if (tasksFlag) {
return logTasks(generator.name, gulpInst);
} else if (gulpInst.start) {
// Gulp <= 3.9.1 (gulp.start() is unsupported and breaks under Node 10)
return gulpInst.start.apply(gulpInst, toRun);
}
gulpInst.start.apply(gulpInst, toRun);
runGulpV4Tasks(gulpInst, toRun);
});
}

// For Gulp 4, we have to bind gulpInst to task functions individually. We
// trigger our own `task_not_found` and `finished` events to maintain the same
// Slush interface between Slush versions (rather than relying on the default
// Gulp 4 behavior).
function runGulpV4Tasks(gulpInst, toRun) {
toRun.forEach(function(task) {
var gulpTask = gulpInst.task(task);
if (gulpTask === undefined) {
gulpInst.emit('task_not_found', { task: task });
}
gulpInst.task(task, gulpTask.bind(gulpInst));
});
gulpInst.parallel(toRun)(function(err) {
if (err) {
process.exit(1);
}
gulpInst.emit('finished');
});
}

Expand All @@ -121,7 +144,8 @@ function logGenerators(generators) {
}

function logTasks(name, localGulp) {
var tree = taskTree(localGulp.tasks);
// Gulp <= 3.9.1 uses gulp.tasks; Gulp >= 4.0.0 uses gulp.tree().
var tree = localGulp.tasks ? taskTree(localGulp.tasks) : localGulp.tree();
tree.label = 'Tasks for generator ' + chalk.magenta(name);
archy(tree).split('\n').forEach(function(v) {
if (v.trim().length === 0) return;
Expand All @@ -131,38 +155,67 @@ function logTasks(name, localGulp) {

// format orchestrator errors
function formatError(e) {
if (!e.err) return e.message;
if (e.err.message) return e.err.message;
return JSON.stringify(e.err);
var err = e.err || e.error;
if (!err) return e.message;
if (err.message) return err.message;
return JSON.stringify(err);
}

// wire up logging events
function logEvents(name, gulpInst) {
gulpInst.on('task_start', function(e) {
gutil.log('Starting', "'" + chalk.cyan(name + ':' + e.task) + "'...");
var names = getEventNames(gulpInst);

gulpInst.on(names.task_start, function(e) {
gutil.log('Starting', "'" + chalk.cyan(name + ':' + taskName(e)) + "'...");
});

gulpInst.on('task_stop', function(e) {
var time = prettyTime(e.hrDuration);
gutil.log('Finished', "'" + chalk.cyan(name + ':' + e.task) + "'", 'after', chalk.magenta(time));
gulpInst.on(names.task_stop, function(e) {
gutil.log('Finished', "'" + chalk.cyan(name + ':' + taskName(e)) + "'", 'after', chalk.magenta(taskDuration(e)));
});

gulpInst.on('task_err', function(e) {
gulpInst.on(names.task_error, function(e) {
console.error('ERR', e);
var msg = formatError(e);
var time = prettyTime(e.hrDuration);
gutil.log("'" + chalk.cyan(name + ':' + e.task) + "'", 'errored after', chalk.magenta(time), chalk.red(msg));
gutil.log("'" + chalk.cyan(name + ':' + taskName(e)) + "'", 'errored after', chalk.magenta(taskDuration(e)), chalk.red(msg));
});

gulpInst.on('task_not_found', function(err) {
log(chalk.red("Task '" + err.task + "' was not defined in `slush-" + name + "` but you tried to run it."));
process.exit(1);
});

gulpInst.on('stop', function () {
gulpInst.on(names.finished, function () {
log('Scaffolding done');
});
}

function taskName(event) {
return event.task || event.name;
}

function taskDuration(event) {
return prettyTime(event.hrDuration || event.duration);
}

function getEventNames(gulpInst) {
if (gulpInst.tasks) {
// Gulp v3.9.1 and earlier
return {
task_start: 'task_start',
task_stop: 'task_stop',
task_error: 'task_err',
finished: 'stop'
};
}
// Gulp v4.0.0 and later
return {
task_start: 'start',
task_stop: 'stop',
task_error: 'error',
finished: 'finished'
};
}

function getGenerator (name) {
return getAllGenerators().filter(function (gen) {
return gen.name === name;
Expand Down
6 changes: 4 additions & 2 deletions test/slush-test/slushfile.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use strict';
var gulp = require('gulp');

gulp.task('default', function () {
gulp.task('default', function (done) {
console.log('default');
done();
});

gulp.task('app', function () {
gulp.task('app', function (done) {
console.log('app' + (this.args.length ? ' (' + this.args.join(', ') + ')' : ''));
done(this.args[0] === 'fail' ? new Error('forced error') : undefined);
});
23 changes: 23 additions & 0 deletions test/slush.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ describe('slush', function () {
}, done);
});

it('should run multiple tasks in generator', function (done) {
runSlush(['test:app:default'], function(code, data) {
code.should.equal(0);
data.should.match(/ Starting 'test:app'\.\.\./);
data.should.match(/\napp\n/);
data.should.match(/ Finished 'test:app' after /);
data.should.match(/ Starting 'test:default'\.\.\./);
data.should.match(/\ndefault\n/);
data.should.match(/ Finished 'test:default' after /);
data.should.match(/Scaffolding done/);
}, done);
});

it('should run provided task with arguments in generator', function (done) {
runSlush(['test:app', 'arg1', 'arg2'], function(code, data) {
code.should.match(0);
Expand All @@ -49,6 +62,16 @@ describe('slush', function () {
}, done);
});

it('should fail when a task fails in generator', function (done) {
runSlush(['test:app', 'fail'], function(code, data) {
code.should.match(1);
data.should.match(/ Starting 'test:app'\.\.\./);
data.should.match(/\napp \(fail\)\n/);
data.should.match(/ 'test:app' errored after .* forced error\n/);
data.should.not.match(/Scaffolding done/);
}, done);
});

it('should fail when running a non-existing task in a generator', function (done) {
runSlush(['test:noexist'], function(code, data) {
code.should.match(1);
Expand Down

0 comments on commit d92d227

Please sign in to comment.