diff --git a/index.js b/index.js index 2d9ebae4c1..5e41728604 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ var npmRunPath = require('npm-run-path'); var isStream = require('is-stream'); var _getStream = require('get-stream'); var pathKey = require('path-key')(); +var errname = require('./lib/errname'); var TEN_MEBIBYTE = 1024 * 1024 * 10; function handleArgs(cmd, args, opts) { @@ -149,10 +150,7 @@ module.exports = function (cmd, args, opts) { // 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; + err.code = code < 0 ? errname(code) : code; } err.stdout = stdout; diff --git a/lib/errname.js b/lib/errname.js new file mode 100644 index 0000000000..ead21201e7 --- /dev/null +++ b/lib/errname.js @@ -0,0 +1,38 @@ +'use strict'; +// The Node team wants to deprecate `process.bind(...)`. +// https://github.com/nodejs/node/pull/2768 +// +// However, we need the 'uv' binding for errname support. +// This is a defensive wrapper around it so `execa` will not fail entirely if it stops working someday. +// +// If this ever stops working. See: https://github.com/sindresorhus/execa/issues/31#issuecomment-215939939 for another possible solution. +var uv; + +try { + uv = process.binding('uv'); + if (typeof uv.errname !== 'function') { + throw new Error('uv.errname is not a function'); + } +} catch (e) { + console.error('execa/lib/errname: unable to establish process.binding("uv")', e); + uv = null; +} + +function errname(uv, code) { + if (uv) { + return uv.errname(code); + } + + if (!(code < 0)) { + throw new Error('err >= 0'); + } + + return 'UNKNOWN CODE: ' + code; +} + +module.exports = function getErrname(code) { + return errname(uv, code); +}; + +// used for testing the fallback behavior. +module.exports._test = errname; diff --git a/test/errname.js b/test/errname.js new file mode 100644 index 0000000000..9d1ddb2aba --- /dev/null +++ b/test/errname.js @@ -0,0 +1,23 @@ +import test from 'ava'; +import errname from '../lib/errname'; + +// simulates failure to capture process.binding('uv'); +function fallback(code) { + return errname._test(null, code); +} + +function makeTests(name, m, expected) { + test(name, t => { + // throws >= 0 + t.throws(() => m(0), /err >= 0/); + t.throws(() => m(1), /err >= 0/); + t.throws(() => m('2'), /err >= 0/); + t.throws(() => m('foo'), /err >= 0/); + + t.is(m(-2), expected); + t.is(m('-2'), expected); + }); +} + +makeTests('native', errname, 'ENOENT'); +makeTests('fallback', fallback, 'UNKNOWN CODE: -2');