UNPKG

6.16 kBJavaScriptView Raw
1var common = require('./common');
2var _tempDir = require('./tempdir');
3var _pwd = require('./pwd');
4var path = require('path');
5var fs = require('fs');
6var child = require('child_process');
7
8var DEFAULT_MAXBUFFER_SIZE = 20 * 1024 * 1024;
9
10common.register('exec', _exec, {
11 unix: false,
12 canReceivePipe: true,
13 wrapOutput: false,
14});
15
16// We use this function to run exec synchronously while also providing realtime
17// output.
18function execSync(cmd, opts, pipe) {
19 if (!common.config.execPath) {
20 common.error('Unable to find a path to the node binary. Please manually set config.execPath');
21 }
22
23 var tempDir = _tempDir();
24 var stderrFile = path.resolve(tempDir + '/' + common.randomFileName());
25 var stdoutFile = path.resolve(tempDir + '/' + common.randomFileName());
26
27 opts = common.extend({
28 silent: common.config.silent,
29 cwd: _pwd().toString(),
30 env: process.env,
31 maxBuffer: DEFAULT_MAXBUFFER_SIZE,
32 encoding: 'utf8',
33 }, opts);
34
35 if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile);
36 if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
37
38 opts.cwd = path.resolve(opts.cwd);
39
40 var paramsToSerialize = {
41 command: cmd,
42 execOptions: opts,
43 pipe: pipe,
44 stdoutFile: stdoutFile,
45 stderrFile: stderrFile,
46 };
47
48 var execArgs = [
49 path.join(__dirname, 'exec-child.js'),
50 JSON.stringify(paramsToSerialize),
51 ];
52
53 /* istanbul ignore else */
54 if (opts.silent) {
55 opts.stdio = 'ignore';
56 } else {
57 opts.stdio = [0, 1, 2];
58 }
59
60 var code = 0;
61
62 // Welcome to the future
63 try {
64 // Bad things if we pass in a `shell` option to child_process.execFileSync,
65 // so we need to explicitly remove it here.
66 delete opts.shell;
67
68 child.execFileSync(common.config.execPath, execArgs, opts);
69 } catch (e) {
70 // Commands with non-zero exit code raise an exception.
71 code = e.status;
72 }
73
74 // fs.readFileSync uses buffer encoding by default, so call
75 // it without the encoding option if the encoding is 'buffer'
76 var stdout;
77 var stderr;
78 if (opts.encoding === 'buffer') {
79 stdout = fs.readFileSync(stdoutFile);
80 stderr = fs.readFileSync(stderrFile);
81 } else {
82 stdout = fs.readFileSync(stdoutFile, opts.encoding);
83 stderr = fs.readFileSync(stderrFile, opts.encoding);
84 }
85
86 // No biggie if we can't erase the files now -- they're in a temp dir anyway
87 try { common.unlinkSync(stderrFile); } catch (e) {}
88 try { common.unlinkSync(stdoutFile); } catch (e) {}
89
90 if (code !== 0) {
91 common.error('', code, { continue: true });
92 }
93 var obj = common.ShellString(stdout, stderr, code);
94 return obj;
95} // execSync()
96
97// Wrapper around exec() to enable echoing output to console in real time
98function execAsync(cmd, opts, pipe, callback) {
99 opts = common.extend({
100 silent: common.config.silent,
101 cwd: _pwd().toString(),
102 env: process.env,
103 maxBuffer: DEFAULT_MAXBUFFER_SIZE,
104 encoding: 'utf8',
105 }, opts);
106
107 var c = child.exec(cmd, opts, function (err, stdout, stderr) {
108 if (callback) {
109 if (!err) {
110 callback(0, stdout, stderr);
111 } else if (err.code === undefined) {
112 // See issue #536
113 /* istanbul ignore next */
114 callback(1, stdout, stderr);
115 } else {
116 callback(err.code, stdout, stderr);
117 }
118 }
119 });
120
121 if (pipe) c.stdin.end(pipe);
122
123 if (!opts.silent) {
124 c.stdout.pipe(process.stdout);
125 c.stderr.pipe(process.stderr);
126 }
127
128 return c;
129}
130
131//@
132//@ ### exec(command [, options] [, callback])
133//@ Available options:
134//@
135//@ + `async`: Asynchronous execution. If a callback is provided, it will be set to
136//@ `true`, regardless of the passed value (default: `false`).
137//@ + `silent`: Do not echo program output to console (default: `false`).
138//@ + `encoding`: Character encoding to use. Affects the returned stdout and stderr values, and
139//@ what is written to stdout and stderr when not in silent mode (default: `'utf8'`).
140//@ + and any option available to Node.js's
141//@ [child_process.exec()](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback)
142//@
143//@ Examples:
144//@
145//@ ```javascript
146//@ var version = exec('node --version', {silent:true}).stdout;
147//@
148//@ var child = exec('some_long_running_process', {async:true});
149//@ child.stdout.on('data', function(data) {
150//@ /* ... do something with data ... */
151//@ });
152//@
153//@ exec('some_long_running_process', function(code, stdout, stderr) {
154//@ console.log('Exit code:', code);
155//@ console.log('Program output:', stdout);
156//@ console.log('Program stderr:', stderr);
157//@ });
158//@ ```
159//@
160//@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous
161//@ mode, this returns a ShellString (compatible with ShellJS v0.6.x, which returns an object
162//@ of the form `{ code:..., stdout:... , stderr:... }`). Otherwise, this returns the child process
163//@ object, and the `callback` gets the arguments `(code, stdout, stderr)`.
164//@
165//@ Not seeing the behavior you want? `exec()` runs everything through `sh`
166//@ by default (or `cmd.exe` on Windows), which differs from `bash`. If you
167//@ need bash-specific behavior, try out the `{shell: 'path/to/bash'}` option.
168//@
169//@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
170//@ the current synchronous implementation uses a lot of CPU. This should be getting
171//@ fixed soon.
172function _exec(command, options, callback) {
173 options = options || {};
174 if (!command) common.error('must specify command');
175
176 var pipe = common.readFromPipe();
177
178 // Callback is defined instead of options.
179 if (typeof options === 'function') {
180 callback = options;
181 options = { async: true };
182 }
183
184 // Callback is defined with options.
185 if (typeof options === 'object' && typeof callback === 'function') {
186 options.async = true;
187 }
188
189 options = common.extend({
190 silent: common.config.silent,
191 async: false,
192 }, options);
193
194 try {
195 if (options.async) {
196 return execAsync(command, options, pipe, callback);
197 } else {
198 return execSync(command, options, pipe);
199 }
200 } catch (e) {
201 common.error('internal error');
202 }
203}
204module.exports = _exec;