diff --git a/.travis.yml b/.travis.yml index 05d299e..f0566d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ language: node_js node_js: - - "0.10" - - "0.11" + - "node" + - "lts/*" +script: + - "node_modules/.bin/gulp lint" + - "npm test" + - "printf 'Finished testing with Gulp v4; now testing with Gulp v3\n' && npm install gulp@3 && npm test" diff --git a/bin/slush.js b/bin/slush.js index 1b1a538..70d0829 100755 --- a/bin/slush.js +++ b/bin/slush.js @@ -16,6 +16,7 @@ var versionFlag = argv.v || argv.version; var params = argv._.slice(); var generatorAndTasks = params.length ? params.shift().split(':') : []; var generatorName = generatorAndTasks.shift(); +var tasks = generatorAndTasks; if (!generatorName) { if (versionFlag) { @@ -34,11 +35,6 @@ if (!generator) { process.exit(1); } -// Setting cwd and slushfile dir: -argv.cwd = process.cwd(); -argv.slushfile = path.join(generator.path, 'slushfile.js'); -argv._ = generatorAndTasks; - var cli = new Liftoff({ processTitle: 'slush', moduleName: 'gulp', @@ -54,13 +50,14 @@ cli.on('requireFail', function(name) { gutil.log(chalk.red('Failed to load external module'), chalk.magenta(name)); }); -cli.launch(handleArguments, argv); +cli.launch({ + // Setting cwd and slushfile dir: + cwd: process.cwd(), + configPath: path.join(generator.path, 'slushfile.js') +}, handleArguments); function handleArguments(env) { - - var argv = env.argv; var tasksFlag = argv.T || argv.tasks; - var tasks = argv._; var toRun = tasks.length ? tasks : ['default']; var args = params; @@ -102,8 +99,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 Gulp 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'); }); } @@ -121,7 +141,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; @@ -131,26 +152,28 @@ 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) { @@ -158,11 +181,38 @@ function logEvents(name, gulpInst) { 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; diff --git a/gulpfile.js b/gulpfile.js index a0dd0ba..ac2f563 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,6 +7,10 @@ var jshint = require('gulp-jshint'); var codeFiles = ['**/*.js', '!node_modules/**']; +function taskSpec(tasks) { + return gulp.parallel ? gulp.parallel(tasks) : tasks; +} + gulp.task('lint', function() { log('Linting Files'); return gulp.src(codeFiles) @@ -16,7 +20,7 @@ gulp.task('lint', function() { gulp.task('watch', function() { log('Watching Files'); - gulp.watch(codeFiles, ['lint']); + gulp.watch(codeFiles, taskSpec(['lint'])); }); -gulp.task('default', ['lint', 'watch']); +gulp.task('default', taskSpec(['lint', 'watch'])); diff --git a/package.json b/package.json index c4b569e..8858899 100644 --- a/package.json +++ b/package.json @@ -23,20 +23,20 @@ "slush": "./bin/slush.js" }, "dependencies": { - "glob": "~4.0.0", - "minimist": "~0.1.0", + "glob": "^7.1.2", + "minimist": "^1.2.0", "gulp-util": "^2.2.0", - "pretty-hrtime": "^0.2.0", - "archy": "^0.0.2", - "liftoff": "~0.10.0", - "chalk": "^0.4.0" + "pretty-hrtime": "^1.0.3", + "archy": "^1.0.0", + "liftoff": "^2.5.0", + "chalk": "^2.4.1" }, "devDependencies": { - "mocha": "^1.17.0", - "should": "^3.1.0", - "jshint": "^2.4.1", - "gulp-jshint": "^1.4.0", - "gulp": "~3.6.2" + "mocha": "^5.2.0", + "should": "^13.2.1", + "jshint": "^2.9.5", + "gulp-jshint": "^2.1.0", + "gulp": "^4.0.0" }, "scripts": { "test": "node gulpfile.js && NODE_ENV=test mocha --reporter spec" diff --git a/test/slush-test/slushfile.js b/test/slush-test/slushfile.js index 88246c5..c151260 100644 --- a/test/slush-test/slushfile.js +++ b/test/slush-test/slushfile.js @@ -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); }); diff --git a/test/slush.js b/test/slush.js index c9a6a5f..958f8ef 100644 --- a/test/slush.js +++ b/test/slush.js @@ -4,116 +4,119 @@ var spawn = require('child_process').spawn, describe('slush', function () { it('should list installed generators', function (done) { - var slush = runSlush(); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(undefined, function(code, data) { code.should.equal(0); data.should.match(/\[slush\] ├── bad/); data.should.match(/\[slush\] └── test/); - done(); - }); + }, done); }); it('should list tasks for given generator', function (done) { - var slush = runSlush(['test', '--tasks']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['test', '--tasks'], function(code, data) { code.should.equal(0); data.should.match(/├── default/); data.should.match(/└── app/); - done(); - }); + }, done); }); it('should run `default` task in generator, when task is not provided', function (done) { - var slush = runSlush(['test']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['test'], function(code, data) { code.should.equal(0); + data.should.match(/ Starting 'test:default'\.\.\./); data.should.match(/\ndefault\n/); - done(); - }); + data.should.match(/ Finished 'test:default' after /); + data.should.match(/Scaffolding done/); + }, done); }); it('should run provided task in generator', function (done) { - var slush = runSlush(['test:app']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['test:app'], function(code, data) { code.should.equal(0); + data.should.match(/ Starting 'test:app'\.\.\./); data.should.match(/\napp\n/); - done(); - }); + data.should.match(/ Finished 'test:app' after /); + data.should.match(/Scaffolding done/); + }, 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) { - var slush = runSlush(['test:app', 'arg1', 'arg2']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['test:app', 'arg1', 'arg2'], function(code, data) { code.should.equal(0); + data.should.match(/ Starting 'test:app'\.\.\./); data.should.match(/\napp \(arg1, arg2\)\n/); - done(); - }); + data.should.match(/ Finished 'test:app' after /); + data.should.match(/Scaffolding done/); + }, done); + }); + + it('should fail when a task fails in generator', function (done) { + runSlush(['test:app', 'fail'], function(code, data) { + code.should.equal(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) { - var slush = runSlush(['test:noexist']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['test:noexist'], function(code, data) { code.should.equal(1); data.should.match(/\[slush\] Task 'noexist' was not defined in `slush-test`/); - done(); - }); + data.should.not.match(/Scaffolding done/); + }, done); }); it('should fail when running a generator without slushfile', function (done) { - var slush = runSlush(['bad']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['bad'], function(code, data) { code.should.equal(1); data.should.match(/\[slush\] No slushfile found/); data.should.match(/\[slush\].+issue with.+`slush-bad`/); - done(); - }); + }, done); }); it('should fail trying to run a non-existing generator', function (done) { - var slush = runSlush(['noexist']); - var data = ''; - slush.stdout.on('data', function (chunk) { - data += chunk; - }); - slush.on('close', function (code) { + runSlush(['noexist'], function(code, data) { code.should.equal(1); data.should.match(/\[slush\] No generator by name: "noexist" was found/); - done(); - }); + }, done); }); }); -function runSlush (args) { - args = args || []; - var slush = spawn('node', [path.join(__dirname, '..', 'bin', 'slush.js')].concat(args), {cwd: __dirname}); +function runSlush (args, doAssertions, done) { + var slush = spawn('node', + [path.join(__dirname, '..', 'bin', 'slush.js')].concat(args || []), + {cwd: __dirname}); + var data = ''; + slush.stdout.setEncoding('utf8'); - return slush; + + slush.stdout.on('data', function (chunk) { + data += chunk; + }); + slush.stderr.on('data', function (chunk) { + data += chunk; + }); + slush.on('close', function (code) { + try { + doAssertions(code, data); + done(); + } catch (e) { + process.stderr.write(data); + done(e); + } + }); }