You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
7.1 KiB
297 lines
7.1 KiB
/* |
|
* Jake JavaScript build tool |
|
* Copyright 2112 Matthew Eernisse (mde@fleegix.org) |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
* |
|
*/ |
|
|
|
|
|
let util = require('util'); // Native Node util module |
|
let spawn = require('child_process').spawn; |
|
let EventEmitter = require('events').EventEmitter; |
|
let logger = require('./logger'); |
|
let file = require('./file'); |
|
let Exec; |
|
|
|
const _UUID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); |
|
|
|
let parseArgs = function (argumentsObj) { |
|
let args; |
|
let arg; |
|
let cmds; |
|
let callback; |
|
let opts = { |
|
interactive: false, |
|
printStdout: false, |
|
printStderr: false, |
|
breakOnError: true |
|
}; |
|
|
|
args = Array.prototype.slice.call(argumentsObj); |
|
|
|
cmds = args.shift(); |
|
// Arrayize if passed a single string command |
|
if (typeof cmds == 'string') { |
|
cmds = [cmds]; |
|
} |
|
// Make a copy if it's an actual list |
|
else { |
|
cmds = cmds.slice(); |
|
} |
|
|
|
// Get optional callback or opts |
|
while((arg = args.shift())) { |
|
if (typeof arg == 'function') { |
|
callback = arg; |
|
} |
|
else if (typeof arg == 'object') { |
|
opts = Object.assign(opts, arg); |
|
} |
|
} |
|
|
|
// Backward-compat shim |
|
if (typeof opts.stdout != 'undefined') { |
|
opts.printStdout = opts.stdout; |
|
delete opts.stdout; |
|
} |
|
if (typeof opts.stderr != 'undefined') { |
|
opts.printStderr = opts.stderr; |
|
delete opts.stderr; |
|
} |
|
|
|
return { |
|
cmds: cmds, |
|
opts: opts, |
|
callback: callback |
|
}; |
|
}; |
|
|
|
/** |
|
@name jake |
|
@namespace jake |
|
*/ |
|
let utils = new (function () { |
|
/** |
|
@name jake.exec |
|
@static |
|
@function |
|
@description Executes shell-commands asynchronously with an optional |
|
final callback. |
|
` |
|
@param {String[]} cmds The list of shell-commands to execute |
|
@param {Object} [opts] |
|
@param {Boolean} [opts.printStdout=false] Print stdout from each command |
|
@param {Boolean} [opts.printStderr=false] Print stderr from each command |
|
@param {Boolean} [opts.breakOnError=true] Stop further execution on |
|
the first error. |
|
@param {Boolean} [opts.windowsVerbatimArguments=false] Don't translate |
|
arguments on Windows. |
|
@param {Function} [callback] Callback to run after executing the |
|
commands |
|
|
|
@example |
|
let cmds = [ |
|
'echo "showing directories"' |
|
, 'ls -al | grep ^d' |
|
, 'echo "moving up a directory"' |
|
, 'cd ../' |
|
] |
|
, callback = function () { |
|
console.log('Finished running commands.'); |
|
} |
|
jake.exec(cmds, {stdout: true}, callback); |
|
*/ |
|
this.exec = function (a, b, c) { |
|
let parsed = parseArgs(arguments); |
|
let cmds = parsed.cmds; |
|
let opts = parsed.opts; |
|
let callback = parsed.callback; |
|
|
|
let ex = new Exec(cmds, opts, callback); |
|
|
|
ex.addListener('error', function (msg, code) { |
|
if (opts.breakOnError) { |
|
fail(msg, code); |
|
} |
|
}); |
|
ex.run(); |
|
|
|
return ex; |
|
}; |
|
|
|
this.createExec = function (a, b, c) { |
|
return new Exec(a, b, c); |
|
}; |
|
|
|
// From Math.uuid.js, https://github.com/broofa/node-uuid |
|
// Robert Kieffer (robert@broofa.com), MIT license |
|
this.uuid = function (length, radix) { |
|
var chars = _UUID_CHARS |
|
, uuid = [] |
|
, r |
|
, i; |
|
|
|
radix = radix || chars.length; |
|
|
|
if (length) { |
|
// Compact form |
|
i = -1; |
|
while (++i < length) { |
|
uuid[i] = chars[0 | Math.random()*radix]; |
|
} |
|
} else { |
|
// rfc4122, version 4 form |
|
|
|
// rfc4122 requires these characters |
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; |
|
uuid[14] = '4'; |
|
|
|
// Fill in random data. At i==19 set the high bits of clock sequence as |
|
// per rfc4122, sec. 4.1.5 |
|
i = -1; |
|
while (++i < 36) { |
|
if (!uuid[i]) { |
|
r = 0 | Math.random()*16; |
|
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; |
|
} |
|
} |
|
} |
|
|
|
return uuid.join(''); |
|
}; |
|
|
|
})(); |
|
|
|
Exec = function () { |
|
let parsed = parseArgs(arguments); |
|
let cmds = parsed.cmds; |
|
let opts = parsed.opts; |
|
let callback = parsed.callback; |
|
|
|
this._cmds = cmds; |
|
this._callback = callback; |
|
this._config = opts; |
|
}; |
|
|
|
util.inherits(Exec, EventEmitter); |
|
|
|
Object.assign(Exec.prototype, new (function () { |
|
|
|
let _run = function () { |
|
let self = this; |
|
let sh; |
|
let cmd; |
|
let args; |
|
let next = this._cmds.shift(); |
|
let config = this._config; |
|
let errData = ''; |
|
let shStdio; |
|
let handleStdoutData = function (data) { |
|
self.emit('stdout', data); |
|
}; |
|
let handleStderrData = function (data) { |
|
let d = data.toString(); |
|
self.emit('stderr', data); |
|
// Accumulate the error-data so we can use it as the |
|
// stack if the process exits with an error |
|
errData += d; |
|
}; |
|
|
|
// Keep running as long as there are commands in the array |
|
if (next) { |
|
let spawnOpts = {}; |
|
this.emit('cmdStart', next); |
|
|
|
// Ganking part of Node's child_process.exec to get cmdline args parsed |
|
if (process.platform == 'win32') { |
|
cmd = 'cmd'; |
|
args = ['/c', next]; |
|
if (config.windowsVerbatimArguments) { |
|
spawnOpts.windowsVerbatimArguments = true; |
|
} |
|
} |
|
else { |
|
cmd = '/bin/sh'; |
|
args = ['-c', next]; |
|
} |
|
|
|
if (config.interactive) { |
|
spawnOpts.stdio = 'inherit'; |
|
sh = spawn(cmd, args, spawnOpts); |
|
} |
|
else { |
|
shStdio = [ |
|
process.stdin |
|
]; |
|
if (config.printStdout) { |
|
shStdio.push(process.stdout); |
|
} |
|
else { |
|
shStdio.push('pipe'); |
|
} |
|
if (config.printStderr) { |
|
shStdio.push(process.stderr); |
|
} |
|
else { |
|
shStdio.push('pipe'); |
|
} |
|
spawnOpts.stdio = shStdio; |
|
sh = spawn(cmd, args, spawnOpts); |
|
if (!config.printStdout) { |
|
sh.stdout.addListener('data', handleStdoutData); |
|
} |
|
if (!config.printStderr) { |
|
sh.stderr.addListener('data', handleStderrData); |
|
} |
|
} |
|
|
|
// Exit, handle err or run next |
|
sh.on('exit', function (code) { |
|
let msg; |
|
if (code !== 0) { |
|
msg = errData || 'Process exited with error.'; |
|
msg = msg.trim(); |
|
self.emit('error', msg, code); |
|
} |
|
if (code === 0 || !config.breakOnError) { |
|
self.emit('cmdEnd', next); |
|
setTimeout(function () { _run.call(self); }, 0); |
|
} |
|
}); |
|
|
|
} |
|
else { |
|
self.emit('end'); |
|
if (typeof self._callback == 'function') { |
|
self._callback(); |
|
} |
|
} |
|
}; |
|
|
|
this.append = function (cmd) { |
|
this._cmds.push(cmd); |
|
}; |
|
|
|
this.run = function () { |
|
_run.call(this); |
|
}; |
|
|
|
})()); |
|
|
|
utils.Exec = Exec; |
|
utils.file = file; |
|
utils.logger = logger; |
|
|
|
module.exports = utils; |
|
|
|
|