mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
- options.cwd passed through convertToOSPath - launch processes on Windows using child_process.exec - parse args for windows into space-delimited string
167 lines
6.1 KiB
JavaScript
167 lines
6.1 KiB
JavaScript
import _ from 'underscore';
|
|
import child_process from 'child_process';
|
|
import files from '../fs/mini-files';
|
|
|
|
// The execFileSync function is meant to resemble the similarly-named Node 0.12
|
|
// synchronous process creation API, but instead of being fully blocking it
|
|
// uses a promise-based implementation. You can also use
|
|
// execFileAsync directly, which returns a promise.
|
|
// Some functionality is currently missing but could be added when the need
|
|
// arises (e.g. support for timeout, maxBuffer, and encoding options).
|
|
// Eventually, these versions should replace the ones in tools/utils/utils.js
|
|
// and tools/tool-testing/selftest.js.
|
|
|
|
/**
|
|
* @summary Executes a command synchronously, returning either the captured
|
|
* stdout output or throwing an error containing the stderr output as part of
|
|
* the message. In addition, the error will contain fields pid, stderr, stdout,
|
|
* status and signal.
|
|
* @param {String} command The command to run
|
|
* @param {Array} [args] List of string arguments
|
|
* @param {Object} [options]
|
|
* @param {Object} [options.cwd] Current working directory of the child process
|
|
* @param {Object} [options.env] Environment key-value pairs
|
|
* @param {Array|String} [options.stdio] Child's stdio configuration.
|
|
* (Default: 'pipe') Specifying anything else than 'pipe' will disallow
|
|
* capture.
|
|
* @param {Writable} [options.destination] If specified, instead of capturing
|
|
* the output, the child process stdout will be piped to the destination stream.
|
|
* @param {String} [options.waitForClose] Whether to wait for the child process
|
|
* streams to close or to resolve the promise when the child process exits.
|
|
* @returns {String} The stdout from the command
|
|
*/
|
|
export function execFileSync(command, args, options) {
|
|
return Promise.await(execFileAsync(command, args, options));
|
|
}
|
|
|
|
/**
|
|
* @summary Executes a command asynchronously, returning a promise that will
|
|
* either be resolved to the captured stdout output or be rejected with an
|
|
* error containing the stderr output as part of the message. In addition,
|
|
* the error will contain fields pid, stderr, stdout, status and signal.
|
|
* @param {String} command The command to run
|
|
* @param {Array} [args] List of string arguments
|
|
* @param {Object} [options]
|
|
* @param {Object} [options.cwd] Current working directory of the child process
|
|
* @param {Object} [options.env] Environment key-value pairs
|
|
* @param {Array|String} [options.stdio] Child's stdio configuration.
|
|
* (Default: 'pipe') Specifying anything else than 'pipe' will disallow
|
|
* capture.
|
|
* @param {Writable} [options.destination] If specified, instead of capturing
|
|
* the output, the child process stdout will be piped to the destination stream.
|
|
* @param {String} [options.waitForClose] Whether to wait for the child process
|
|
* streams to close or to resolve the promise when the child process exits.
|
|
* @returns {Promise<String>}
|
|
*/
|
|
export function execFileAsync(command, args,
|
|
options = { waitForClose: true }) {
|
|
// args is optional, so if it's not an array we interpret it as options
|
|
if (!Array.isArray(args)) {
|
|
options = _.extend(options, args);
|
|
args = [];
|
|
}
|
|
if (options.cwd) {
|
|
options.cwd = files.convertToOSPath(options.cwd);
|
|
}
|
|
// The child process close event is emitted when the stdio streams
|
|
// have all terminated. If those streams are shared with other
|
|
// processes, that means we won't receive a 'close' until all processes
|
|
// have exited, so we may want to respond to 'exit' instead.
|
|
// (The downside of responding to 'exit' is that the streams may not be
|
|
// fully flushed, so we could miss captured output. Only use this
|
|
// option when needed.)
|
|
const exitEvent = options.waitForClose ? 'close' : 'exit';
|
|
|
|
return new Promise((resolve, reject) => {
|
|
var child;
|
|
|
|
if (process.platform !== 'win32') {
|
|
child = child_process.spawn(command, args,
|
|
{ cwd, env, stdio } = options);
|
|
} else {
|
|
// https://github.com/nodejs/node-v0.x-archive/issues/2318
|
|
args.forEach(arg => {
|
|
command += ' ' + arg;
|
|
});
|
|
child = child_process.exec(command,
|
|
{ cwd, env, stdio } = options);
|
|
}
|
|
|
|
let capturedStdout = '';
|
|
if (child.stdout) {
|
|
if (options.destination) {
|
|
child.stdout.pipe(options.destination);
|
|
} else {
|
|
child.stdout.setEncoding('utf8');
|
|
child.stdout.on('data', (data) => {
|
|
capturedStdout += data;
|
|
});
|
|
}
|
|
}
|
|
|
|
let capturedStderr = '';
|
|
if (child.stderr) {
|
|
child.stderr.setEncoding('utf8');
|
|
child.stderr.on('data', (data) => {
|
|
capturedStderr += data;
|
|
});
|
|
}
|
|
|
|
const errorCallback = (error) => {
|
|
// Make sure we only receive one type of callback
|
|
child.removeListener(exitEvent, exitCallback);
|
|
|
|
// Trim captured output to get rid of excess whitespace
|
|
capturedStdout = capturedStdout.trim();
|
|
capturedStderr = capturedStderr.trim();
|
|
|
|
_.extend(error, {
|
|
pid: child.pid,
|
|
stdout: capturedStdout,
|
|
stderr: capturedStderr,
|
|
});
|
|
|
|
// Set a more informative error message on ENOENT, that includes the
|
|
// command we attempted to execute
|
|
if (error.code === 'ENOENT') {
|
|
error.message = `Could not find command '${command}'`;
|
|
}
|
|
|
|
reject(error);
|
|
};
|
|
child.on('error', errorCallback);
|
|
|
|
const exitCallback = (code, signal) => {
|
|
// Make sure we only receive one type of callback
|
|
child.removeListener('error', errorCallback);
|
|
|
|
// Trim captured output to get rid of excess whitespace
|
|
capturedStdout = capturedStdout.trim();
|
|
capturedStderr = capturedStderr.trim();
|
|
|
|
if (code === 0) {
|
|
resolve(capturedStdout);
|
|
} else {
|
|
let errorMessage = `Command failed: ${command}`;
|
|
if (args) {
|
|
errorMessage += ` ${args.join(' ')}`;
|
|
}
|
|
errorMessage += `\n${capturedStderr}`;
|
|
|
|
const error = new Error(errorMessage);
|
|
|
|
_.extend(error, {
|
|
pid: child.pid,
|
|
stdout: capturedStdout,
|
|
stderr: capturedStderr,
|
|
status: code,
|
|
signal: signal
|
|
});
|
|
|
|
reject(error);
|
|
}
|
|
};
|
|
child.on(exitEvent, exitCallback);
|
|
});
|
|
}
|