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.
135 lines
4.1 KiB
135 lines
4.1 KiB
'use strict';
|
|
|
|
const debug = require('debug')('runscript');
|
|
const is = require('is-type-of');
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const spawn = require('child_process').spawn;
|
|
|
|
/**
|
|
* Run shell script in child process
|
|
* Support OSX, Linux and Windows
|
|
* @param {String} script - full script string, like `git clone https://github.com/node-modules/runscript.git`
|
|
* @param {Object} [options] - spawn options
|
|
* @see https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
|
|
* @param {Object} [extraOptions] - extra options for running
|
|
* - {Number} [extraOptions.timeout] - child process running timeout
|
|
* @return {Object} stdio object, will contains stdio.stdout and stdio.stderr buffer.
|
|
*/
|
|
module.exports = function runScript(script, options, extraOptions) {
|
|
return new Promise((resolve, reject) => {
|
|
extraOptions = extraOptions || {};
|
|
options = options || {};
|
|
options.env = options.env || Object.create(process.env);
|
|
options.cwd = options.cwd || process.cwd();
|
|
options.stdio = options.stdio || 'inherit';
|
|
if (options.stdout) assert(is.writableStream(options.stdout), 'options.stdout should be writable stream');
|
|
if (options.stderr) assert(is.writableStream(options.stderr), 'options.stderr should be writable stream');
|
|
|
|
let sh = 'sh';
|
|
let shFlag = '-c';
|
|
|
|
if (process.platform === 'win32') {
|
|
sh = process.env.comspec || 'cmd';
|
|
shFlag = '/d /s /c';
|
|
options.windowsVerbatimArguments = true;
|
|
if (script.indexOf('./') === 0 || script.indexOf('.\\') === 0 ||
|
|
script.indexOf('../') === 0 || script.indexOf('..\\') === 0) {
|
|
const splits = script.split(' ');
|
|
splits[0] = path.join(options.cwd, splits[0]);
|
|
script = splits.join(' ');
|
|
}
|
|
}
|
|
|
|
debug('%s %s %s, %j, %j', sh, shFlag, script, options, extraOptions);
|
|
const proc = spawn(sh, [ shFlag, script ], options);
|
|
const stdout = [];
|
|
const stderr = [];
|
|
let isEnd = false;
|
|
let timeoutTimer;
|
|
|
|
if (proc.stdout) {
|
|
proc.stdout.on('data', buf => {
|
|
debug('stdout %d bytes', buf.length);
|
|
stdout.push(buf);
|
|
});
|
|
if (options.stdout) {
|
|
proc.stdout.pipe(options.stdout);
|
|
}
|
|
}
|
|
if (proc.stderr) {
|
|
proc.stderr.on('data', buf => {
|
|
debug('stderr %d bytes', buf.length);
|
|
stderr.push(buf);
|
|
});
|
|
if (options.stderr) {
|
|
proc.stderr.pipe(options.stderr);
|
|
}
|
|
}
|
|
|
|
proc.on('error', err => {
|
|
debug('proc emit error: %s', err);
|
|
if (isEnd) return;
|
|
isEnd = true;
|
|
clearTimeout(timeoutTimer);
|
|
|
|
reject(err);
|
|
});
|
|
|
|
proc.on('close', code => {
|
|
debug('proc emit close: %s', code);
|
|
if (isEnd) return;
|
|
isEnd = true;
|
|
clearTimeout(timeoutTimer);
|
|
|
|
const stdio = {
|
|
stdout: null,
|
|
stderr: null,
|
|
};
|
|
if (stdout.length > 0) {
|
|
stdio.stdout = Buffer.concat(stdout);
|
|
}
|
|
if (stderr.length > 0) {
|
|
stdio.stderr = Buffer.concat(stderr);
|
|
}
|
|
if (code !== 0) {
|
|
const err = new Error(`Run "${sh} ${shFlag} ${script}" error, exit code ${code}`);
|
|
err.name = 'RunScriptError';
|
|
err.stdio = stdio;
|
|
err.exitcode = code;
|
|
return reject(err);
|
|
}
|
|
return resolve(stdio);
|
|
});
|
|
|
|
proc.on('exit', code => {
|
|
debug('proc emit exit: %s', code);
|
|
});
|
|
|
|
if (typeof extraOptions.timeout === 'number' && extraOptions.timeout > 0) {
|
|
// start timer
|
|
timeoutTimer = setTimeout(() => {
|
|
debug('proc run timeout: %dms', extraOptions.timeout);
|
|
isEnd = true;
|
|
debug('kill child process %s', proc.pid);
|
|
proc.kill();
|
|
|
|
const err = new Error(`Run "${sh} ${shFlag} ${script}" timeout in ${extraOptions.timeout}ms`);
|
|
err.name = 'RunScriptTimeoutError';
|
|
const stdio = {
|
|
stdout: null,
|
|
stderr: null,
|
|
};
|
|
if (stdout.length > 0) {
|
|
stdio.stdout = Buffer.concat(stdout);
|
|
}
|
|
if (stderr.length > 0) {
|
|
stdio.stderr = Buffer.concat(stderr);
|
|
}
|
|
err.stdio = stdio;
|
|
return reject(err);
|
|
}, extraOptions.timeout);
|
|
}
|
|
});
|
|
};
|