Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use spawn instead of execFile #27

Merged
merged 8 commits into from
Apr 28, 2016
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fixtures/error-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'use strict';
console.log('stdout');
console.error('stderr');
process.exit(1);
process.exit(2);
108 changes: 76 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use strict';
var childProcess = require('child_process');
var util = require('util');
var crossSpawnAsync = require('cross-spawn-async');
var stripEof = require('strip-eof');
var objectAssign = require('object-assign');
var npmRunPath = require('npm-run-path');
var isStream = require('is-stream');
var _getStream = require('get-stream');
var pathKey = require('path-key')();
var TEN_MEBIBYTE = 1024 * 1024 * 10;

Expand Down Expand Up @@ -61,7 +63,7 @@ function handleInput(spawned, opts) {
}

function handleOutput(opts, val) {
if (opts.stripEof) {
if (val && opts.stripEof) {
val = stripEof(val);
}

Expand Down Expand Up @@ -91,32 +93,83 @@ function handleShell(fn, cmd, opts) {
return fn(file, args, opts);
}

function getStream(stream, encoding) {
if (!stream) {
return null;
}

if (encoding) {
return _getStream(stream, {encoding: encoding});
}
return _getStream.buffer(stream);
}

function processDone(spawned) {
return new Promise(function (resolve) {
spawned.on('exit', function (code, signal) {
resolve({code: code, signal: signal});
});

spawned.on('error', function (err) {
resolve({err: err});
});
});
}

module.exports = function (cmd, args, opts) {
var spawned;

var promise = new Promise(function (resolve, reject) {
var parsed = handleArgs(cmd, args, opts);

spawned = childProcess.execFile(parsed.cmd, parsed.args, parsed.opts, function (err, stdout, stderr) {
if (err) {
err.stdout = stdout;
err.stderr = stderr;
err.message += stdout;
reject(err);
return;
var parsed = handleArgs(cmd, args, opts);
var encoding = parsed.opts.encoding;
var spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts);

var promise = Promise.all([
processDone(spawned),
getStream(spawned.stdout, encoding),
getStream(spawned.stderr, encoding)
]).then(function (arr) {
var result = arr[0];
var stdout = arr[1];
var stderr = arr[2];

var err = result.err;
var code = result.code;
var signal = result.signal;

if (err || code !== 0 || signal !== null) {
var joinedCmd = cmd;

if (Array.isArray(args) && args.length) {
joinedCmd += ' ' + args.join(' ');
}

resolve({
stdout: handleOutput(parsed.opts, stdout),
stderr: handleOutput(parsed.opts, stderr)
});
});
if (!err) {
err = new Error('Command failed: ' + joinedCmd + '\n' + stderr + stdout);

// TODO: missing some timeout logic for killed
// https://github.com/nodejs/node/blob/master/lib/child_process.js#L203
// err.killed = spawned.killed || killed;
err.killed = spawned.killed;

// TODO: child_process applies the following logic for resolving the code:
// var uv = process.bind('uv');
// ex.code = code < 0 ? uv.errname(code) : code;
err.code = code;
}

crossSpawnAsync._enoent.hookChildProcess(spawned, parsed);
err.stdout = stdout;
err.stderr = stderr;
throw err;
}

handleInput(spawned, parsed.opts);
return {
stdout: handleOutput(parsed.opts, stdout),
stderr: handleOutput(parsed.opts, stderr)
};
});

crossSpawnAsync._enoent.hookChildProcess(spawned, parsed);

handleInput(spawned, parsed.opts);

spawned.then = promise.then.bind(promise);
spawned.catch = promise.catch.bind(promise);

Expand All @@ -141,14 +194,7 @@ module.exports.shell = function (cmd, opts) {
return handleShell(module.exports, cmd, opts);
};

module.exports.spawn = function (cmd, args, opts) {
var parsed = handleArgs(cmd, args, opts);
var spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts);

crossSpawnAsync._enoent.hookChildProcess(spawned, parsed);

return spawned;
};
module.exports.spawn = util.deprecate(module.exports, 'execa.spawn: just use execa instead.');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execa.spawn() is deprecated. Use execa() instead.


module.exports.sync = function (cmd, args, opts) {
var parsed = handleArgs(cmd, args, opts);
Expand All @@ -159,10 +205,8 @@ module.exports.sync = function (cmd, args, opts) {

var result = childProcess.spawnSync(parsed.cmd, parsed.args, parsed.opts);

if (parsed.opts.stripEof) {
result.stdout = stripEof(result.stdout);
result.stderr = stripEof(result.stderr);
}
result.stdout = handleOutput(parsed.opts, result.stdout);
result.stderr = handleOutput(parsed.opts, result.stderr);

return result;
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
],
"dependencies": {
"cross-spawn-async": "^2.1.1",
"get-stream": "^2.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^1.0.0",
"object-assign": "^4.0.1",
Expand All @@ -55,7 +56,6 @@
"ava": "*",
"cat-names": "^1.0.2",
"coveralls": "^2.11.9",
"get-stream": "^2.0.0",
"nyc": "^6.4.0",
"xo": "*"
}
Expand Down
9 changes: 7 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ test('stdout/stderr available on errors', async t => {
});

test('include stdout in errors for improved debugging', async t => {
const err = await t.throws(m('./fixtures/error-message.js'));
t.true(err.message.indexOf('stdout') !== -1);
const err = await t.throws(m('fixtures/error-message.js'));
t.regex(err.message, /stdout/);
});

test('execa.shell()', async t => {
Expand Down Expand Up @@ -112,6 +112,11 @@ test('input option can be a Buffer - sync', async t => {
t.is(stdout, 'testing12');
});

test('opts.stdout:ignore - stdout will not collect data', async t => {
const {stdout} = await m('stdin', {input: 'hello', stdio: [null, 'ignore', null]});
t.is(stdout, null);
});

test('helpful error trying to provide an input stream in sync mode', t => {
t.throws(
() => m.sync('stdin', {input: new stream.PassThrough()}),
Expand Down