Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stdout/stderr to error messages #397

Merged
merged 2 commits into from Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);