UNPKG

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