Repository: sindresorhus/execa (js)

Issue:
[Feature proposal] Distinguishing between error strings and error numbers
At the moment `result.code` is either a string like `EACCES`, an integer like `13` or `undefined`.

It would be nice to split this property into two: `code` for the string and `errno` for the integer. 

This would be a breaking change if the name `code` is re-used. Otherwise we can pick another property name.

If this sounds good, I will submit a PR.

Pull request description:
Distinguishing between error strings and error numbers
Fixes #179 

This introduces 3 different properties for status codes:
  - `code` is a string or integer (e.g. `2`)
  - `exitCode` is an integer (e.g. `2`)
  - `exitCodeName` is a string (e.g. `ENOENT`)

Patch:
diff --git a/index.js b/index.js
index 872c7fa..fa42bab 100644
--- a/index.js
+++ b/index.js
@@ -126,17 +126,19 @@ function makeError(result, options) {
 	let {error} = result;
 	const {joinedCommand, timedOut, parsed: {options: {timeout}}} = options;
 
-	const [codeString, codeNumber] = getCode(result, code);
+	const [exitCodeName, exitCode] = getCode(result, code);
 
 	if (!(error instanceof Error)) {
 		const message = [joinedCommand, stderr, stdout].filter(Boolean).join('\n');
 		error = new Error(message);
 	}
 
-	const prefix = getErrorPrefix({timedOut, timeout, signal, codeString, codeNumber});
+	const prefix = getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode});
 	error.message = `Command ${prefix}: ${error.message}`;
 
-	error.code = codeNumber || codeString;
+	error.code = exitCode || exitCodeName;
+	error.exitCode = exitCode;
+	error.exitCodeName = exitCodeName;
 	error.stdout = stdout;
 	error.stderr = stderr;
 	error.failed = true;
@@ -159,7 +161,7 @@ function getCode({error = {}}, code) {
 	return [];
 }
 
