diff --git a/index.d.ts b/index.d.ts index f787fe7566..a615237deb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -267,9 +267,18 @@ declare namespace execa { killed: boolean; /** - The signal that was used to terminate the process. + The name of the signal that was used to terminate the process. For example, `SIGFPE`. + + If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`. */ signal?: string; + + /** + A human-friendly description of the signal that was used to terminate the process. For example, `Floating point arithmetic error`. + + If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`. It is also `undefined` when the signal is very uncommon which should seldomly happen. + */ + signalDescription?: string; } interface ExecaSyncReturnValue diff --git a/index.test-d.ts b/index.test-d.ts index 5729538879..8d5a5fbc86 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -25,6 +25,7 @@ try { expectType(unicornsResult.isCanceled); expectType(unicornsResult.killed); expectType(unicornsResult.signal); + expectType(unicornsResult.signalDescription); } catch (error) { const execaError: ExecaError = error; @@ -38,6 +39,7 @@ try { expectType(execaError.isCanceled); expectType(execaError.killed); expectType(execaError.signal); + expectType(execaError.signalDescription); expectType(execaError.originalMessage); } @@ -53,6 +55,7 @@ try { expectError(unicornsResult.isCanceled); expectType(unicornsResult.killed); expectType(unicornsResult.signal); + expectType(unicornsResult.signalDescription); } catch (error) { const execaError: ExecaSyncError = error; @@ -66,6 +69,7 @@ try { expectError(execaError.isCanceled); expectType(execaError.killed); expectType(execaError.signal); + expectType(execaError.signalDescription); expectType(execaError.originalMessage); } diff --git a/lib/error.js b/lib/error.js index b651823832..808ad4384b 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,5 +1,7 @@ 'use strict'; -const getErrorPrefix = ({timedOut, timeout, errorCode, signal, exitCode, isCanceled}) => { +const {signalsByName} = require('human-signals'); + +const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { if (timedOut) { return `timed out after ${timeout} milliseconds`; } @@ -13,7 +15,7 @@ const getErrorPrefix = ({timedOut, timeout, errorCode, signal, exitCode, isCance } if (signal !== undefined) { - return `was killed with ${signal}`; + return `was killed with ${signal} (${signalDescription})`; } if (exitCode !== undefined) { @@ -40,10 +42,11 @@ const makeError = ({ // We normalize them to `undefined` exitCode = exitCode === null ? undefined : exitCode; signal = signal === null ? undefined : signal; + const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; const errorCode = error && error.code; - const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, exitCode, isCanceled}); + const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); const message = `Command ${prefix}: ${command}`; if (error instanceof Error) { @@ -56,6 +59,7 @@ const makeError = ({ error.command = command; error.exitCode = exitCode; error.signal = signal; + error.signalDescription = signalDescription; error.stdout = stdout; error.stderr = stderr; diff --git a/package.json b/package.json index b87ea38f07..ce8f1a4f5f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", + "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", diff --git a/readme.md b/readme.md index 69cb1fcf53..18f5099318 100644 --- a/readme.md +++ b/readme.md @@ -270,7 +270,17 @@ Whether the process was killed. Type: `string | undefined` -The signal that was used to terminate the process. +The name of the signal that was used to terminate the process. For example, `SIGFPE`. + +If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`. + +#### signalDescription + +Type: `string | undefined` + +A human-friendly description of the signal that was used to terminate the process. For example, `Floating point arithmetic error`. + +If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`. It is also `undefined` when the signal is very uncommon which should seldomly happen. #### originalMessage diff --git a/test/error.js b/test/error.js index a16581342b..7a79312c52 100644 --- a/test/error.js +++ b/test/error.js @@ -133,6 +133,15 @@ if (process.platform !== 'win32') { t.is(signal, 'SIGINT'); }); + test('error.signalDescription is defined', async t => { + const subprocess = execa('noop'); + + process.kill(subprocess.pid, 'SIGINT'); + + const {signalDescription} = await t.throwsAsync(subprocess, {message: /User interruption with CTRL-C/}); + t.is(signalDescription, 'User interruption with CTRL-C'); + }); + test('error.signal is SIGTERM', async t => { const subprocess = execa('noop'); @@ -167,6 +176,11 @@ test('result.signal is undefined if process failed, but was not killed', async t t.is(signal, undefined); }); +test('result.signalDescription is undefined for successful execution', async t => { + const {signalDescription} = await execa('noop'); + t.is(signalDescription, undefined); +}); + test('error.code is undefined on success', async t => { const {code} = await execa('noop'); t.is(code, undefined);