2 const childProcess = require('child_process');
3 const util = require('util');
4 const crossSpawn = require('cross-spawn');
5 const stripEof = require('strip-eof');
6 const npmRunPath = require('npm-run-path');
7 const isStream = require('is-stream');
8 const _getStream = require('get-stream');
9 const pFinally = require('p-finally');
10 const onExit = require('signal-exit');
11 const errname = require('./lib/errname');
13 const TEN_MEGABYTES = 1000 * 1000 * 10;
15 function handleArgs(cmd, args, opts) {
18 if (opts && opts.__winShell === true) {
19 delete opts.__winShell;
28 parsed = crossSpawn._parse(cmd, args, opts);
31 opts = Object.assign({
32 maxBuffer: TEN_MEGABYTES,
40 if (opts.preferLocal) {
41 opts.env = npmRunPath.env(opts);
51 function handleInput(spawned, opts) {
52 const input = opts.input;
54 if (input === null || input === undefined) {
58 if (isStream(input)) {
59 input.pipe(spawned.stdin);
61 spawned.stdin.end(input);
65 function handleOutput(opts, val) {
66 if (val && opts.stripEof) {
73 function handleShell(fn, cmd, opts) {
75 let args = ['-c', cmd];
77 opts = Object.assign({}, opts);
79 if (process.platform === 'win32') {
80 opts.__winShell = true;
81 file = process.env.comspec || 'cmd.exe';
82 args = ['/s', '/c', `"${cmd}"`];
83 opts.windowsVerbatimArguments = true;
91 return fn(file, args, opts);
94 function getStream(process, stream, encoding, maxBuffer) {
95 if (!process[stream]) {
102 ret = _getStream(process[stream], {
107 ret = _getStream.buffer(process[stream], {maxBuffer});
110 return ret.catch(err => {
112 err.message = `${stream} ${err.message}`;
117 module.exports = (cmd, args, opts) => {
120 if (Array.isArray(args) && args.length > 0) {
121 joinedCmd += ' ' + args.join(' ');
124 const parsed = handleArgs(cmd, args, opts);
125 const encoding = parsed.opts.encoding;
126 const maxBuffer = parsed.opts.maxBuffer;
130 spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts);
132 return Promise.reject(err);
135 let removeExitHandler;
136 if (parsed.opts.cleanup) {
137 removeExitHandler = onExit(() => {
142 let timeoutId = null;
143 let timedOut = false;
145 const cleanupTimeout = () => {
147 clearTimeout(timeoutId);
152 if (parsed.opts.timeout > 0) {
153 timeoutId = setTimeout(() => {
156 spawned.kill(parsed.killSignal);
157 }, parsed.opts.timeout);
160 const processDone = new Promise(resolve => {
161 spawned.on('exit', (code, signal) => {
163 resolve({code, signal});
166 spawned.on('error', err => {
173 if (spawned.stdout) {
174 spawned.stdout.destroy();
177 if (spawned.stderr) {
178 spawned.stderr.destroy();
182 const promise = pFinally(Promise.all([
184 getStream(spawned, 'stdout', encoding, maxBuffer),
185 getStream(spawned, 'stderr', encoding, maxBuffer)
187 const result = arr[0];
188 const stdout = arr[1];
189 const stderr = arr[2];
191 let err = result.err;
192 const code = result.code;
193 const signal = result.signal;
195 if (removeExitHandler) {
199 if (err || code !== 0 || signal !== null) {
201 err = new Error(`Command failed: ${joinedCmd}\n${stderr}${stdout}`);
202 err.code = code < 0 ? errname(code) : code;
205 // TODO: missing some timeout logic for killed
206 // https://github.com/nodejs/node/blob/master/lib/child_process.js#L203
207 // err.killed = spawned.killed || killed;
208 err.killed = err.killed || spawned.killed;
213 err.signal = signal || null;
215 err.timedOut = timedOut;
217 if (!parsed.opts.reject) {
225 stdout: handleOutput(parsed.opts, stdout),
226 stderr: handleOutput(parsed.opts, stderr),
236 crossSpawn._enoent.hookChildProcess(spawned, parsed);
238 handleInput(spawned, parsed.opts);
240 spawned.then = promise.then.bind(promise);
241 spawned.catch = promise.catch.bind(promise);
246 module.exports.stdout = function () {
247 // TODO: set `stderr: 'ignore'` when that option is implemented
248 return module.exports.apply(null, arguments).then(x => x.stdout);
251 module.exports.stderr = function () {
252 // TODO: set `stdout: 'ignore'` when that option is implemented
253 return module.exports.apply(null, arguments).then(x => x.stderr);
256 module.exports.shell = (cmd, opts) => handleShell(module.exports, cmd, opts);
258 module.exports.sync = (cmd, args, opts) => {
259 const parsed = handleArgs(cmd, args, opts);
261 if (isStream(parsed.opts.input)) {
262 throw new TypeError('The `input` option cannot be a stream in sync mode');
265 const result = childProcess.spawnSync(parsed.cmd, parsed.args, parsed.opts);
267 result.stdout = handleOutput(parsed.opts, result.stdout);
268 result.stderr = handleOutput(parsed.opts, result.stderr);
273 module.exports.shellSync = (cmd, opts) => handleShell(module.exports.sync, cmd, opts);
275 module.exports.spawn = util.deprecate(module.exports, 'execa.spawn() is deprecated. Use execa() instead.');