209 lines
6.4 KiB
Plaintext
209 lines
6.4 KiB
Plaintext
var common = require('./common');
|
|
var _tempDir = require('./tempdir').tempDir;
|
|
var _pwd = require('./pwd');
|
|
var path = require('path');
|
|
var fs = require('fs');
|
|
var child = require('child_process');
|
|
|
|
var DEFAULT_MAXBUFFER_SIZE = 20 * 1024 * 1024;
|
|
var DEFAULT_ERROR_CODE = 1;
|
|
|
|
common.register('exec', _exec, {
|
|
unix: false,
|
|
canReceivePipe: true,
|
|
wrapOutput: false,
|
|
});
|
|
|
|
// We use this function to run `exec` synchronously while also providing realtime
|
|
// output.
|
|
function execSync(cmd, opts, pipe) {
|
|
if (!common.config.execPath) {
|
|
common.error('Unable to find a path to the node binary. Please manually set config.execPath');
|
|
}
|
|
|
|
var tempDir = _tempDir();
|
|
var paramsFile = path.resolve(tempDir + '/' + common.randomFileName());
|
|
var stderrFile = path.resolve(tempDir + '/' + common.randomFileName());
|
|
var stdoutFile = path.resolve(tempDir + '/' + common.randomFileName());
|
|
|
|
opts = common.extend({
|
|
silent: common.config.silent,
|
|
cwd: _pwd().toString(),
|
|
env: process.env,
|
|
maxBuffer: DEFAULT_MAXBUFFER_SIZE,
|
|
encoding: 'utf8',
|
|
}, opts);
|
|
|
|
if (fs.existsSync(paramsFile)) common.unlinkSync(paramsFile);
|
|
if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile);
|
|
if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
|
|
|
|
opts.cwd = path.resolve(opts.cwd);
|
|
|
|
var paramsToSerialize = {
|
|
command: cmd,
|
|
execOptions: opts,
|
|
pipe: pipe,
|
|
stdoutFile: stdoutFile,
|
|
stderrFile: stderrFile,
|
|
};
|
|
|
|
fs.writeFileSync(paramsFile, JSON.stringify(paramsToSerialize), 'utf8');
|
|
|
|
var execArgs = [
|
|
path.join(__dirname, 'exec-child.js'),
|
|
paramsFile,
|
|
];
|
|
|
|
/* istanbul ignore else */
|
|
if (opts.silent) {
|
|
opts.stdio = 'ignore';
|
|
} else {
|
|
opts.stdio = [0, 1, 2];
|
|
}
|
|
|
|
var code = 0;
|
|
|
|
// Welcome to the future
|
|
try {
|
|
// Bad things if we pass in a `shell` option to child_process.execFileSync,
|
|
// so we need to explicitly remove it here.
|
|
delete opts.shell;
|
|
|
|
child.execFileSync(common.config.execPath, execArgs, opts);
|
|
} catch (e) {
|
|
// Commands with non-zero exit code raise an exception.
|
|
code = e.status || DEFAULT_ERROR_CODE;
|
|
}
|
|
|
|
// fs.readFileSync uses buffer encoding by default, so call
|
|
// it without the encoding option if the encoding is 'buffer'.
|
|
// Also, if the exec timeout is too short for node to start up,
|
|
// the files will not be created, so these calls will throw.
|
|
var stdout = '';
|
|
var stderr = '';
|
|
if (opts.encoding === 'buffer') {
|
|
stdout = fs.readFileSync(stdoutFile);
|
|
stderr = fs.readFileSync(stderrFile);
|
|
} else {
|
|
stdout = fs.readFileSync(stdoutFile, opts.encoding);
|
|
stderr = fs.readFileSync(stderrFile, opts.encoding);
|
|
}
|
|
|
|
// No biggie if we can't erase the files now -- they're in a temp dir anyway
|
|
try { common.unlinkSync(paramsFile); } catch (e) {}
|
|
try { common.unlinkSync(stderrFile); } catch (e) {}
|
|
try { common.unlinkSync(stdoutFile); } catch (e) {}
|
|
|
|
if (code !== 0) {
|
|
// Note: `silent` should be unconditionally true to avoid double-printing
|
|
// the command's stderr, and to avoid printing any stderr when the user has
|
|
// set `shell.config.silent`.
|
|
common.error(stderr, code, { continue: true, silent: true });
|
|
}
|
|
var obj = common.ShellString(stdout, stderr, code);
|
|
return obj;
|
|
} // execSync()
|
|
|
|
// Wrapper around exec() to enable echoing output to console in real time
|
|
function execAsync(cmd, opts, pipe, callback) {
|
|
opts = common.extend({
|
|
silent: common.config.silent,
|
|
cwd: _pwd().toString(),
|
|
env: process.env,
|
|
maxBuffer: DEFAULT_MAXBUFFER_SIZE,
|
|
encoding: 'utf8',
|
|
}, opts);
|
|
|
|
var c = child.exec(cmd, opts, function (err, stdout, stderr) {
|
|
if (callback) {
|
|
if (!err) {
|
|
callback(0, stdout, stderr);
|
|
} else if (err.code === undefined) {
|
|
// See issue #536
|
|
/* istanbul ignore next */
|
|
callback(1, stdout, stderr);
|
|
} else {
|
|
callback(err.code, stdout, stderr);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (pipe) c.stdin.end(pipe);
|
|
|
|
if (!opts.silent) {
|
|
c.stdout.pipe(process.stdout);
|
|
c.stderr.pipe(process.stderr);
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
//@
|
|
//@ ### exec(command [, options] [, callback])
|
|
//@
|
|
//@ Available options:
|
|
//@
|
|
//@ + `async`: Asynchronous execution. If a callback is provided, it will be set to
|
|
//@ `true`, regardless of the passed value (default: `false`).
|
|
//@ + `silent`: Do not echo program output to console (default: `false`).
|
|
//@ + `encoding`: Character encoding to use. Affects the values returned to stdout and stderr, and
|
|
//@ what is written to stdout and stderr when not in silent mode (default: `'utf8'`).
|
|
//@ + and any option available to Node.js's
|
|
//@ [`child_process.exec()`](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback)
|
|
//@
|
|
//@ Examples:
|
|
//@
|
|
//@ ```javascript
|
|
//@ var version = exec('node --version', {silent:true}).stdout;
|
|
//@
|
|
//@ var child = exec('some_long_running_process', {async:true});
|
|
//@ child.stdout.on('data', function(data) {
|
|
//@ /* ... do something with data ... */
|
|
//@ });
|
|
//@
|
|
//@ exec('some_long_running_process', function(code, stdout, stderr) {
|
|
//@ console.log('Exit code:', code);
|
|
//@ console.log('Program output:', stdout);
|
|
//@ console.log('Program stderr:', stderr);
|
|
//@ });
|
|
//@ ```
|
|
//@
|
|
//@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous
|
|
//@ mode, this returns a `ShellString` (compatible with ShellJS v0.6.x, which returns an object
|
|
//@ of the form `{ code:..., stdout:... , stderr:... }`). Otherwise, this returns the child process
|
|
//@ object, and the `callback` receives the arguments `(code, stdout, stderr)`.
|
|
//@
|
|
//@ Not seeing the behavior you want? `exec()` runs everything through `sh`
|
|
//@ by default (or `cmd.exe` on Windows), which differs from `bash`. If you
|
|
//@ need bash-specific behavior, try out the `{shell: 'path/to/bash'}` option.
|
|
function _exec(command, options, callback) {
|
|
options = options || {};
|
|
if (!command) common.error('must specify command');
|
|
|
|
var pipe = common.readFromPipe();
|
|
|
|
// Callback is defined instead of options.
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = { async: true };
|
|
}
|
|
|
|
// Callback is defined with options.
|
|
if (typeof options === 'object' && typeof callback === 'function') {
|
|
options.async = true;
|
|
}
|
|
|
|
options = common.extend({
|
|
silent: common.config.silent,
|
|
async: false,
|
|
}, options);
|
|
|
|
if (options.async) {
|
|
return execAsync(command, options, pipe, callback);
|
|
} else {
|
|
return execSync(command, options, pipe);
|
|
}
|
|
}
|
|
module.exports = _exec;
|