forked from shelljs/shelljs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexec.js
More file actions
233 lines (204 loc) · 7.76 KB
/
Copy pathexec.js
File metadata and controls
233 lines (204 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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());
opts = common.extend({
silent: common.config.silent,
cwd: _pwd().toString(),
env: process.env,
maxBuffer: DEFAULT_MAXBUFFER_SIZE,
}, opts);
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);
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;
}
// 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;