UNPKG

execa

Version:
157 lines (136 loc) • 4.76 kB
import {inspect} from 'node:util'; import stripFinalNewline from 'strip-final-newline'; import {isUint8Array, uint8ArrayToString} from '../utils/uint-array.js'; import {fixCwdError} from '../arguments/cwd.js'; import {escapeLines} from '../arguments/escape.js'; import {getMaxBufferMessage} from '../io/max-buffer.js'; import {getSignalDescription} from '../terminate/signal.js'; import {DiscardedError, isExecaError} from './final-error.js'; // Computes `error.message`, `error.shortMessage` and `error.originalMessage` export const createMessages = ({ stdio, all, ipcOutput, originalError, signal, signalDescription, exitCode, escapedCommand, timedOut, isCanceled, isGracefullyCanceled, isMaxBuffer, isForcefullyTerminated, forceKillAfterDelay, killSignal, maxBuffer, timeout, cwd, }) => { const errorCode = getErrorCode(originalError); const prefix = getErrorPrefix({ originalError, timedOut, timeout, isMaxBuffer, maxBuffer, errorCode, signal, signalDescription, exitCode, isCanceled, isGracefullyCanceled, isForcefullyTerminated, forceKillAfterDelay, killSignal, }); const originalMessage = getOriginalMessage(originalError, cwd); const suffix = originalMessage === undefined ? '' : `\n${originalMessage}`; const shortMessage = `${prefix}: ${escapedCommand}${suffix}`; const messageStdio = all === undefined ? [stdio[2], stdio[1]] : [all]; const message = [ shortMessage, ...messageStdio, ...stdio.slice(3), ipcOutput.map(ipcMessage => serializeIpcMessage(ipcMessage)).join('\n'), ] .map(messagePart => escapeLines(stripFinalNewline(serializeMessagePart(messagePart)))) .filter(Boolean) .join('\n\n'); return {originalMessage, shortMessage, message}; }; // `DOMException`, e.g. the default `cancelSignal` reason, has a legacy numeric `code`, which is not an error code like `ENOENT` export const getErrorCode = error => typeof error?.code === 'string' ? error.code : undefined; const getErrorPrefix = ({ originalError, timedOut, timeout, isMaxBuffer, maxBuffer, errorCode, signal, signalDescription, exitCode, isCanceled, isGracefullyCanceled, isForcefullyTerminated, forceKillAfterDelay, killSignal, }) => { const forcefulSuffix = getForcefulSuffix(isForcefullyTerminated, forceKillAfterDelay); if (timedOut) { return `Command timed out after ${timeout} milliseconds${forcefulSuffix}`; } if (isGracefullyCanceled) { if (signal === undefined) { return `Command was gracefully canceled with exit code ${exitCode}`; } return isForcefullyTerminated ? `Command was gracefully canceled${forcefulSuffix}` : `Command was gracefully canceled with ${signal} (${signalDescription})`; } if (isCanceled) { return `Command was canceled${forcefulSuffix}`; } if (isMaxBuffer) { return `${getMaxBufferMessage(originalError, maxBuffer)}${forcefulSuffix}`; } if (errorCode !== undefined) { return `Command failed with ${errorCode}${forcefulSuffix}`; } if (isForcefullyTerminated) { return `Command was killed with ${killSignal} (${getSignalDescription(killSignal)})${forcefulSuffix}`; } if (signal !== undefined) { return `Command was killed with ${signal} (${signalDescription})`; } return exitCode === undefined ? 'Command failed' : `Command failed with exit code ${exitCode}`; }; const getForcefulSuffix = (isForcefullyTerminated, forceKillAfterDelay) => isForcefullyTerminated ? ` and was forcefully terminated after ${forceKillAfterDelay} milliseconds` : ''; const getOriginalMessage = (originalError, cwd) => { if (originalError instanceof DiscardedError) { return; } // An `ExecaError` has no `originalMessage` when it was not caused by another error, e.g. with a non-zero exit code const originalMessage = isExecaError(originalError) ? originalError.originalMessage ?? '' : String(originalError?.message ?? originalError); const escapedOriginalMessage = escapeLines(fixCwdError(originalMessage, cwd)); return escapedOriginalMessage === '' ? undefined : escapedOriginalMessage; }; const serializeIpcMessage = ipcMessage => typeof ipcMessage === 'string' ? ipcMessage : inspect(ipcMessage); // With the `lines` option, empty lines are kept. Only objects are omitted. const serializeMessagePart = messagePart => Array.isArray(messagePart) ? messagePart.filter(messageItem => isTextItem(messageItem)).map(messageItem => stripFinalNewline(serializeMessageItem(messageItem))).join('\n') : serializeMessageItem(messagePart); const isTextItem = messageItem => typeof messageItem === 'string' || isUint8Array(messageItem); const serializeMessageItem = messageItem => { if (typeof messageItem === 'string') { return messageItem; } return isUint8Array(messageItem) ? uint8ArrayToString(messageItem) : ''; };