diff --git a/.eslintrc b/.eslintrc index a5a4a173..557e2d31 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "gulp" + "extends": "gulp", + "rules": { + "max-statements": [1, 40] + } } diff --git a/index.js b/index.js index 6bb1f4eb..e6238b2e 100644 --- a/index.js +++ b/index.js @@ -9,8 +9,6 @@ var Liftoff = require('liftoff'); var tildify = require('tildify'); var interpret = require('interpret'); var v8flags = require('v8flags'); -var merge = require('lodash.merge'); -var isString = require('lodash.isstring'); var findRange = require('semver-greatest-satisfied-range'); var exit = require('./lib/shared/exit'); var cliOptions = require('./lib/shared/cliOptions'); @@ -20,6 +18,10 @@ var cliVersion = require('./package.json').version; var getBlacklist = require('./lib/shared/getBlacklist'); var toConsole = require('./lib/shared/log/toConsole'); +var loadConfigFiles = require('./lib/shared/config/load-files'); +var mergeConfigToCliFlags = require('./lib/shared/config/cli-flags'); +var mergeConfigToEnvFlags = require('./lib/shared/config/env-flags'); + // Logging functions var logVerify = require('./lib/shared/log/verify'); var logBlacklistError = require('./lib/shared/log/blacklistError'); @@ -65,7 +67,7 @@ if (opts.continue) { process.env.UNDERTAKER_SETTLE = 'true'; } -// Set up event listeners for logging. +// Set up event listeners for logging temporarily. toConsole(log, opts); cli.on('require', function(name) { @@ -96,14 +98,13 @@ module.exports = run; // The actual logic function handleArguments(env) { + var cfgLoadOrder = ['home', 'cwd']; + var cfg = loadConfigFiles(env.configFiles['.gulp'], cfgLoadOrder); + opts = mergeConfigToCliFlags(opts, cfg); + env = mergeConfigToEnvFlags(env, cfg); - // Map an array of keys to preserve order - var configFilePaths = ['home', 'cwd'].map(function(key) { - return env.configFiles['.gulp'][key]; - }); - configFilePaths.filter(isString).forEach(function(filePath) { - merge(opts, require(filePath)); - }); + // Set up event listeners for logging again after configuring. + toConsole(log, opts); if (opts.help) { console.log(parser.help()); @@ -169,5 +170,5 @@ function handleArguments(env) { } // Load and execute the CLI version - require(path.join(__dirname, '/lib/versioned/', range, '/'))(opts, env); + require(path.join(__dirname, '/lib/versioned/', range, '/'))(opts, env, cfg); } diff --git a/lib/shared/config/cli-flags.js b/lib/shared/config/cli-flags.js new file mode 100644 index 00000000..ad79aa58 --- /dev/null +++ b/lib/shared/config/cli-flags.js @@ -0,0 +1,13 @@ +'use strict'; + +var copyProps = require('copy-props'); + +var fromTo = { + 'flags.silent': 'silent', +}; + +function mergeConfigToCliFlags(opt, config) { + return copyProps(config, opt, fromTo); +} + +module.exports = mergeConfigToCliFlags; diff --git a/lib/shared/config/env-flags.js b/lib/shared/config/env-flags.js new file mode 100644 index 00000000..c404e5f2 --- /dev/null +++ b/lib/shared/config/env-flags.js @@ -0,0 +1,22 @@ +'use strict'; + +var path = require('path'); +var copyProps = require('copy-props'); + +var toFrom = { + configPath: 'flags.gulpfile', + configBase: 'flags.gulpfile', +}; + +function mergeConfigToEnvFlags(env, config) { + return copyProps(env, config, toFrom, convert, true); +} + +function convert(value, configKey, envKey) { + if (envKey === 'configBase') { + return path.dirname(value); + } + return value; +} + +module.exports = mergeConfigToEnvFlags; diff --git a/lib/shared/config/load-files.js b/lib/shared/config/load-files.js new file mode 100644 index 00000000..3810c93e --- /dev/null +++ b/lib/shared/config/load-files.js @@ -0,0 +1,30 @@ +'use strict'; + +var copyProps = require('copy-props'); +var path = require('path'); + +function loadConfigFiles(configFiles, configFileOrder) { + var config = {}; + + configFileOrder.forEach(loadFile); + + function loadFile(key) { + var filePath = configFiles[key]; + if (!filePath) { + return; + } + + copyProps(require(filePath), config, convert); + + function convert(value, name) { + if (name === 'flags.gulpfile') { + return path.resolve(path.dirname(filePath), value); + } + return value; + } + } + + return config; +} + +module.exports = loadConfigFiles; diff --git a/lib/shared/log/toConsole.js b/lib/shared/log/toConsole.js index 39483bd6..2c290f0d 100644 --- a/lib/shared/log/toConsole.js +++ b/lib/shared/log/toConsole.js @@ -13,7 +13,23 @@ var levels = [ 'debug', // -LLLL: Logs all log levels. ]; +function cleanup(log) { + levels.forEach(removeListeners); + + function removeListeners(level) { + if (level === 'error') { + log.removeListener(level, noop); + log.removeListener(level, fancyLog.error); + } else { + log.removeListener(level, fancyLog); + } + } +} + function toConsole(log, opts) { + // Remove previous listeners to enable to call this twice. + cleanup(log); + // Return immediately if logging is // not desired. if (opts.tasksSimple || opts.silent) { diff --git a/lib/versioned/^3.7.0/index.js b/lib/versioned/^3.7.0/index.js index 4879095e..d6ccc326 100644 --- a/lib/versioned/^3.7.0/index.js +++ b/lib/versioned/^3.7.0/index.js @@ -12,7 +12,7 @@ var logEvents = require('./log/events'); var logTasksSimple = require('./log/tasksSimple'); var registerExports = require('../../shared/registerExports'); -function execute(opts, env) { +function execute(opts, env, config) { var tasks = opts._; var toRun = tasks.length ? tasks : ['default']; @@ -39,8 +39,8 @@ function execute(opts, env) { } if (opts.tasks) { var tree = taskTree(gulpInst.tasks); - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); } diff --git a/lib/versioned/^4.0.0-alpha.1/index.js b/lib/versioned/^4.0.0-alpha.1/index.js index 1eb91b06..9788e8be 100644 --- a/lib/versioned/^4.0.0-alpha.1/index.js +++ b/lib/versioned/^4.0.0-alpha.1/index.js @@ -16,7 +16,7 @@ var logSyncTask = require('../^4.0.0/log/syncTask'); var logTasksSimple = require('../^4.0.0/log/tasksSimple'); var registerExports = require('../../shared/registerExports'); -function execute(opts, env) { +function execute(opts, env, config) { var tasks = opts._; var toRun = tasks.length ? tasks : ['default']; @@ -44,8 +44,8 @@ function execute(opts, env) { } if (opts.tasks) { var tree = {}; - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); } diff --git a/lib/versioned/^4.0.0-alpha.2/index.js b/lib/versioned/^4.0.0-alpha.2/index.js index 63c85b49..783071d6 100644 --- a/lib/versioned/^4.0.0-alpha.2/index.js +++ b/lib/versioned/^4.0.0-alpha.2/index.js @@ -18,7 +18,7 @@ var registerExports = require('../../shared/registerExports'); var getTask = require('../^4.0.0/log/getTask'); -function execute(opts, env) { +function execute(opts, env, config) { var tasks = opts._; var toRun = tasks.length ? tasks : ['default']; @@ -49,8 +49,8 @@ function execute(opts, env) { } if (opts.tasks) { tree = gulpInst.tree({ deep: true }); - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); } @@ -59,8 +59,8 @@ function execute(opts, env) { } if (opts.tasksJson) { tree = gulpInst.tree({ deep: true }); - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + tildify(env.configPath); } diff --git a/lib/versioned/^4.0.0/index.js b/lib/versioned/^4.0.0/index.js index d434827c..042cb04e 100644 --- a/lib/versioned/^4.0.0/index.js +++ b/lib/versioned/^4.0.0/index.js @@ -18,7 +18,7 @@ var registerExports = require('../../shared/registerExports'); var getTask = require('./log/getTask'); -function execute(opts, env) { +function execute(opts, env, config) { var tasks = opts._; var toRun = tasks.length ? tasks : ['default']; @@ -49,8 +49,8 @@ function execute(opts, env) { } if (opts.tasks) { tree = gulpInst.tree({ deep: true }); - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); } @@ -59,8 +59,8 @@ function execute(opts, env) { } if (opts.tasksJson) { tree = gulpInst.tree({ deep: true }); - if (opts.description && isString(opts.description)) { - tree.label = opts.description; + if (config.description && isString(config.description)) { + tree.label = config.description; } else { tree.label = 'Tasks for ' + tildify(env.configPath); } diff --git a/package.json b/package.json index 5e316222..eeb8857f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint": "eslint . && jscs index.js bin/ lib/ test/", "prepublish": "marked-man --name gulp docs/CLI.md > gulp.1", "pretest": "npm run lint", - "test": "mocha --async-only --timeout 3000", + "test": "mocha --async-only --timeout 3000 test/lib test", "cover": "nyc --reporter=lcov --reporter=text-summary npm test", "coveralls": "nyc --reporter=text-lcov npm test | coveralls", "changelog": "github-changes -o gulpjs -r gulp-cli -b master -f ./CHANGELOG.md --order-semver --use-commit-body" @@ -34,6 +34,7 @@ "dependencies": { "archy": "^1.0.0", "chalk": "^1.1.0", + "copy-props": "^1.4.1", "fancy-log": "^1.1.0", "gulplog": "^1.0.0", "interpret": "^1.0.0", @@ -41,7 +42,6 @@ "lodash.isfunction": "^3.0.8", "lodash.isplainobject": "^4.0.4", "lodash.isstring": "^4.0.1", - "lodash.merge": "^4.5.1", "lodash.sortby": "^4.5.0", "matchdep": "^1.0.0", "mute-stdout": "^1.0.0", @@ -62,8 +62,7 @@ "fs-extra": "^0.26.1", "github-changes": "^1.0.1", "gulp": "gulpjs/gulp#4.0", - "gulp-test-tools": "^0.6.0", - "istanbul": "^0.4.5", + "gulp-test-tools": "^0.6.1", "jscs": "^2.3.5", "jscs-preset-gulp": "^1.0.0", "marked-man": "^0.1.3", diff --git a/test/config.js b/test/config-description.js similarity index 97% rename from test/config.js rename to test/config-description.js index e2ce6c48..9fd7842e 100644 --- a/test/config.js +++ b/test/config-description.js @@ -11,7 +11,7 @@ var runner = require('gulp-test-tools').gulpRunner; var fixturesDir = path.join(__dirname, 'fixtures', 'config'); var expectedDir = path.join(__dirname, 'expected', 'config'); -describe('gulp configuration', function() { +describe('config: description', function() { it('Should configure with a .gulp.* file in cwd', function(done) { runner({ verbose: false }) diff --git a/test/config-flags-gulpfile.js b/test/config-flags-gulpfile.js new file mode 100644 index 00000000..b04d84e7 --- /dev/null +++ b/test/config-flags-gulpfile.js @@ -0,0 +1,88 @@ +'use strict'; + +var expect = require('expect'); + +var path = require('path'); +var fixturesDir = path.join(__dirname, 'fixtures/config'); + +var headLines = require('gulp-test-tools').headLines; +var runner = require('gulp-test-tools').gulpRunner().basedir(fixturesDir); + +describe('config: flags.gulpfile', function() { + + it('Should configure with a .gulp.* file', function(done) { + runner + .chdir('flags/gulpfile') + .gulp() + .run(cb); + + function cb(err, stdout, stderr) { + stdout = headLines(stdout, 2, 2); + expect(stdout).toEqual( + 'This gulpfile : ' + + path.join(fixturesDir, 'flags/gulpfile/is/here/mygulpfile.js') + + '\n' + + 'The current directory : ' + path.join(fixturesDir, 'flags/gulpfile') + ); + expect(stderr).toEqual(''); + done(err); + } + }); + + it('Should configure with a .gulp.* file in the directory specified by ' + + '\n\t--cwd', function(done) { + runner + .gulp('--cwd ./flags/gulpfile') + .run(cb); + + function cb(err, stdout, stderr) { + stdout = headLines(stdout, 2, 3); + expect(stdout).toEqual( + 'This gulpfile : ' + + path.join(fixturesDir, 'flags/gulpfile/is/here/mygulpfile.js') + + '\n' + + 'The current directory : ' + path.join(fixturesDir, 'flags/gulpfile') + ); + expect(stderr).toEqual(''); + done(err); + } + }); + + it('Should ignore a ./gulp.* file if another directory is specified by ' + + '\n\t--cwd', function(done) { + runner + .chdir('./flags/gulpfile') + .gulp('--cwd ./cwd') + .run(cb); + + function cb(err, stdout, stderr) { + stdout = headLines(stdout, 1, 3); + expect(stdout).toEqual( + 'Another gulpfile : ' + + path.join(fixturesDir, 'flags/gulpfile/cwd/gulpfile.js') + ); + expect(stderr).toEqual(''); + done(err); + } + }); + + it('Should ignore a ./.gulp.* file if another gulpfile is specified by ' + + '\n\t--gulpfile', function(done) { + runner + .chdir('./flags/gulpfile') + .gulp('--gulpfile ./cwd/gulpfile.js') + .run(cb); + + function cb(err, stdout, stderr) { + stdout = headLines(stdout, 1, 3); + expect(stdout).toEqual( + 'Another gulpfile : ' + + path.join(fixturesDir, 'flags/gulpfile/cwd/gulpfile.js') + ); + expect(stderr).toEqual(''); + done(err); + } + }); + +}); + diff --git a/test/config-flags-silent.js b/test/config-flags-silent.js new file mode 100644 index 00000000..8f22f402 --- /dev/null +++ b/test/config-flags-silent.js @@ -0,0 +1,47 @@ +'use strict'; + +var expect = require('expect'); +var path = require('path'); +var skipLines = require('gulp-test-tools').skipLines; +var eraseTime = require('gulp-test-tools').eraseTime; +var eraseLapse = require('gulp-test-tools').eraseLapse; + +var fixturesDir = path.join(__dirname, 'fixtures/config'); +var runner = require('gulp-test-tools').gulpRunner().basedir(fixturesDir); + +describe('config: flags.silent', function() { + + it('Should be silent if `flags.silent` is true in .gulp.*', + function(done) { + runner + .chdir('flags/silent/t') + .gulp() + .run(cb); + + function cb(err, stdout, stderr) { + expect(stdout).toEqual(''); + expect(stderr).toEqual(''); + done(err); + } + }); + + it('Should not be silent if `flags.silent` is false in .gulp.*', + function(done) { + runner + .chdir('flags/silent/f') + .gulp() + .run(cb); + + function cb(err, stdout, stderr) { + stdout = eraseLapse(eraseTime(skipLines(stdout, 1))); + expect(stdout).toEqual( + 'Starting \'default\'...\n' + + 'Finished \'default\' after ?\n' + + '' + ); + expect(stderr).toEqual(''); + done(err); + } + }); + +}); diff --git a/test/fixtures/config/flags/gulpfile/.gulp.json b/test/fixtures/config/flags/gulpfile/.gulp.json new file mode 100644 index 00000000..b2089dc7 --- /dev/null +++ b/test/fixtures/config/flags/gulpfile/.gulp.json @@ -0,0 +1,5 @@ +{ + "flags": { + "gulpfile": "./is/here/mygulpfile.js" + } +} diff --git a/test/fixtures/config/flags/gulpfile/cwd/gulpfile.js b/test/fixtures/config/flags/gulpfile/cwd/gulpfile.js new file mode 100644 index 00000000..3b6476b6 --- /dev/null +++ b/test/fixtures/config/flags/gulpfile/cwd/gulpfile.js @@ -0,0 +1,8 @@ +'use strict'; + +var gulp = require('gulp'); + +gulp.task('default', function(done) { + console.log('Another gulpfile : ' + __filename); + done(); +}); diff --git a/test/fixtures/config/flags/gulpfile/is/here/mygulpfile.js b/test/fixtures/config/flags/gulpfile/is/here/mygulpfile.js new file mode 100644 index 00000000..61b4199a --- /dev/null +++ b/test/fixtures/config/flags/gulpfile/is/here/mygulpfile.js @@ -0,0 +1,9 @@ +'use strict'; + +var gulp = require('gulp'); + +gulp.task('default', function(done) { + console.log('This gulpfile : ' + __filename); + console.log('The current directory : ' + process.cwd()); + done(); +}); diff --git a/test/fixtures/config/flags/silent/f/.gulp.json b/test/fixtures/config/flags/silent/f/.gulp.json new file mode 100644 index 00000000..4f3822ab --- /dev/null +++ b/test/fixtures/config/flags/silent/f/.gulp.json @@ -0,0 +1,5 @@ +{ + "flags": { + "silent": false + } +} diff --git a/test/fixtures/config/flags/silent/f/gulpfile.js b/test/fixtures/config/flags/silent/f/gulpfile.js new file mode 100644 index 00000000..42466b03 --- /dev/null +++ b/test/fixtures/config/flags/silent/f/gulpfile.js @@ -0,0 +1,7 @@ +'use strict'; + +var gulp = require('gulp'); + +gulp.task('default', function(done) { + done(); +}); diff --git a/test/fixtures/config/flags/silent/t/.gulp.json b/test/fixtures/config/flags/silent/t/.gulp.json new file mode 100644 index 00000000..9917bd8c --- /dev/null +++ b/test/fixtures/config/flags/silent/t/.gulp.json @@ -0,0 +1,5 @@ +{ + "flags": { + "silent": true + } +} diff --git a/test/fixtures/config/flags/silent/t/gulpfile.js b/test/fixtures/config/flags/silent/t/gulpfile.js new file mode 100644 index 00000000..42466b03 --- /dev/null +++ b/test/fixtures/config/flags/silent/t/gulpfile.js @@ -0,0 +1,7 @@ +'use strict'; + +var gulp = require('gulp'); + +gulp.task('default', function(done) { + done(); +}); diff --git a/test/lib/config-cli-flags.js b/test/lib/config-cli-flags.js new file mode 100644 index 00000000..c541095d --- /dev/null +++ b/test/lib/config-cli-flags.js @@ -0,0 +1,73 @@ +'use strict'; + +var expect = require('expect'); +var mergeConfig = require('../../lib/shared/config/cli-flags'); + +describe('lib: config/cli-flags', function() { + + it('Should copy only config props specified to cli flags', function(done) { + var opts = {}; + + var config = { + description: 'DESCRIPTION.', + flags: { + silent: true, + gulpfile: '/path/to/gulpfile', + }, + }; + + var result = mergeConfig(opts, config); + expect(result).toEqual({ + silent: true, + }); + done(); + }); + + it('Should override cli flags with config props', function(done) { + var opts = { + help: false, + depth: 4, + silent: true, + tasks: false, + }; + + var config = { + description: 'DESCRIPTION.', + flags: { + silent: false, + gulpfile: '/path/to/gulpfile', + }, + }; + + var result = mergeConfig(opts, config); + expect(result).toEqual({ + help: false, + depth: 4, + silent: false, + tasks: false, + }); + done(); + }); + + it('Should not cause error if config is empty', function(done) { + var opts = { + help: false, + depth: 4, + silent: true, + tasks: false, + }; + + var config = {}; + + var result = mergeConfig(opts, config); + expect(result).toEqual({ + help: false, + depth: 4, + silent: true, + tasks: false, + }); + done(); + }); + +}); + diff --git a/test/lib/config-env-flags.js b/test/lib/config-env-flags.js new file mode 100644 index 00000000..31d67076 --- /dev/null +++ b/test/lib/config-env-flags.js @@ -0,0 +1,89 @@ +'use strict'; + +var expect = require('expect'); +var mergeConfig = require('../../lib/shared/config/env-flags'); + +describe('lib: config/env-flags', function() { + + it('Should copy only config props specified to env flags', function(done) { + var env = {}; + + var config = { + description: 'DESCRIPTION.', + flags: { + silent: true, + gulpfile: '/path/to/gulpfile', + }, + }; + + var result = mergeConfig(env, config); + expect(result).toEqual({ + configPath: '/path/to/gulpfile', + configBase: '/path/to', + }); + done(); + }); + + it('Should override env flags with config props', function(done) { + var env = { + cwd: '/path/to/cwd', + require: 'preload', + configNameSearch: 'configNameSearch', + configPath: '/path/of/config/path', + configBase: '/path/of/config/base', + modulePath: '/path/of/module/path', + modulePackage: { name: 'modulePackage' }, + configFiles: { aaa: {} }, + }; + + var config = { + description: 'DESCRIPTION.', + flags: { + silent: false, + gulpfile: '/path/to/gulpfile', + }, + }; + + var result = mergeConfig(env, config); + expect(result).toEqual({ + cwd: '/path/to/cwd', + require: 'preload', + configNameSearch: 'configNameSearch', + configPath: '/path/to/gulpfile', + configBase: '/path/to', + modulePath: '/path/of/module/path', + modulePackage: { name: 'modulePackage' }, + configFiles: { aaa: {} }, + }); + done(); + }); + + it('Should not cause error if config is empty', function(done) { + var env = { + cwd: '/path/to/cwd', + require: 'preload', + configNameSearch: 'configNameSearch', + configPath: '/path/of/config/path', + configBase: '/path/of/config/base', + modulePath: '/path/of/module/path', + modulePackage: { name: 'modulePackage' }, + configFiles: { aaa: {} }, + }; + + var config = {}; + + var result = mergeConfig(env, config); + expect(result).toEqual({ + cwd: '/path/to/cwd', + require: 'preload', + configNameSearch: 'configNameSearch', + configPath: '/path/of/config/path', + configBase: '/path/of/config/base', + modulePath: '/path/of/module/path', + modulePackage: { name: 'modulePackage' }, + configFiles: { aaa: {} }, + }); + done(); + }); + +}); diff --git a/test/lib/config-load-files.js b/test/lib/config-load-files.js new file mode 100644 index 00000000..9d94b6fd --- /dev/null +++ b/test/lib/config-load-files.js @@ -0,0 +1,58 @@ +'use strict'; + +var expect = require('expect'); +var path = require('path'); +var loadConfigFiles = require('../../lib/shared/config/load-files'); + +var fixturesDir = path.join(__dirname, '../fixtures/config'); + +describe('lib: config/load-files', function() { + + it('Should load config from files', function(done) { + var configFiles = { + a: path.join(fixturesDir, 'foo/bar/.gulp.json'), + b: null, + c: path.join(fixturesDir, 'qux/.gulp.js'), + }; + + var config = loadConfigFiles(configFiles, ['a', 'b', 'c']); + + expect(config).toEqual({ + description: 'description by .gulp.js in directory qux', + }); + done(); + }); + + it('Should load config files in specified order', function(done) { + var configFiles = { + a: path.join(fixturesDir, 'foo/bar/.gulp.json'), + b: null, + c: path.join(fixturesDir, 'qux/.gulp.js'), + }; + + var config = loadConfigFiles(configFiles, ['b', 'c', 'a']); + + expect(config).toEqual({ + description: 'Description by .gulp.json in directory foo/bar', + }); + done(); + }); + + it('Should convert a value of `flags.gulpfile` to absolute path', + function(done) { + var configFiles = { + a: path.join(fixturesDir, 'flags/gulpfile/.gulp.json'), + }; + + var config = loadConfigFiles(configFiles, ['a']); + + expect(config).toEqual({ + flags: { + gulpfile: path.join(fixturesDir, + 'flags/gulpfile/is/here/mygulpfile.js'), + }, + }); + done(); + }); + +});