UNPKG

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