-function getErrorPrefix({timedOut, timeout, signal, codeString, codeNumber}) {
+function getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode}) {
 	if (timedOut) {
 		return `timed out after ${timeout} milliseconds`;
 	}
@@ -168,16 +170,16 @@ function getErrorPrefix({timedOut, timeout, signal, codeString, codeNumber}) {
 		return `was killed with ${signal}`;
 	}
 
-	if (codeString !== undefined && codeNumber !== undefined) {
-		return `failed with exit code ${codeNumber} (${codeString})`;
+	if (exitCodeName !== undefined && exitCode !== undefined) {
+		return `failed with exit code ${exitCode} (${exitCodeName})`;
 	}
 
-	if (codeString !== undefined) {
-		return `failed with exit code ${codeString}`;
+	if (exitCodeName !== undefined) {
+		return `failed with exit code ${exitCodeName}`;
 	}
 
-	if (codeNumber !== undefined) {
-		return `failed with exit code ${codeNumber}`;
+	if (exitCode !== undefined) {
+		return `failed with exit code ${exitCode}`;
 	}
 
 	return 'failed';
@@ -296,6 +298,8 @@ module.exports = (command, args, options) => {
 			stdout: handleOutput(parsed.options, result.stdout),
 			stderr: handleOutput(parsed.options, result.stderr),
 			code: 0,
+			exitCode: 0,
+			exitCodeName: 'SUCCESS',
 			failed: false,
 			killed: false,
 			signal: null,
@@ -362,6 +366,8 @@ module.exports.sync = (command, args, options) => {
 		stdout: handleOutput(parsed.options, result.stdout),
 		stderr: handleOutput(parsed.options, result.stderr),
 		code: 0,
+		exitCode: 0,
+		exitCodeName: 'SUCCESS',
 		failed: false,
 		signal: null,
 		cmd: joinedCommand,
diff --git a/readme.md b/readme.md
index b6921fa..6542e97 100644
--- a/readme.md
+++ b/readme.md
@@ -59,14 +59,17 @@ const execa = require('execa');
 		console.log(error);
 		/*
 		{
-			message: 'Command failed: /bin/sh -c exit 3'
-			killed: false,
+			message: 'Command failed with exit code 3 (ESRCH): exit 3',
 			code: 3,
-			signal: null,
-			cmd: '/bin/sh -c exit 3',
+			exitCode: 3,
+			exitCodeName: 'ESRCH',
 			stdout: '',
 			stderr: '',
-			timedOut: false
+			failed: true,
+			signal: null,
+			cmd: 'exit 3',
+			timedOut: false,
+			killed: false
 		}
 		*/
 	}
@@ -79,12 +82,15 @@ try {
 	console.log(error);
 	/*
 	{
-		message: 'Command failed: /bin/sh -c exit 3'
+		message: 'Command failed with exit code 3 (ESRCH): exit 3',
 		code: 3,
-		signal: null,
-		cmd: '/bin/sh -c exit 3',
+		exitCode: 3,
+		exitCodeName: 'ESRCH',
 		stdout: '',
 		stderr: '',
+		failed: true,
+		signal: null,
+		cmd: 'exit 3',
 		timedOut: false
 	}
 	*/
@@ -322,6 +328,7 @@ getStream(stream).then(value => {
 ```
 
 
-## License
+## Maintainers
 
-MIT © [Sindre Sorhus](https://sindresorhus.com)
+- [Sindre Sorhus](https://github.com/sindresorhus)
+- [@ehmicky](https://github.com/ehmicky)


Test patch:
diff --git a/test.js b/test.js
index 93b3e0d..3e72c60 100644
--- a/test.js
+++ b/test.js
@@ -259,14 +259,22 @@ test('skip throwing when using reject option', async t => {
 });
 
 test('allow unknown exit code', async t => {
-	await t.throwsAsync(execa('exit', ['255']), {message: /exit code 255 \(Unknown system error -255\)/});
+	const {exitCode, exitCodeName} = await t.throwsAsync(execa('exit', ['255']), {message: /exit code 255 \(Unknown system error -255\)/});
+	t.is(exitCode, 255);
+	t.is(exitCodeName, 'Unknown system error -255');
 });
 
 test('execa() returns code and failed properties', async t => {
-	const {code, failed} = await execa('noop', ['foo']);
-	const error = await t.throwsAsync(execa('exit', ['2']), {code: 2, message: getExitRegExp('2')});
+	const {code, exitCode, exitCodeName, failed} = await execa('noop', ['foo']);
 	t.is(code, 0);
+	t.is(exitCode, 0);
+	t.is(exitCodeName, 'SUCCESS');
 	t.false(failed);
+
+	const error = await t.throwsAsync(execa('exit', ['2']), {code: 2, message: getExitRegExp('2')});
+	t.is(error.exitCode, 2);
+	const expectedName = process.platform === 'win32' ? 'Unknown system error -2' : 'ENOENT';
+	t.is(error.exitCodeName, expectedName);
 	t.true(error.failed);
 });
 
@@ -361,7 +369,8 @@ test('result.signal is null if process failed, but was not killed', async t => {
 });
 
 async function code(t, num) {
-	await t.throwsAsync(execa('exit', [`${num}`]), {code: num, message: getExitRegExp(num)});
+	const error = await t.throwsAsync(execa('exit', [`${num}`]), {code: num, message: getExitRegExp(num)});
+	t.is(error.exitCode, num);
 }
 
 test('error.code is 2', code, 2);
@@ -372,7 +381,7 @@ test('timeout will kill the process early', async t => {
 	const error = await t.throwsAsync(execa('delay', ['60000', '0'], {timeout: 1500, message: TIMEOUT_REGEXP}));
 
 	t.true(error.timedOut);
-	t.not(error.code, 22);
+	t.not(error.exitCode, 22);
 });
 
 test('timeout will not kill the process early', async t => {
@@ -461,7 +470,7 @@ if (process.platform !== 'win32') {
 			await execa(`fast-exit-${process.platform}`, [], {input: 'data'});
 			t.pass();
 		} catch (error) {
-			t.is(error.code, 32);
+			t.is(error.exitCode, 32);
 		}
 	});
 }