Comparing version 3.2.3 to 4.0.0
@@ -8,3 +8,2 @@ "use strict"; | ||
var Task = require("../lib/task"); | ||
var runner = require("../lib/runner"); | ||
var log = require("../lib/log"); | ||
@@ -54,4 +53,3 @@ | ||
env: env, | ||
argv: opts.argv, | ||
runner: runner | ||
argv: opts.argv | ||
}); | ||
@@ -58,0 +56,0 @@ |
@@ -19,3 +19,3 @@ #!/usr/bin/env node | ||
var builderPath = require.resolve("./builder-core"); | ||
var localPath = path.resolve(process.cwd(), "node_modules/builder/bin/builder-core.js"); | ||
var localPath = path.resolve("node_modules/builder/bin/builder-core.js"); | ||
@@ -32,3 +32,3 @@ // Swap to local path if different. | ||
msgs.push({ | ||
level: "warn", type: "local-detect", | ||
level: "info", type: "local-detect", | ||
msg: "Error importing local builder: " + err.message | ||
@@ -35,0 +35,0 @@ }); |
History | ||
======= | ||
## 4.0.0 | ||
**BREAKING**: | ||
* Restrict to `node` version `4+`. | ||
* Default to `--log-level=error` intead of `info`. | ||
* Add `pre|post` lifecycle commands. | ||
[#68](https://github.com/FormidableLabs/builder/issues/68) | ||
* Error out on invalid or conflicting command line flags passed | ||
to `builder`. | ||
Other enhancements: | ||
* Add `--log-level=debug`. Switch some existing logs over. | ||
* Add more explicit behavior and tests for `--setup` flag. | ||
## 3.2.3 | ||
@@ -5,0 +21,0 @@ |
"use strict"; | ||
/*eslint max-statements:[2, 20]*/ | ||
/*eslint max-statements:[2, 30]*/ | ||
@@ -72,5 +72,5 @@ /** | ||
"log-level": { | ||
desc: "Level to log at (`info`, `warn`, `error`, `none`)", | ||
desc: "Level to log at (`debug`, `info`, `warn`, `error`, `none`)", | ||
types: [String], | ||
default: "info" | ||
default: "error" | ||
}, | ||
@@ -119,2 +119,9 @@ env: { | ||
// Get all possible flag fields. | ||
var FLAGS_ALL_FIELDS = _.chain(FLAGS) // Use `_.chain` not `_()` because of `reduce` | ||
.values() | ||
.reduce(function (memo, obj) { return memo.concat(_.keys(obj)); }, []) | ||
.uniq() | ||
.value(); | ||
// Convert our bespoke flags object into `nopt` options. | ||
@@ -151,7 +158,9 @@ var getOpts = function (obj) { | ||
// Option parser. | ||
var createFn = function (flags) { | ||
var createFn = function (flags, isGeneral) { | ||
var opts = getOpts(flags); | ||
var flagKeys = _.keys(flags); | ||
return function (argv) { | ||
argv = argv || process.argv; | ||
var unparsedArgv = argv; | ||
@@ -173,2 +182,5 @@ // Capture any flags after `--` like `npm run <task> -- <args>` does. | ||
// Hack in unparsed for pristine version. | ||
parsedOpts.argv.unparsed = unparsedArgv; | ||
// Stash if log-level was actually set. | ||
@@ -203,2 +215,15 @@ var logLevel = parsedOpts["log-level"]; | ||
// Validate no invalid or ambiguous flags. | ||
var parsedKeys = _(parsedOpts).keys().without("argv").value(); | ||
var extraKeys = _.difference(parsedKeys, flagKeys); | ||
if (isGeneral) { | ||
// If the general command, allow flags from *any* other action. | ||
// We'll eventually hit the right action with tighter enforcement. | ||
extraKeys = _.difference(extraKeys, FLAGS_ALL_FIELDS); | ||
} | ||
if (extraKeys.length) { | ||
throw new Error("Found invalid/conflicting keys: " + extraKeys.join(", ")); | ||
} | ||
return _(parsedOpts) | ||
@@ -217,5 +242,9 @@ // Camel-case flags. | ||
version: version | ||
}, _.mapValues(FLAGS, function (flags) { | ||
}, _.mapValues(FLAGS, function (flags, key) { | ||
// Merge in general flags to all other configs. | ||
var isGeneral = key === "general"; | ||
flags = isGeneral ? flags : _.extend({}, FLAGS.general, flags); | ||
// Add in `KEY()` methods. | ||
return createFn(flags); | ||
return createFn(flags, isGeneral); | ||
})); |
@@ -27,3 +27,3 @@ "use strict"; | ||
var Config = module.exports = function (opts) { | ||
log.info("config:environment", JSON.stringify({ | ||
log.debug("config:environment", JSON.stringify({ | ||
cwd: process.cwd(), | ||
@@ -126,3 +126,3 @@ dir: __dirname | ||
} catch (err) { | ||
log.error("config:load-archetype-scripts", | ||
log.info("config:load-archetype-scripts", | ||
"Error loading package.json for: " + chalk.gray(name) + " " + | ||
@@ -149,3 +149,3 @@ (err.message || err.toString())); | ||
var self = this; | ||
var CWD_PKG = this._lazyRequire(path.join(process.cwd(), "package.json")) || {}; | ||
var CWD_PKG = this._lazyRequire(path.resolve("package.json")) || {}; | ||
@@ -152,0 +152,0 @@ // Load base packages. |
@@ -28,4 +28,4 @@ "use strict"; | ||
// Node directories. | ||
var CWD_BIN = path.join(process.cwd(), "node_modules/.bin"); | ||
var CWD_NODE_PATH = path.join(process.cwd(), "node_modules"); | ||
var CWD_BIN = path.resolve("node_modules/.bin"); | ||
var CWD_NODE_PATH = path.resolve("node_modules"); | ||
@@ -39,2 +39,3 @@ /** | ||
* @param {Object} opts.config Configuration object | ||
* @param {Object} opts.argv Argv object (Default `process.argv`) | ||
* @param {Object} opts.env Environment object to mutate (Default `process.env`) | ||
@@ -41,0 +42,0 @@ * @returns {void} |
@@ -14,6 +14,7 @@ "use strict"; | ||
var LEVELS = { | ||
info: 0, | ||
warn: 1, | ||
error: 2, | ||
none: 3 | ||
debug: 0, | ||
info: 1, | ||
warn: 2, | ||
error: 3, | ||
none: 4 | ||
}; | ||
@@ -104,2 +105,6 @@ | ||
// Switch to `console.log` friendly methods. | ||
// - `console.debug` won't normally output to stdout. | ||
level = level === "debug" ? "log" : level; | ||
// Call directly once level is set. | ||
@@ -113,4 +118,5 @@ // This is also used to drain the queue. | ||
// Actual implementation methods. | ||
log.debug = log._wrapper.bind(log, "debug", "gray"); | ||
log.info = log._wrapper.bind(log, "info", "green"); | ||
log.warn = log._wrapper.bind(log, "warn", "yellow"); | ||
log.error = log._wrapper.bind(log, "error", "red"); |
434
lib/task.js
"use strict"; | ||
/*eslint max-statements:[2, 30]*/ | ||
var path = require("path"); | ||
var _ = require("lodash"); | ||
var async = require("async"); | ||
var chalk = require("chalk"); | ||
@@ -9,4 +11,11 @@ var args = require("./args"); | ||
var log = require("./log"); | ||
var clone = require("./utils/clone"); | ||
var jsonParse = require("./utils/json").parse; | ||
var Tracker = require("./utils/tracker"); | ||
var runner = require("./utils/runner"); | ||
// Paths for inference of "is builder task?" | ||
var BUILDER_NODE_MODULES = path.normalize("node_modules/builder/bin/builder.js"); | ||
var BUILDER_BIN = path.normalize("node_modules/.bin/builder"); | ||
/** | ||
@@ -28,3 +37,2 @@ * Task wrapper. | ||
this._env = opts.env || new Environment(); | ||
this._runner = opts.runner; | ||
@@ -40,7 +48,10 @@ // Infer parts. | ||
// State | ||
this._tracker = new Tracker(); | ||
this._setupKilled = false; | ||
this._errors = []; | ||
// Validation. | ||
if (!this._config) { | ||
throw new Error("Configuration object required"); | ||
} else if (!this._runner) { | ||
throw new Error("Runner object required"); | ||
} | ||
@@ -80,2 +91,4 @@ | ||
* | ||
* Checks either `builder` or `node PATH/TO/BUILDER`. | ||
* | ||
* _Note_: Naive. Only captures `builder` at start of command. | ||
@@ -91,7 +104,22 @@ * See: https://github.com/FormidableLabs/builder/issues/93 | ||
var builder = path.basename(this._script, ".js"); | ||
var taskParts = task.split(/\s+/); | ||
var taskBin = taskParts[0]; | ||
var taskParts = (task || "").trim().split(/\s+/); | ||
var taskBin = taskParts[0] || ""; | ||
// Note: Assumes a binary script match without `.js` extension. | ||
return builder === taskBin; | ||
// Case: `builder run foo` | ||
// Compare directly if not a `node` script execution. | ||
if (taskBin.toLowerCase() !== "node") { | ||
return taskBin === builder; | ||
} | ||
// We know we're now in `node PATH/TO/BUILDER`. | ||
taskBin = path.resolve(taskParts[1] || ""); | ||
// Case: `node CURRENT_BUILDER_PATH` | ||
return taskBin === path.resolve(this._script) || | ||
// Case: `node node_modules/builder/bin/builder.js` | ||
taskBin.substr(taskBin.length - BUILDER_NODE_MODULES.length) === BUILDER_NODE_MODULES || | ||
// Case: `node node_modules/.bin/builder` | ||
taskBin.substr(taskBin.length - BUILDER_BIN.length) === BUILDER_BIN; | ||
}; | ||
@@ -115,22 +143,58 @@ | ||
/** | ||
* Get executable command. | ||
* | ||
* @param {String} cmd Script command | ||
* @returns {Object} Command object `{ archetype, cmd }` | ||
*/ | ||
Task.prototype.getCommand = function (cmd) { | ||
Task.prototype._findCommand = function (cmd) { | ||
var self = this; | ||
// Select first non-passthrough command. | ||
var task = _.find(this._config.getCommands(cmd), function (obj) { | ||
return _.find(this._config.getCommands(cmd), function (obj) { | ||
return !self.isPassthrough(obj.cmd); | ||
}); | ||
}; | ||
// Error out if still can't find task. | ||
if (!task) { | ||
/** | ||
* Get execution parameters, options, etc. | ||
* | ||
* @param {String} cmd Script command | ||
* @param {String} action Action name (default `general`) | ||
* @returns {Object} Commands obj `{ main: { archetype, cmd, opts }, pre, post }` | ||
*/ | ||
Task.prototype.getRunParams = function (cmd, action) { | ||
// For external use: | ||
cmd = cmd || this._command; | ||
action = action || "general"; | ||
// Require a `main` task. | ||
var main = this._findCommand(cmd); | ||
if (!main) { | ||
if (_.isUndefined(cmd)) { | ||
throw new Error("No task specified"); | ||
} | ||
throw new Error("Unable to find task for: " + cmd); | ||
} | ||
return task; | ||
// Gather pre|post tasks, if they aren't already pre|post-prefixed. | ||
// **Note**: This follows `npm` behavior. `yarn` would run `prepre<task>` | ||
// when executing `yarn run pre<task>`. | ||
var pre = null; | ||
var post = null; | ||
if (!/^(pre|post)/.test(cmd)) { | ||
pre = this._findCommand("pre" + cmd) || null; | ||
post = this._findCommand("post" + cmd) || null; | ||
} | ||
// Infer execution options and enhance objects. | ||
var taskArgs = args[action](this.argv); | ||
if (pre) { | ||
_.extend(pre, { opts: this.getPrePostOpts(pre, taskArgs, action) }); | ||
} | ||
_.extend(main, { opts: this.getMainOpts(main, taskArgs) }); | ||
if (post) { | ||
_.extend(post, { opts: this.getPrePostOpts(post, taskArgs, action) }); | ||
} | ||
return { | ||
pre: pre, | ||
main: main, | ||
post: post | ||
}; | ||
}; | ||
@@ -145,3 +209,3 @@ | ||
*/ | ||
Task.prototype.getOpts = function (task, opts) { | ||
Task.prototype.getMainOpts = function (task, opts) { | ||
return _.extend({ | ||
@@ -155,2 +219,84 @@ _isBuilderTask: this.isBuilderTask(task.cmd), | ||
/** | ||
* Merge base options with custom options and filter out non-pre|post things. | ||
* | ||
* @param {Object} task Task object `{ archetype, cmd }` | ||
* @param {Object} opts Custom options | ||
* @param {String} action Action type to take. | ||
* @returns {Object} Combined options | ||
*/ | ||
Task.prototype.getPrePostOpts = function (task, opts, action) { | ||
if (!task) { return null; } | ||
// Action specific overrides. | ||
var overrides = { | ||
"envs": { | ||
buffer: false | ||
} | ||
}; | ||
// Generally applicable options. | ||
return _.extend({}, this.getMainOpts(task, opts), { | ||
tries: 1, | ||
setup: false, | ||
_customFlags: null | ||
}, overrides[action]); | ||
}; | ||
// Expose require because with mock-fs and node 0.10, 0.12, the `require` itself | ||
// is mocked. | ||
Task.prototype._lazyRequire = require; // eslint-disable-line global-require | ||
/** | ||
* | ||
* @param {String} taskName Setup task name | ||
* @param {Object} shOpts Shell options | ||
* @param {Function} callback Function `(err)` called on process end. | ||
* @returns {Object} Process object or `null` if no setup. | ||
*/ | ||
Task.prototype.setup = function (taskName, shOpts, callback) { | ||
// Lazy require because `task` is a dependency. | ||
var setup = this._lazyRequire("./utils/setup"); | ||
// Short-circuit empty task. | ||
if (!taskName) { return null; } | ||
// Add, invoke, and hook to final callback if setup dies early. | ||
var self = this; | ||
var proc = this._tracker.add(setup.create(taskName, shOpts)); | ||
if (proc) { | ||
// If setup exit happens before normal termination, kill everything. | ||
proc.on("exit", function (code) { | ||
self._setupKilled = true; | ||
callback(new Error("Setup exited with code: " + code)); | ||
}); | ||
} | ||
return proc; | ||
}; | ||
Task.prototype.trackAndRun = function () { | ||
return this._tracker.add(runner.run.apply(null, arguments)); | ||
}; | ||
// Wrap all callbacks to noop if the tracker or setup are already killed. | ||
Task.prototype._skipIfKilled = function (fn) { | ||
var self = this; | ||
return function (cb) { | ||
return self._tracker.killed || self._setupKilled ? cb() : fn(cb); | ||
}; | ||
}; | ||
// Convenience wrapper for processing errors with `opts.bail`. | ||
Task.prototype._errOrBail = function (opts, callback) { | ||
var self = this; | ||
return function (err) { | ||
if (err) { | ||
self._errors.push(err); | ||
} | ||
callback(opts.bail ? err : null); // Decide if failure ends the run. | ||
}; | ||
}; | ||
/** | ||
* Help. | ||
@@ -229,10 +375,36 @@ * | ||
var env = this._env.env; // Raw environment object. | ||
var task = this.getCommand(this._command); | ||
var flags = args.run(this.argv); | ||
var opts = this.getOpts(task, flags); | ||
// Ensure only one call, so `--setup` process can call early for error. | ||
callback = _.once(callback); | ||
log.info(this._action, this._command + chalk.gray(" - " + task.cmd)); | ||
// Aliases | ||
var self = this; | ||
var shOpts = { env: this._env.env }; | ||
return this._runner.run(task.cmd, { env: env }, opts, callback); | ||
// Get execution parameters. | ||
var params = this.getRunParams(this._command, "run"); | ||
log.info(this._action, this._command + chalk.gray(" - " + params.main.cmd)); | ||
// Task execution sequence | ||
async.series([ | ||
// Execute `pre<task>` | ||
params.pre && self.trackAndRun.bind(self, params.pre.cmd, shOpts, params.pre.opts), | ||
// Execute `--setup=<setup-task>` (spawned for lifetime of <task>) | ||
function (cb) { | ||
self.setup(params.main.opts.setup, shOpts, callback); | ||
cb(); | ||
}, | ||
// Execute `<task>` | ||
function (cb) { | ||
var opts = _.extend({ tracker: self._tracker }, params.main.opts); | ||
return opts.tries === 1 ? | ||
self.trackAndRun(params.main.cmd, shOpts, opts, cb) : | ||
runner.retry(params.main.cmd, shOpts, opts, cb); | ||
}, | ||
// Execute `post<task>` | ||
params.post && self.trackAndRun.bind(self, params.post.cmd, shOpts, params.post.opts) | ||
].filter(Boolean).map(self._skipIfKilled.bind(self)), callback); | ||
}; | ||
@@ -247,39 +419,69 @@ | ||
Task.prototype.concurrent = function (callback) { | ||
var env = this._env.env; // Raw environment object. | ||
// Ensure only one call, so `--setup` process can call early for error. | ||
callback = _.once(callback); | ||
// Aliases | ||
var self = this; | ||
var shOpts = { env: this._env.env }; | ||
// Get execution parameters (list). | ||
var cmds = this._commands; | ||
var tasks = cmds.map(this.getCommand.bind(this)); | ||
var flags = args.concurrent(this.argv); | ||
var opts = this.getOpts(tasks[0] || {}, flags); | ||
log.info(this._action, cmds.join(", ") + tasks.map(function (t, i) { | ||
return "\n * " + cmds[i] + chalk.gray(" - " + t.cmd); | ||
var paramsList = cmds.map(function (cmd) { | ||
return self.getRunParams(cmd, "concurrent"); | ||
}); | ||
var firstParams = paramsList[0]; | ||
if (!firstParams) { | ||
log.warn(this._action, "No tasks found"); | ||
return callback(); | ||
} | ||
log.info(this._action, cmds.join(", ") + paramsList.map(function (t, i) { | ||
return "\n * " + cmds[i] + chalk.gray(" - " + t.main.cmd); | ||
}).join("")); | ||
this._runner.concurrent( | ||
tasks.map(function (task) { return task.cmd; }), | ||
{ env: env }, opts, callback); | ||
// Use first task options to slice off general concurrent information. | ||
var queue = firstParams.main.opts.queue; | ||
// Start setup on first of **any** main tasks. | ||
var startSetup = _.once(function (opts) { | ||
return opts.setup ? self.setup(opts.setup, shOpts, callback) : null; | ||
}); | ||
log.debug("concurrent", "Starting with queue size: " + chalk.magenta(queue || "unlimited")); | ||
async.mapLimit(paramsList, queue || Infinity, function (params, concCb) { | ||
// Task execution sequence | ||
async.series([ | ||
// Execute `pre<task>` | ||
params.pre && self.trackAndRun.bind(self, params.pre.cmd, shOpts, params.pre.opts), | ||
// (ONLY FIRST ONE) Execute `--setup=<setup-task>` (spawned for lifetime of <task>) | ||
function (cb) { | ||
startSetup(params.main.opts, shOpts, callback); | ||
cb(); | ||
}, | ||
// Execute `<task1>`, `<task2>`, `...` | ||
function (cb) { | ||
var opts = _.extend({ tracker: self._tracker }, params.main.opts); | ||
return opts.tries === 1 ? | ||
self.trackAndRun(params.main.cmd, shOpts, opts, cb) : | ||
runner.retry(params.main.cmd, shOpts, opts, cb); | ||
}, | ||
// Execute `post<task>` | ||
params.post && self.trackAndRun.bind(self, params.post.cmd, shOpts, params.post.opts) | ||
// Apply --bail to **all** of this sequence by wrapping .series final callback. | ||
].filter(Boolean).map(self._skipIfKilled.bind(self)), | ||
self._errOrBail(params.main.opts, concCb)); | ||
}, callback); | ||
}; | ||
/** | ||
* Run multiple environments. | ||
* | ||
* @param {Function} callback Callback function `(err)` | ||
* @returns {void} | ||
*/ | ||
Task.prototype.envs = function (callback) { | ||
/*eslint max-statements: [2, 20]*/ | ||
// Core setup. | ||
var env = this._env.env; | ||
var task = this.getCommand(this._command); | ||
var flags = args.envs(this.argv); | ||
var opts = this.getOpts(task, flags); | ||
// Return validated environment object from CLI or file. | ||
Task.prototype._getEnvsList = function (parseObj) { | ||
// Parse envs. | ||
var envsObj; | ||
var envsList; | ||
var err; | ||
try { | ||
envsObj = jsonParse({ | ||
str: this._commands[1], // Get task environment array. | ||
path: opts.envsPath | ||
}); | ||
envsList = jsonParse(parseObj); | ||
} catch (parseErr) { | ||
@@ -290,6 +492,6 @@ err = parseErr; | ||
// Validation | ||
if (!err && _.isEmpty(envsObj)) { | ||
if (!err && _.isEmpty(envsList)) { | ||
err = new Error("Empty/null JSON environments array."); | ||
} else if (!err && !_.isArray(envsObj)) { | ||
err = new Error("Non-array JSON environments object: " + JSON.stringify(envsObj)); | ||
} else if (!err && !_.isArray(envsList)) { | ||
err = new Error("Non-array JSON environments object: " + JSON.stringify(envsList)); | ||
} | ||
@@ -300,15 +502,103 @@ | ||
"Failed to load environments string / path with error: " + err); | ||
return callback(err); | ||
throw err; | ||
} | ||
// Stash for use in runner options. | ||
opts._envs = envsObj; | ||
return envsList; | ||
}; | ||
// Run. | ||
this._runner.envs(task.cmd, { env: env }, opts, callback); | ||
/** | ||
* Run multiple environments. | ||
* | ||
* @param {Function} callback Callback function `(err)` | ||
* @returns {void} | ||
*/ | ||
Task.prototype.envs = function (callback) { | ||
// Ensure only one call, so `--setup` process can call early for error. | ||
callback = _.once(callback); | ||
// Aliases | ||
var self = this; | ||
var shOpts = { env: this._env.env }; | ||
// Get execution parameters. | ||
var params = this.getRunParams(this._command, "envs"); | ||
log.info(this._action, this._command + chalk.gray(" - " + params.main.cmd)); | ||
// Get list of environment variable objects. | ||
var envsList; | ||
try { | ||
envsList = this._getEnvsList({ | ||
str: this._commands[1], // from CLI arguments | ||
path: params.main.opts.envsPath // from `--envs-path` | ||
}); | ||
} catch (err) { | ||
return void callback(err); | ||
} | ||
// Task execution sequence | ||
async.series([ | ||
// Execute `pre<task>` | ||
params.pre && self.trackAndRun.bind(self, params.pre.cmd, shOpts, params.pre.opts), | ||
// Execute `--setup=<setup-task>` (spawned for lifetime of <task>) | ||
function (cb) { | ||
self.setup(params.main.opts.setup, shOpts, callback); | ||
cb(); | ||
}, | ||
// Execute `<task>` w/ each env var. | ||
function (cb) { | ||
var cmd = params.main.cmd; | ||
var cmdStr = runner.cmdStr; | ||
var opts = _.extend({ tracker: self._tracker }, params.main.opts); | ||
var taskEnvs = clone(envsList); | ||
var queue = opts.queue; | ||
log.debug("envs", "Starting with queue size: " + chalk.magenta(queue || "unlimited")); | ||
async.mapLimit(taskEnvs, queue || Infinity, function (taskEnv, envCb) { | ||
// Add each specific set of environment variables. | ||
// Clone `shOpts` to turn `env` into a plain object: in Node 4+ | ||
// `process.env` is a special object which changes merge behavior. | ||
var taskShOpts = _.merge(clone(shOpts), { env: taskEnv }); | ||
var taskOpts = _.extend({ taskEnv: taskEnv }, opts); | ||
log.info("envs", "Starting " + cmdStr(cmd, taskOpts)); | ||
runner.retry(cmd, taskShOpts, taskOpts, self._errOrBail(taskOpts, envCb)); | ||
}, cb); | ||
}, | ||
// Execute `post<task>` | ||
params.post && self.trackAndRun.bind(self, params.post.cmd, shOpts, params.post.opts) | ||
].filter(Boolean).map(self._skipIfKilled.bind(self)), callback); | ||
}; | ||
/** | ||
* Execute task or tasks. | ||
* Clean up task state. | ||
* | ||
* - Terminate all existing processes. | ||
* - Drain and log all accumulated errors. | ||
* | ||
* @param {Function} callback Callback `(err)` | ||
* @returns {Function} Wrapped callback | ||
*/ | ||
Task.prototype.finish = function (callback) { | ||
var errors = this._errors; | ||
this._tracker.kill(function () { | ||
if (errors.length > 1) { | ||
log.error("finish", "Hit " + chalk.red(errors.length) + " errors: \n" + | ||
errors.map(function (errItem) { | ||
return " * " + chalk.gray(errItem.name) + ": " + chalk.red(errItem.message); | ||
}).join("\n")); | ||
} | ||
callback(errors[0]); | ||
}); | ||
}; | ||
/** | ||
* Execute action and cleanup. | ||
* | ||
* **Note**: All tasks (e.g., `run`, `concurrent`) through this gate. | ||
* | ||
* @param {Function} callback Callback function `(err)` | ||
@@ -318,10 +608,20 @@ * @returns {Object} Process object for `run` or `null` otherwise / for errors | ||
Task.prototype.execute = function (callback) { | ||
var self = this; | ||
var action = this._action; | ||
// Check task action method exists. | ||
if (!this[this._action]) { | ||
callback(new Error("Unrecognized action: " + this._action)); | ||
return null; | ||
if (!this[action]) { | ||
return void callback(new Error("Unrecognized action: " + action)); | ||
} | ||
// Call action. | ||
return this[this._action](callback); | ||
this[action](function (err) { | ||
// Add errors, if any. | ||
if (err) { | ||
self._errors.push(err); | ||
} | ||
// Unconditionally cleanup. | ||
self.finish(callback); | ||
}); | ||
}; |
@@ -6,2 +6,9 @@ "use strict"; | ||
// Ignore errors: We want to kill as many procs as we can. | ||
var ignoreError = function (cb) { | ||
return cb ? | ||
function () { cb(); } : | ||
function () {}; // noop | ||
}; | ||
/** | ||
@@ -14,2 +21,3 @@ * Multi-process tracker. | ||
this.procs = []; | ||
this.killed = false; | ||
}; | ||
@@ -28,2 +36,8 @@ | ||
// Short-circuit and kill without async wait if killed. | ||
if (self.killed) { | ||
treeKill(proc.pid, "SIGTERM", ignoreError()); | ||
return proc; | ||
} | ||
// Track. | ||
@@ -49,9 +63,8 @@ self.procs.push(proc); | ||
Tracker.prototype.kill = function (callback) { | ||
this.killed = true; | ||
if (this.procs.length === 0) { return callback(); } | ||
async.map(this.procs, function (proc, cb) { | ||
// Ignore errors: We want to kill as many procs as we can. | ||
treeKill(proc.pid, "SIGTERM", function () { cb(); }); | ||
treeKill(proc.pid, "SIGTERM", ignoreError(cb)); | ||
}, callback); | ||
}; |
The MIT License (MIT) | ||
Copyright (c) 2015-2016 Formidable Labs | ||
Copyright (c) 2015-2018 Formidable Labs | ||
@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
{ | ||
"name": "builder", | ||
"version": "3.2.3", | ||
"version": "4.0.0", | ||
"description": "An NPM-based task runner", | ||
@@ -15,2 +15,5 @@ "repository": { | ||
"homepage": "https://github.com/FormidableLabs/builder", | ||
"engines": { | ||
"node": ">=4" | ||
}, | ||
"scripts": { | ||
@@ -24,4 +27,5 @@ "builder:build-md-toc": "doctoc --notitle README.md", | ||
"builder:test-cov": "istanbul cover --config .istanbul.server.yml _mocha -- --opts test/server/mocha.opts test/server/spec", | ||
"builder:check": "npm run builder:lint && npm run builder:test", | ||
"builder:check-ci": "npm run builder:lint && npm run builder:test-cov" | ||
"builder:test-func": "mocha --opts test/func/mocha.opts test/func/spec", | ||
"builder:check": "npm run builder:lint && npm run builder:test && npm run builder:test-func", | ||
"builder:check-ci": "npm run builder:lint && npm run builder:test-cov && npm run builder:test-func" | ||
}, | ||
@@ -36,3 +40,3 @@ "bin": { | ||
"lodash": "^3.10.1", | ||
"nopt": "^3.0.6", | ||
"nopt": "^4.0.1", | ||
"tree-kill": "^1.0.0" | ||
@@ -39,0 +43,0 @@ }, |
203
README.md
@@ -62,2 +62,8 @@ [![Travis Status][trav_img]][trav_site] | ||
- [builder envs](#builder-envs) | ||
- [Setup Task](#setup-task) | ||
- [Task Lifecycle](#task-lifecycle) | ||
- [The Basics](#the-basics) | ||
- [Other Builder Actions](#other-builder-actions) | ||
- [Builder Flags During Pre and Post](#builder-flags-during-pre-and-post) | ||
- [Task Prefix Complexities](#task-prefix-complexities) | ||
- [Custom Flags](#custom-flags) | ||
@@ -281,3 +287,3 @@ - [Expanding the Archetype Path](#expanding-the-archetype-path) | ||
* `--quiet`: Silence logging | ||
* `--log-level`: Level to log at (`info`, `warn`, `error`, `none`) | ||
* `--log-level`: Level to log at (`debug`, `info`, `warn`, `error`, `none`) (default: `error`) | ||
* `--env`: JSON object of keys to add to environment. | ||
@@ -302,3 +308,6 @@ * `--env-path`: JSON file path of keys to add to environment. | ||
* `--tries`: Number of times to attempt a task (default: `1`) | ||
* `--setup`: Single task to run for the entirety of `<action>` | ||
* `--setup`: Single task to run for the entirety of `<action>`. | ||
* **Note**: The `--setup` task is run at the start of the first main task | ||
to actually run. This may _not_ be the first specified task however, | ||
as `pre` tasks could end up with main tasks starting out of order. | ||
* `--queue`: Number of concurrent processes to run (default: unlimited - `0|null`) | ||
@@ -308,3 +317,3 @@ * `--[no-]buffer`: Buffer output until process end (default: `false`) | ||
* `--quiet`: Silence logging | ||
* `--log-level`: Level to log at (`info`, `warn`, `error`, `none`) | ||
* `--log-level`: Level to log at (`debug`, `info`, `warn`, `error`, `none`) (default: `error`) | ||
* `--env`: JSON object of keys to add to environment. | ||
@@ -357,2 +366,6 @@ * `--env-path`: JSON file path of keys to add to environment. | ||
* `--setup`: Single task to run for the entirety of `<action>` | ||
* **Note**: The `--setup` task is run at the start of the first main task | ||
to actually run. This may _not_ be the first specified task however, | ||
as `pre` tasks could end up with main tasks per environment object | ||
starting out of order. | ||
* `--queue`: Number of concurrent processes to run (default: unlimited - `0|null`) | ||
@@ -363,3 +376,3 @@ * `--[no-]buffer`: Buffer output until process end (default: `false`) | ||
* `--quiet`: Silence logging | ||
* `--log-level`: Level to log at (`info`, `warn`, `error`, `none`) | ||
* `--log-level`: Level to log at (`debug`, `info`, `warn`, `error`, `none`) (default: `error`) | ||
* `--env`: JSON object of keys to add to environment. | ||
@@ -384,2 +397,184 @@ * `--env-path`: JSON file path of keys to add to environment. | ||
#### Setup Task | ||
A task specified in `--setup <task>` will have the following flags apply to | ||
the setup task as apply to the main task: | ||
* `--env` | ||
* `--env-path` | ||
* `--quiet` | ||
* `--log-level` | ||
The following flags do _not_ apply to a setup task: | ||
* `--` custom flags | ||
* `--tries` | ||
* `--expand-archetype` | ||
* `--queue` | ||
* `--buffer` | ||
That said, if you need things like `--tries`, etc., these can be always coded | ||
into a wrapped task like: | ||
```js | ||
"scripts": { | ||
"setup-alone": "while sleep 1; do echo SETUP; done", | ||
"setup": "builder run --tries=5 setup-alone", | ||
"test": "mocha", | ||
"test-full": "builder run --setup=setup test" | ||
} | ||
``` | ||
#### Task Lifecycle | ||
Builder executes `pre<task>` and `post<task>` tasks the same as `npm` does, | ||
with some perhaps not completely obvious corner cases. | ||
##### The Basics | ||
If you have: | ||
```js | ||
"scripts": { | ||
"prefoo": "echo PRE", | ||
"foo": "echo TEMP", | ||
"postfoo": "echo POST" | ||
} | ||
``` | ||
And run `builder run foo`, then just like `npm`, builder will run in order: | ||
1. `prefoo` | ||
2. `foo` | ||
3. `postfoo` | ||
assuming each task succeeds, otherwise execution is terminated. | ||
`pre` and `post` tasks can be provided in an archetype and overridden in a root | ||
`package.json` in the exact same manner as normal Builder tasks. | ||
##### Other Builder Actions | ||
`builder run` works essentially the same as `npm run`. Things get a little | ||
messy with Builder's other execution options: | ||
`builder envs` runs `pre|post` tasks exactly **once** regardless of how many | ||
concurrent executions of the underlying task (with different environment | ||
variables) occur. | ||
`builder concurrent` runs appropriate `pre|post` tasks for each independent | ||
task. So, for something like: | ||
```js | ||
"scripts": { | ||
"prefoo": "echo PRE FOO", | ||
"foo": "echo TEMP FOO", | ||
"postfoo": "echo POST FOO", | ||
"prebar": "echo PRE BAR", | ||
"bar": "echo TEMP BAR", | ||
"postbar": "echo POST BAR" | ||
} | ||
``` | ||
running `builder concurrent foo bar` would run **all** of the above tasks at | ||
the appropriate lifecycle moment. | ||
Note that things like a `--queue=NUM` limit on a concurrent task will have | ||
*all* of the `pre`, main, and `post` task need to finish serial execution before | ||
the next spot is freed up. | ||
The `--bail` flag applies to all of a single tasks `pre`, main, and `post` | ||
group. So if any of those fail, it's as if the main task failed. | ||
##### Builder Flags During Pre and Post | ||
*Applicable Flags* | ||
When executing a `<task>` that has `pre<task>` and/or `post<task>` entries, the | ||
following execution flags **do** apply to the `pre|post` tasks. | ||
* `--env` | ||
* `--env-path` | ||
* `--quiet` | ||
* `--log-level` | ||
* `--expand-archetype` | ||
These flags have mixed application: | ||
* `--queue`: Applies for `concurrent`, but not `envs`. The flag is invalid for | ||
`run`. | ||
* `--buffer`: Applies for `concurrent`, but not `envs`. The flag is invalid for | ||
`run`. | ||
* `--bail`: Applies for `concurrent`, but not `envs`. The flag is invalid for | ||
`run`. A `pre<task>`, `<task>`, and a `post<task>` are treated as a group, so | ||
a failure of any short-circuits the rests and ends with failures. But with | ||
`--bail=false` a failure doesn't stop execution of the _other_ groups. | ||
The following flags do _not_ apply to pre/post tasks: | ||
* `--` custom flags | ||
* `--tries` | ||
* `--setup`: A task specified in `--setup <task>` will not have `pre|post` | ||
tasks apply. | ||
We will explain a few of these situations in a bit more depth: | ||
*Custom Flags* | ||
The special `--` flag with any subsequent custom flags to the underlying task | ||
are only passed to the the main `<task>` and not `pre<task>` or `post<task>`. | ||
The rationale here is that custom command line flags most likely just apply to | ||
a single shell command (the main one). | ||
So, for example | ||
```js | ||
"scripts": { | ||
"prefoo": "echo PRE", | ||
"foo": "echo TEMP", | ||
"postfoo": "echo POST" | ||
} | ||
``` | ||
running `builder run foo -- --hi` would produce: | ||
``` | ||
PRE | ||
TEMP --hi | ||
POST | ||
``` | ||
*Other Flags* | ||
By contrast, the various other Builder-specific flags that can be applied to a | ||
task like `--env`, etc., **will** apply to `pre|post` tasks, under the | ||
assumption that control flags + environment variables will most likely want to | ||
be used for the execution of all commands in the workflow. | ||
So, for example: | ||
```js | ||
"scripts": { | ||
"prefoo": "echo PRE $VAR", | ||
"foo": "echo TEMP $VAR", | ||
"postfoo": "echo POST $VAR" | ||
} | ||
``` | ||
running `builder run foo --env '{"VAR":"HI"}'` would produce: | ||
``` | ||
PRE HI | ||
TEMP HI | ||
POST HI | ||
``` | ||
##### Task Prefix Complexities | ||
For the above example, if you have a task named `preprefoo`, then running | ||
`foo` **or** even `prefoo` directly will **not** run `preprefoo`. Builder | ||
follows `npm`'s current implementation which is roughly "add `pre|post` tasks | ||
to current execution as long as the task itself is not prefixed with | ||
`pre|post`". (_Note_ that `yarn` does not follow this logic in task execution). | ||
#### Custom Flags | ||
@@ -386,0 +581,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
128156
19
1875
1673
13
+ Addednopt@4.0.3(transitive)
+ Addedos-homedir@1.0.2(transitive)
+ Addedos-tmpdir@1.0.2(transitive)
+ Addedosenv@0.1.5(transitive)
- Removednopt@3.0.6(transitive)
Updatednopt@^4.0.1