Repository: sindresorhus/execa
Pull request #250 (2019-05-14 16:14:14)

Remove `error.code`
`error.code` is deprecated in favor of `error.exitCode` and `error.exitCodeName`. This PR removes it from the TypeScript definition.

I am not sure whether unknown properties are an issue with TypeScript. If they are, feel free to close this PR.

sindresorhus: Let's just remove it completely. You were right. It's confusing and weird to have it now that we have the other properties.
sindresorhus: https://github.com/sindresorhus/execa/blob/1f427fd71a515377a19640a3fc8a7e7a9f9b746d/index.js#L425 ?
ehmicky: That's because for some reason `spawnSync()` returns the exit code with a `signal` property name, while `spawn()` use a `code` property name. We:
  - normalize both to `result.code`
  - use `result.code` to set `result.exitCode` and `result.exitCodeName`
  - delete `result.code`

I agree it looks bad though, I'm going to refactor it.

Patch:
diff --git a/index.d.ts b/index.d.ts
index 054c097..f432e2e 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -242,10 +242,6 @@ declare namespace execa {
 
 	interface ExecaSyncReturnValue<StdoutErrorType = string>
 		extends ExecaReturnBase<StdoutErrorType> {
-		/**
-		The exit code of the process that was run.
-		*/
-		code: number;
 	}
 
 	interface ExecaReturnValue<StdoutErrorType = string>
@@ -268,11 +264,6 @@ declare namespace execa {
 		The error message.
 		*/
 		message: string;
-
-		/**
-		The exit code (either numeric or textual) of the process that was run.
-		*/
-		code: number | string;
 	}
 
 	interface ExecaError<StdoutErrorType = string>
diff --git a/index.js b/index.js
index 303537d..4c2cbb1 100644
--- a/index.js
+++ b/index.js
@@ -166,9 +166,9 @@ function getStream(process, stream, {encoding, buffer, maxBuffer}) {
 }
 
 function makeError(result, options) {
-	const {stdout, stderr, code, signal} = result;
+	const {stdout, stderr, signal} = result;
 	let {error} = result;
-	const {joinedCommand, timedOut, isCanceled, killed, parsed: {options: {timeout}}} = options;
+	const {code, joinedCommand, timedOut, isCanceled, killed, parsed: {options: {timeout}}} = options;
 
 	const [exitCodeName, exitCode] = getCode(result, code);
 
@@ -182,9 +182,9 @@ function makeError(result, options) {
 	}
 
 	error.command = joinedCommand;
+	delete error.code;
 	error.exitCode = exitCode;
 	error.exitCodeName = exitCodeName;
-	error.code = exitCode || exitCodeName;
 	error.stdout = stdout;
 	error.stderr = stderr;
 
@@ -356,6 +356,7 @@ const execa = (command, args, options) => {
 
 			if (result.error || result.code !== 0 || result.signal !== null) {
 				const error = makeError(result, {
+					code: result.code,
 					joinedCommand,
 					parsed,
 					timedOut,
@@ -374,7 +375,6 @@ const execa = (command, args, options) => {
 				command: joinedCommand,
 				exitCode: 0,
 				exitCodeName: 'SUCCESS',
-				code: 0,
 				stdout: result.stdout,
 				stderr: result.stderr,
 				all: result.all,
@@ -423,16 +423,17 @@ module.exports.sync = (command, args, options) => {
 	}
 
 	const result = childProcess.spawnSync(parsed.command, parsed.args, parsed.options);
-	result.code = result.status;
 	result.stdout = handleOutput(parsed.options, result.stdout);
 	result.stderr = handleOutput(parsed.options, result.stderr);
 
 	if (result.error || result.status !== 0 || result.signal !== null) {
 		const error = makeError(result, {
+			code: result.status,
 			joinedCommand,
 			parsed,
 			timedOut: false,
-			isCanceled: false
+			isCanceled: false,
+			killed: result.signal !== null
 		});
 
 		if (!parsed.options.reject) {
@@ -446,7 +447,6 @@ module.exports.sync = (command, args, options) => {
 		command: joinedCommand,
 		exitCode: 0,
 		exitCodeName: 'SUCCESS',
-		code: 0,
 		stdout: result.stdout,
 		stderr: result.stderr,
 		failed: false,
diff --git a/readme.md b/readme.md
index d735f01..7e573f1 100644
--- a/readme.md
+++ b/readme.md
@@ -62,7 +62,6 @@ const execa = require('execa');
 		{
 			message: 'Command failed with exit code 2 (ENOENT): wrong command spawn wrong ENOENT',
 			errno: 'ENOENT',
-			code: 2,
 			syscall: 'spawn wrong',
 			path: 'wrong',
 			spawnargs: ['command'],
@@ -102,7 +101,6 @@ try {
 	{
 		message: 'Command failed with exit code 2 (ENOENT): wrong command spawnSync wrong ENOENT',
 		errno: 'ENOENT',
-		code: 2,
 		syscall: 'spawnSync wrong',
 		path: 'wrong',
 		spawnargs: ['command'],
@@ -113,7 +111,8 @@ try {
 		stderr: null,
 		failed: true,
 		timedOut: false,
-		isCanceled: false
+		isCanceled: false,
+		killed: false
 	}
 	*/
 }


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index 72fcc8c..98bda07 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -14,7 +14,6 @@ try {
 
 	const unicornsResult = await execaPromise;
 	expectType<string>(unicornsResult.command);
-	expectType<string | number>(unicornsResult.code);
 	expectType<number>(unicornsResult.exitCode);
 	expectType<string>(unicornsResult.exitCodeName);
 	expectType<string>(unicornsResult.stdout);
@@ -29,7 +28,6 @@ try {
 	const execaError: ExecaError = error;
 
 	expectType<string>(execaError.message);
-	expectType<number | string>(execaError.code);
 	expectType<number>(execaError.exitCode);
 	expectType<string>(execaError.exitCodeName);
 	expectType<string>(execaError.stdout);
@@ -45,7 +43,6 @@ try {
 try {
 	const unicornsResult = execa.sync('unicorns');
 	expectType<string>(unicornsResult.command);
-	expectType<string | number>(unicornsResult.code);
 	expectType<number>(unicornsResult.exitCode);
 	expectType<string>(unicornsResult.exitCodeName);
 	expectType<string>(unicornsResult.stdout);
@@ -60,7 +57,6 @@ try {
 	const execaError: ExecaSyncError = error;
 
 	expectType<string>(execaError.message);
-	expectType<number | string>(execaError.code);
 	expectType<number>(execaError.exitCode);
 	expectType<string>(execaError.exitCodeName);
 	expectType<string>(execaError.stdout);
diff --git a/test.js b/test.js
index 9579764..da64172 100644
--- a/test.js
+++ b/test.js
@@ -141,7 +141,9 @@ test('stripFinalNewline in sync mode', t => {
 });
 
 test('stripFinalNewline in sync mode on failure', t => {
-	const {stderr} = t.throws(() => execa.sync('noop-throw', ['foo'], {stripFinalNewline: true}));
+	const {stderr} = t.throws(() => {
+		execa.sync('noop-throw', ['foo'], {stripFinalNewline: true});
+	});
 	t.is(stderr, 'foo');
 });
 
@@ -258,13 +260,12 @@ test('allow unknown exit code', async t => {
 });
 
 test('execa() returns code and failed properties', async t => {
-	const {code, exitCode, exitCodeName, failed} = await execa('noop', ['foo']);
-	t.is(code, 0);
+	const {exitCode, exitCodeName, failed} = await execa('noop', ['foo']);
 	t.is(exitCode, 0);
 	t.is(exitCodeName, 'SUCCESS');
 	t.false(failed);
 
-	const error = await t.throwsAsync(execa('exit', ['2']), {code: 2, message: getExitRegExp('2')});
+	const error = await t.throwsAsync(execa('exit', ['2']), {message: getExitRegExp('2')});
 	t.is(error.exitCode, 2);
 	const expectedName = process.platform === 'win32' ? 'Unknown system error -2' : 'ENOENT';
 	t.is(error.exitCodeName, expectedName);
@@ -318,6 +319,18 @@ test('result.killed is false if not killed, in sync mode', t => {
 	t.false(result.killed);
 });
 
+test('result.killed is false on process error', async t => {
+	const {killed} = await t.throwsAsync(execa('wrong command'));
+	t.false(killed);
+});
+
+test('result.killed is false on process error, in sync mode', t => {
+	const {killed} = t.throws(() => {
+		execa.sync('wrong command');
+	});
+	t.false(killed);
+});
+
 if (process.platform === 'darwin') {
 	test.cb('sanity check: child_process.exec also has killed.false if killed indirectly', t => {
 		const cp = childProcess.exec('forever', error => {
@@ -364,14 +377,14 @@ test('result.signal is undefined if process failed, but was not killed', async t
 	t.is(error.signal, undefined);
 });
 
-async function code(t, num) {
-	const error = await t.throwsAsync(execa('exit', [`${num}`]), {code: num, message: getExitRegExp(num)});
-	t.is(error.exitCode, num);
+async function testExitCode(t, num) {
+	const {exitCode} = await t.throwsAsync(execa('exit', [`${num}`]), {message: getExitRegExp(num)});
+	t.is(exitCode, num);
 }
 
-test('error.code is 2', code, 2);
-test('error.code is 3', code, 3);
-test('error.code is 4', code, 4);
+test('error.exitCode is 2', testExitCode, 2);
+test('error.exitCode is 3', testExitCode, 3);
+test('error.exitCode is 4', testExitCode, 4);
 
 test('timeout kills the process if it times out', async t => {
 	const error = await t.throwsAsync(execa('forever', {timeout: 1, message: TIMEOUT_REGEXP}));
@@ -613,7 +626,9 @@ test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mod
 });
 
 test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mode (failure)', t => {
-	const error = t.throws(() => execa.sync('fail'));
+	const error = t.throws(() => {
+		execa.sync('fail');
+	});
 	t.false(error.isCanceled);
 });