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

Separate error creation logic into its own file #311

Merged
merged 3 commits into from
Jun 23, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Separate error handling into its own file
  • Loading branch information
ehmicky committed Jun 23, 2019
commit b04a3281f6c2ee5fe54e6747a9191beb5558a5a7
95 changes: 15 additions & 80 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';
const path = require('path');
const os = require('os');
const util = require('util');
const childProcess = require('child_process');
const crossSpawn = require('cross-spawn');
const stripFinalNewline = require('strip-final-newline');
Expand All @@ -11,6 +10,7 @@ const getStream = require('get-stream');
const mergeStream = require('merge-stream');
const pFinally = require('p-finally');
const onExit = require('signal-exit');
const makeError = require('./lib/error');
const normalizeStdio = require('./lib/stdio');

const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
Expand Down Expand Up @@ -158,80 +158,6 @@ const getPromiseResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuf
}
};

const makeError = (result, options) => {
const {stdout, stderr, signal} = result;
let {error} = result;
const {code, command, timedOut, isCanceled, killed, parsed: {options: {timeout}}} = options;

const [exitCodeName, exitCode] = getCode(result, code);

const prefix = getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled});
const message = `Command ${prefix}: ${command}`;

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

error.command = command;
delete error.code;
error.exitCode = exitCode;
error.exitCodeName = exitCodeName;
error.stdout = stdout;
error.stderr = stderr;

if ('all' in result) {
error.all = result.all;
}

if ('bufferedData' in error) {
delete error.bufferedData;
}

error.failed = true;
error.timedOut = timedOut;
error.isCanceled = isCanceled;
error.killed = killed && !timedOut;
// `signal` emitted on `spawned.on('exit')` event can be `null`. We normalize
// it to `undefined`
error.signal = signal || undefined;

return error;
};

const getCode = ({error = {}}, code) => {
if (error.code) {
return [error.code, os.constants.errno[error.code]];
}

if (Number.isInteger(code)) {
return [util.getSystemErrorName(-code), code];
}

return [];
};

const getErrorPrefix = ({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled}) => {
if (timedOut) {
return `timed out after ${timeout} milliseconds`;
}

if (isCanceled) {
return 'was canceled';
}

if (signal) {
return `was killed with ${signal}`;
}

if (exitCode !== undefined) {
return `failed with exit code ${exitCode} (${exitCodeName})`;
}

return 'failed';
};

const joinCommand = (file, args = []) => {
if (!Array.isArray(args)) {
return file;
Expand Down Expand Up @@ -372,7 +298,11 @@ const execa = (file, args, options) => {
spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
} catch (error) {
return mergePromise(new childProcess.ChildProcess(), () =>
Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
Promise.reject(makeError({
error,
stdout: '',
stderr: '',
all: '',
command,
parsed,
timedOut: false,
Expand Down Expand Up @@ -402,8 +332,8 @@ const execa = (file, args, options) => {
result.all = handleOutput(parsed.options, all);

if (result.error || result.code !== 0 || result.signal !== null) {
const error = makeError(result, {
code: result.code,
const error = makeError({
...result,
command,
parsed,
timedOut: context.timedOut,
Expand Down Expand Up @@ -455,7 +385,11 @@ module.exports.sync = (file, args, options) => {
try {
result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options);
} catch (error) {
throw makeError({error, stdout: '', stderr: '', all: ''}, {
throw makeError({
error,
stdout: '',
stderr: '',
all: '',
command,
parsed,
timedOut: false,
Expand All @@ -468,7 +402,8 @@ module.exports.sync = (file, args, options) => {
result.stderr = handleOutput(parsed.options, result.stderr, result.error);

if (result.error || result.status !== 0 || result.signal !== null) {
const error = makeError(result, {
const error = makeError({
...result,
code: result.status,
command,
parsed,
Expand Down
88 changes: 88 additions & 0 deletions lib/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict';
const os = require('os');
const util = require('util');

const getCode = (error, code) => {
if (error && error.code) {
return [error.code, os.constants.errno[error.code]];
}

if (Number.isInteger(code)) {
return [util.getSystemErrorName(-code), code];
}

return [];
};

const getErrorPrefix = ({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled}) => {
if (timedOut) {
return `timed out after ${timeout} milliseconds`;
}

if (isCanceled) {
return 'was canceled';
}

if (signal) {
return `was killed with ${signal}`;
}

if (exitCode !== undefined) {
return `failed with exit code ${exitCode} (${exitCodeName})`;
}

return 'failed';
};

const makeError = ({
stdout,
stderr,
all,
error,
signal,
code,
command,
timedOut,
isCanceled,
killed,
parsed: {options: {timeout}}
}) => {
const [exitCodeName, exitCode] = getCode(error, code);

const prefix = getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled});
const message = `Command ${prefix}: ${command}`;

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

error.command = command;
delete error.code;
error.exitCode = exitCode;
error.exitCodeName = exitCodeName;
error.stdout = stdout;
error.stderr = stderr;

if (all !== undefined) {
error.all = all;
}

if ('bufferedData' in error) {
delete error.bufferedData;
}

error.failed = true;
error.timedOut = timedOut;
error.isCanceled = isCanceled;
error.killed = killed && !timedOut;
// `signal` emitted on `spawned.on('exit')` event can be `null`. We normalize
// it to `undefined`
error.signal = signal || undefined;

return error;
};

module.exports = makeError;