aboutsummaryrefslogtreecommitdiff
path: root/node_modules/shelljs/src/exec.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/shelljs/src/exec.js')
-rw-r--r--node_modules/shelljs/src/exec.js295
1 files changed, 295 insertions, 0 deletions
diff --git a/node_modules/shelljs/src/exec.js b/node_modules/shelljs/src/exec.js
new file mode 100644
index 000000000..5d360e868
--- /dev/null
+++ b/node_modules/shelljs/src/exec.js
@@ -0,0 +1,295 @@
+var common = require('./common');
+var _tempDir = require('./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;
+
+common.register('exec', _exec, {
+ unix: false,
+ canReceivePipe: true,
+ wrapOutput: false,
+});
+
+// Hack to run child_process.exec() synchronously (sync avoids callback hell)
+// Uses a custom wait loop that checks for a flag file, created when the child process is done.
+// (Can't do a wait loop that checks for internal Node variables/messages as
+// Node is single-threaded; callbacks and other internal state changes are done in the
+// event loop).
+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 stdoutFile = path.resolve(tempDir + '/' + common.randomFileName());
+ var stderrFile = path.resolve(tempDir + '/' + common.randomFileName());
+ var codeFile = path.resolve(tempDir + '/' + common.randomFileName());
+ var scriptFile = path.resolve(tempDir + '/' + common.randomFileName());
+ var sleepFile = path.resolve(tempDir + '/' + common.randomFileName());
+
+ opts = common.extend({
+ silent: common.config.silent,
+ cwd: _pwd().toString(),
+ env: process.env,
+ maxBuffer: DEFAULT_MAXBUFFER_SIZE,
+ }, opts);
+
+ var previousStdoutContent = '';
+ var previousStderrContent = '';
+ // Echoes stdout and stderr changes from running process, if not silent
+ function updateStream(streamFile) {
+ if (opts.silent || !fs.existsSync(streamFile)) {
+ return;
+ }
+
+ var previousStreamContent;
+ var procStream;
+ if (streamFile === stdoutFile) {
+ previousStreamContent = previousStdoutContent;
+ procStream = process.stdout;
+ } else { // assume stderr
+ previousStreamContent = previousStderrContent;
+ procStream = process.stderr;
+ }
+
+ var streamContent = fs.readFileSync(streamFile, 'utf8');
+ // No changes since last time?
+ if (streamContent.length <= previousStreamContent.length) {
+ return;
+ }
+
+ procStream.write(streamContent.substr(previousStreamContent.length));
+ previousStreamContent = streamContent;
+ }
+
+ if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile);
+ if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
+ if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile);
+ if (fs.existsSync(codeFile)) common.unlinkSync(codeFile);
+
+ var execCommand = JSON.stringify(common.config.execPath) + ' ' + JSON.stringify(scriptFile);
+ var script;
+
+ opts.cwd = path.resolve(opts.cwd);
+ var optString = JSON.stringify(opts);
+
+ if (typeof child.execSync === 'function') {
+ script = [
+ "var child = require('child_process')",
+ " , fs = require('fs');",
+ 'var childProcess = child.exec(' + JSON.stringify(cmd) + ', ' + optString + ', function(err) {',
+ ' var fname = ' + JSON.stringify(codeFile) + ';',
+ ' if (!err) {',
+ ' fs.writeFileSync(fname, "0");',
+ ' } else if (err.code === undefined) {',
+ ' fs.writeFileSync(fname, "1");',
+ ' } else {',
+ ' fs.writeFileSync(fname, err.code.toString());',
+ ' }',
+ '});',
+ 'var stdoutStream = fs.createWriteStream(' + JSON.stringify(stdoutFile) + ');',
+ 'var stderrStream = fs.createWriteStream(' + JSON.stringify(stderrFile) + ');',
+ 'childProcess.stdout.pipe(stdoutStream, {end: false});',
+ 'childProcess.stderr.pipe(stderrStream, {end: false});',
+ 'childProcess.stdout.pipe(process.stdout);',
+ 'childProcess.stderr.pipe(process.stderr);',
+ ].join('\n') +
+ (pipe ? '\nchildProcess.stdin.end(' + JSON.stringify(pipe) + ');\n' : '\n') +
+ [
+ 'var stdoutEnded = false, stderrEnded = false;',
+ 'function tryClosingStdout(){ if(stdoutEnded){ stdoutStream.end(); } }',
+ 'function tryClosingStderr(){ if(stderrEnded){ stderrStream.end(); } }',
+ "childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosingStdout(); });",
+ "childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosingStderr(); });",
+ ].join('\n');
+
+ fs.writeFileSync(scriptFile, script);
+
+ if (opts.silent) {
+ opts.stdio = 'ignore';
+ } else {
+ opts.stdio = [0, 1, 2];
+ }
+
+ // Welcome to the future
+ try {
+ child.execSync(execCommand, opts);
+ } catch (e) {
+ // Clean up immediately if we have an exception
+ try { common.unlinkSync(scriptFile); } catch (e2) {}
+ try { common.unlinkSync(stdoutFile); } catch (e2) {}
+ try { common.unlinkSync(stderrFile); } catch (e2) {}
+ try { common.unlinkSync(codeFile); } catch (e2) {}
+ throw e;
+ }
+ } else {
+ cmd += ' > ' + stdoutFile + ' 2> ' + stderrFile; // works on both win/unix
+
+ script = [
+ "var child = require('child_process')",
+ " , fs = require('fs');",
+ 'var childProcess = child.exec(' + JSON.stringify(cmd) + ', ' + optString + ', function(err) {',
+ ' var fname = ' + JSON.stringify(codeFile) + ';',
+ ' if (!err) {',
+ ' fs.writeFileSync(fname, "0");',
+ ' } else if (err.code === undefined) {',
+ ' fs.writeFileSync(fname, "1");',
+ ' } else {',
+ ' fs.writeFileSync(fname, err.code.toString());',
+ ' }',
+ '});',
+ ].join('\n') +
+ (pipe ? '\nchildProcess.stdin.end(' + JSON.stringify(pipe) + ');\n' : '\n');
+
+ fs.writeFileSync(scriptFile, script);
+
+ child.exec(execCommand, opts);
+
+ // The wait loop
+ // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage
+ // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing
+ // CPU usage, though apparently not so much on Windows)
+ while (!fs.existsSync(codeFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); }
+ while (!fs.existsSync(stdoutFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); }
+ while (!fs.existsSync(stderrFile)) { updateStream(stderrFile); fs.writeFileSync(sleepFile, 'a'); }
+ try { common.unlinkSync(sleepFile); } catch (e) {}
+ }
+
+ // At this point codeFile exists, but it's not necessarily flushed yet.
+ // Keep reading it until it is.
+ var code = parseInt('', 10);
+ while (isNaN(code)) {
+ code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10);
+ }
+
+ var stdout = fs.readFileSync(stdoutFile, 'utf8');
+ var stderr = fs.readFileSync(stderrFile, 'utf8');
+
+ // No biggie if we can't erase the files now -- they're in a temp dir anyway
+ try { common.unlinkSync(scriptFile); } catch (e) {}
+ try { common.unlinkSync(stdoutFile); } catch (e) {}
+ try { common.unlinkSync(stderrFile); } catch (e) {}
+ try { common.unlinkSync(codeFile); } catch (e) {}
+
+ if (code !== 0) {
+ common.error('', code, { continue: 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) {
+ var stdout = '';
+ var stderr = '';
+
+ opts = common.extend({
+ silent: common.config.silent,
+ cwd: _pwd().toString(),
+ env: process.env,
+ maxBuffer: DEFAULT_MAXBUFFER_SIZE,
+ }, opts);
+
+ var c = child.exec(cmd, opts, function (err) {
+ if (callback) {
+ if (!err) {
+ callback(0, stdout, stderr);
+ } else if (err.code === undefined) {
+ // See issue #536
+ callback(1, stdout, stderr);
+ } else {
+ callback(err.code, stdout, stderr);
+ }
+ }
+ });
+
+ if (pipe) c.stdin.end(pipe);
+
+ c.stdout.on('data', function (data) {
+ stdout += data;
+ if (!opts.silent) process.stdout.write(data);
+ });
+
+ c.stderr.on('data', function (data) {
+ stderr += data;
+ if (!opts.silent) process.stderr.write(data);
+ });
+
+ return c;
+}
+
+//@
+//@ ### exec(command [, options] [, callback])
+//@ Available options (all `false` by default):
+//@
+//@ + `async`: Asynchronous execution. If a callback is provided, it will be set to
+//@ `true`, regardless of the passed value.
+//@ + `silent`: Do not echo program output to console.
+//@ + 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` gets 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.
+//@
+//@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
+//@ the current synchronous implementation uses a lot of CPU. This should be getting
+//@ fixed soon.
+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);
+
+ try {
+ if (options.async) {
+ return execAsync(command, options, pipe, callback);
+ } else {
+ return execSync(command, options, pipe);
+ }
+ } catch (e) {
+ common.error('internal error');
+ }
+}
+module.exports = _exec;