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.

298 lines
7.1 KiB

2 years ago
/*
* 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;