UNPKG

pretty-print-error

Version:

Formats errors as nice strings with colors

149 lines (135 loc) 3.89 kB
let isNode = false; try { isNode = process.argv0 != null; } catch (err) { // ignored } const fakeKleur = { red: (value: string) => value, gray: (value: string) => value, magenta: (value: string) => value, }; let autoDetectedKleur = fakeKleur; if (isNode) { try { // I use eval("require") here instead of just require, // so that webpack et. al don't try to bundle kleur into // browser stuff, cause it's not very useful there. autoDetectedKleur = eval("require")("kleur"); } catch (err) { // ignored } } let util: any = {}; if (isNode) { // I use eval("require") here instead of just require, // so that webpack et. al don't try to bundle util into // browser stuff, since it won't be used even if it's // bundled, and because most util shims for browsers // are huge. try { util = eval("require")("util"); } catch (err) { // ignored } } /** * Formats an Error into a string suitable for printing. * * @param err The Error object to format. If the value you pass in isn't error-like, it'll get formatted via `String()`. * @param options Options that affect the format of the returned string. * @returns A formatted string containing information about the error. */ export function formatError( err: unknown, { color = true, lineFilter = (line) => !/\(node:/.test(line), }: { /** * Whether to use ANSI color escape sequences in the output string. * * This does nothing in the browser. */ color?: boolean; /** * A function that determines whether a given stack trace line * should be included in the formatted output. * * The default filter omits lines from node's internal functions. * To override this behavior, pass `lineFilter: () => true` */ lineFilter?: (line: string) => boolean; } = {} ): string { const kleur = color ? autoDetectedKleur : fakeKleur; let prettyErr = String(err); if ( typeof err === "object" && err != null && // @ts-ignore typeof err.name === "string" && // @ts-ignore typeof err.message === "string" && // @ts-ignore typeof err.stack === "string" ) { const error = err as Error; prettyErr = kleur.red(error.name) + ": " + error.message.replace(new RegExp(`^${error.name}[: ]*`), "") + "\n" + (error.stack || "") .split("\n") .map((line) => line.trim()) .filter(lineFilter) .map((line) => { if (line.startsWith(error.name + ": " + error.message)) return null; if (line.startsWith("at")) { return " " + kleur.gray(line); } return line; }) .filter(Boolean) .join("\n"); } if (typeof err === "object" && err != null) { const propNames = Object.getOwnPropertyNames(err).filter( (name) => name !== "stack" && name !== "message" ); if (propNames.length > 0) { const props = {}; propNames.forEach((name) => { props[name] = err[name]; }); let propertiesString; if (typeof util.inspect === "function") { propertiesString = util.inspect(props, { depth: Infinity, colors: color, }); } else { try { propertiesString = JSON.stringify(props); } catch (err) { propertiesString = [ "{", " the properties object couldn't be converted to JSON :(", "", " the error that happened while stringifying it was:", formatError(err) .split("\n") .map((line) => " " + line) .join("\n"), "}", ].join("\n"); } } prettyErr += kleur.magenta("\nThe above error also had these properties on it:\n") + propertiesString; } } return prettyErr; }