UNPKG

@shopify/cli-kit

Version:

A set of utilities, interfaces, and models that are common across all the platform features

202 lines • 7.26 kB
import { renderFatalError } from './ui.js'; import { stringifyMessage, TokenizedString } from '../../public/node/output.js'; import { normalizePath } from '../../public/node/path.js'; import { tokenItemToString } from '../../private/node/ui/components/TokenizedText.js'; import { Errors } from '@oclif/core'; export { ExtendableError } from 'ts-error'; export var FatalErrorType; (function (FatalErrorType) { FatalErrorType[FatalErrorType["Abort"] = 0] = "Abort"; FatalErrorType[FatalErrorType["AbortSilent"] = 1] = "AbortSilent"; FatalErrorType[FatalErrorType["Bug"] = 2] = "Bug"; })(FatalErrorType || (FatalErrorType = {})); export class CancelExecution extends Error { } /** * A fatal error represents an error shouldn't be rescued and that causes the execution to terminate. * There shouldn't be code that catches fatal errors. */ export class FatalError extends Error { /** * Creates a new FatalError error. * * @param message - The error message. * @param type - The type of fatal error. * @param tryMessage - The message that recommends next steps to the user. * You can pass a string a {@link TokenizedString} or a {@link TokenItem} * if you need to style the message inside the error Banner component. * @param nextSteps - Message to show as "next steps" with suggestions to solve the issue. * @param customSections - Custom sections to show in the error banner. To be used if nextSteps is not enough. */ constructor(message, type, tryMessage = null, nextSteps, customSections) { const messageIsOutputMessage = typeof message === 'string' || 'value' in message; super(messageIsOutputMessage ? stringifyMessage(message) : tokenItemToString(message)); if (tryMessage) { if (tryMessage instanceof TokenizedString) { this.tryMessage = stringifyMessage(tryMessage); } else { this.tryMessage = tryMessage; } } else { this.tryMessage = null; } this.type = type; this.nextSteps = nextSteps; this.customSections = customSections; this.skipOclifErrorHandling = true; if (!messageIsOutputMessage) { this.formattedMessage = message; } } } /** * An abort error is a fatal error that shouldn't be reported as a bug. * Those usually represent unexpected scenarios that we can't handle and that usually require some action from the developer. */ export class AbortError extends FatalError { constructor(message, tryMessage = null, nextSteps, customSections) { super(message, FatalErrorType.Abort, tryMessage, nextSteps, customSections); } } /** * An external error is similar to Abort but has extra command and args attributes. * This is useful to represent errors coming from external commands, usually executed by execa. */ export class ExternalError extends FatalError { constructor(message, command, args, tryMessage = null) { super(message, FatalErrorType.Abort, tryMessage); this.command = command; this.args = args; } } export class AbortSilentError extends FatalError { constructor() { super('', FatalErrorType.AbortSilent); } } /** * A bug error is an error that represents a bug and therefore should be reported. */ export class BugError extends FatalError { constructor(message, tryMessage = null) { super(message, FatalErrorType.Bug, tryMessage); } } /** * A function that handles errors that blow up in the CLI. * * @param error - Error to be handled. * @returns A promise that resolves with the error passed. */ export async function handler(error) { let fatal; if (isFatal(error)) { fatal = error; } else if (typeof error === 'string') { fatal = new BugError(error); } else if (error instanceof Error) { fatal = new BugError(error.message); fatal.stack = error.stack; } else { // errors can come in all shapes and sizes... // eslint-disable-next-line @typescript-eslint/no-explicit-any const maybeError = error; fatal = new BugError(maybeError?.message ?? 'Unknown error'); if (maybeError?.stack) { fatal.stack = maybeError?.stack; } } renderFatalError(fatal); return Promise.resolve(error); } /** * A function that maps an error to an Abort with the stack trace when coming from the CLI. * * @param error - Error to be mapped. * @returns A promise that resolves with the new error object. */ export function errorMapper(error) { if (error instanceof Errors.CLIError) { const mappedError = new AbortError(error.message); mappedError.stack = error.stack; return Promise.resolve(mappedError); } else { return Promise.resolve(error); } } /** * A function that checks if an error is a fatal one. * * @param error - Error to be checked. * @returns A boolean indicating if the error is a fatal one. */ function isFatal(error) { try { return Object.prototype.hasOwnProperty.call(error, 'type'); // eslint-disable-next-line no-catch-all/no-catch-all } catch { return false; } } /** * A function that checks if an error should be reported as unexpected. * * @param error - Error to be checked. * @returns A boolean indicating if the error should be reported as unexpected. */ export function shouldReportErrorAsUnexpected(error) { if (!isFatal(error)) { // this means its not one of the CLI wrapped errors if (error instanceof Error) { const message = error.message; return !errorMessageImpliesEnvironmentIssue(message); } return true; } if (error.type === FatalErrorType.Bug) { return true; } return false; } /** * Stack traces usually have file:// - we strip that and also remove the Windows drive designation. * * @param filePath - Path to be cleaned. * @returns The cleaned path. */ export function cleanSingleStackTracePath(filePath) { return normalizePath(filePath) .replace('file:/', '/') .replace(/^\/?[A-Z]:/, ''); } /** * There are certain errors that we know are not due to a CLI bug, but are environmental/user error. * * @param message - The error message to check. * @returns A boolean indicating if the error message implies an environment issue. */ function errorMessageImpliesEnvironmentIssue(message) { const environmentIssueMessages = [ 'EPERM: operation not permitted, scandir', 'EPERM: operation not permitted, rename', 'EACCES: permission denied', 'EPERM: operation not permitted, symlink', 'This version of npm supports the following node versions', 'EBUSY: resource busy or locked', 'ENOTEMPTY: directory not empty', 'getaddrinfo ENOTFOUND', 'Client network socket disconnected before secure TLS connection was established', 'spawn EPERM', 'socket hang up', ]; const anyMatches = environmentIssueMessages.some((issueMessage) => message.includes(issueMessage)); return anyMatches; } //# sourceMappingURL=error.js.map