Skip to content

Commit

Permalink
Add stdout/stderr to error messages (#397)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky authored and sindresorhus committed Dec 18, 2019
1 parent 09cd28a commit bc520f9
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 11 deletions.
15 changes: 11 additions & 4 deletions index.d.ts
Expand Up @@ -44,7 +44,7 @@ declare namespace execa {
This can be either an absolute path or a path relative to the `cwd` option.
Requires `preferLocal` to be `true`.
For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version in a child process.
@default process.execPath
Expand Down Expand Up @@ -336,14 +336,21 @@ declare namespace execa {
extends Error,
ExecaReturnBase<StdoutErrorType> {
/**
The error message.
Error message when the child process failed to run. In addition to the underlying error message, it also contains some information related to why the child process errored.
The child process stderr then stdout are appended to the end, separated with newlines and not interleaved.
*/
message: string;

/**
Original error message. This is `undefined` unless the child process exited due to an `error` event or a timeout.
This is the same as the `message` property except it does not include the child process stdout/stderr.
*/
shortMessage: string;

/**
Original error message. This is the same as the `message` property except it includes neither the child process stdout/stderr nor some additional information added by Execa.
The `message` property contains both the `originalMessage` and some additional information added by Execa.
This is `undefined` unless the child process exited due to an `error` event or a timeout.
*/
originalMessage?: string;
}
Expand Down
2 changes: 2 additions & 0 deletions index.test-d.ts
Expand Up @@ -40,6 +40,7 @@ try {
expectType<boolean>(execaError.killed);
expectType<string | undefined>(execaError.signal);
expectType<string | undefined>(execaError.signalDescription);
expectType<string>(execaError.shortMessage);
expectType<string | undefined>(execaError.originalMessage);
}

Expand Down Expand Up @@ -70,6 +71,7 @@ try {
expectType<boolean>(execaError.killed);
expectType<string | undefined>(execaError.signal);
expectType<string | undefined>(execaError.signalDescription);
expectType<string>(execaError.shortMessage);
expectType<string | undefined>(execaError.originalMessage);
}

Expand Down
7 changes: 5 additions & 2 deletions lib/error.js
Expand Up @@ -47,15 +47,18 @@ const makeError = ({
const errorCode = error && error.code;

const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled});
const message = `Command ${prefix}: ${command}`;
const execaMessage = `Command ${prefix}: ${command}`;
const shortMessage = error instanceof Error ? `${execaMessage}\n${error.message}` : execaMessage;
const message = [shortMessage, stderr, stdout].filter(Boolean).join('\n');

if (error instanceof Error) {
error.originalMessage = error.message;
error.message = `${message}\n${error.message}`;
error.message = message;
} else {
error = new Error(message);
}

error.shortMessage = shortMessage;
error.command = command;
error.exitCode = exitCode;
error.signal = signal;
Expand Down
24 changes: 20 additions & 4 deletions readme.md
Expand Up @@ -60,12 +60,13 @@ const execa = require('execa');
/*
{
message: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
errno: 'ENOENT',
errno: -2,
code: 'ENOENT',
syscall: 'spawn unknown',
path: 'unknown',
spawnargs: ['command'],
originalMessage: 'spawn unknown ENOENT',
shortMessage: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
command: 'unknown command',
stdout: '',
stderr: '',
Expand Down Expand Up @@ -112,12 +113,13 @@ try {
/*
{
message: 'Command failed with ENOENT: unknown command spawnSync unknown ENOENT',
errno: 'ENOENT',
errno: -2,
code: 'ENOENT',
syscall: 'spawnSync unknown',
path: 'unknown',
spawnargs: ['command'],
originalMessage: 'spawnSync unknown ENOENT',
shortMessage: 'Command failed with ENOENT: unknown command spawnSync unknown ENOENT',
command: 'unknown command',
stdout: '',
stderr: '',
Expand Down Expand Up @@ -302,13 +304,27 @@ A human-friendly description of the signal that was used to terminate the proces

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.

#### message

Type: `string`

Error message when the child process failed to run. In addition to the [underlying error message](#originalMessage), it also contains some information related to why the child process errored.

The child process [stderr](#stderr) then [stdout](#stdout) are appended to the end, separated with newlines and not interleaved.

#### shortMessage

Type: `string`

This is the same as the [`message` property](#message) except it does not include the child process stdout/stderr.

#### originalMessage

Type: `string | undefined`

Original error message. This is `undefined` unless the child process exited due to an `error` event or a timeout.
Original error message. This is the same as the `message` property except it includes neither the child process stdout/stderr nor some additional information added by Execa.

The `message` property contains both the `originalMessage` and some additional information added by Execa.
This is `undefined` unless the child process exited due to an `error` event or a timeout.

### options

Expand Down
20 changes: 19 additions & 1 deletion test/error.js
Expand Up @@ -51,7 +51,25 @@ test('exitCode is 3', testExitCode, 3);
test('exitCode is 4', testExitCode, 4);

test('error.message contains the command', async t => {
await t.throwsAsync(execa('exit', ['2', 'foo', 'bar'], {message: /exit 2 foo bar/}));
await t.throwsAsync(execa('exit', ['2', 'foo', 'bar']), {message: /exit 2 foo bar/});
});

test('error.message contains stdout/stderr if available', async t => {
const {message} = await t.throwsAsync(execa('echo-fail'));
t.true(message.includes('stderr'));
t.true(message.includes('stdout'));
});

test('error.message does not contain stdout/stderr if not available', async t => {
const {message} = await t.throwsAsync(execa('echo-fail', {stdio: 'ignore'}));
t.false(message.includes('stderr'));
t.false(message.includes('stdout'));
});

test('error.shortMessage does not contain stdout/stderr', async t => {
const {shortMessage} = await t.throwsAsync(execa('echo-fail'));
t.false(shortMessage.includes('stderr'));
t.false(shortMessage.includes('stdout'));
});

test('Original error.message is kept', async t => {
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/echo-fail
@@ -0,0 +1,5 @@
#!/usr/bin/env node
'use strict';
console.log('stdout');
console.error('stderr');
process.exit(1);

0 comments on commit bc520f9

Please sign in to comment.