import { Layout } from "./chunk-VE4LENUR.js"; import { ErrorCause } from "./chunk-PINJDICN.js"; import { ErrorInfo } from "./chunk-3X66E37A.js"; import { ErrorMetadata } from "./chunk-UKBVLD72.js"; import { ErrorStack } from "./chunk-JAN2TFI2.js"; import { ErrorStackSource } from "./chunk-4XB2BYKC.js"; import "./chunk-4L7RY2JA.js"; import { Header } from "./chunk-PUHGL6HA.js"; import "./chunk-OSUFJZHZ.js"; import { BaseComponent } from "./chunk-4YEN7HVQ.js"; // src/youch.ts import cookie from "cookie"; import { ErrorParser } from "youch-core"; // src/metadata.ts var Metadata = class { #groups = {}; /** * Converts value to an array (if not an array already) */ #toArray(value) { return Array.isArray(value) ? value : [value]; } /** * Define a group, its sections and their rows. In case of * existing groups/sections, the new data will be merged * with the existing data */ group(name, sections) { this.#groups[name] = this.#groups[name] ?? {}; Object.keys(sections).forEach((section) => { if (!this.#groups[name][section]) { this.#groups[name][section] = sections[section]; } else { this.#groups[name][section] = this.#toArray(this.#groups[name][section]); this.#groups[name][section].push(...this.#toArray(sections[section])); } }); return this; } /** * Returns the existing metadata groups, sections and * rows. */ toJSON() { return this.#groups; } }; // src/templates.ts import { createScript, createStyleSheet } from "@poppinss/dumper/html"; var Templates = class { constructor(devMode) { this.devMode = devMode; this.#knownTemplates = { layout: new Layout(devMode), header: new Header(devMode), errorInfo: new ErrorInfo(devMode), errorStack: new ErrorStack(devMode), errorStackSource: new ErrorStackSource(devMode), errorCause: new ErrorCause(devMode), errorMetadata: new ErrorMetadata(devMode) }; } #knownTemplates; #styles = /* @__PURE__ */ new Map([["global", createStyleSheet()]]); #scripts = /* @__PURE__ */ new Map([["global", createScript()]]); /** * Returns a collection of style and script tags to dump * inside the document HEAD. */ #getStylesAndScripts(cspNonce) { let customInjectedStyles = ""; const styles = []; const scripts = []; const cspNonceAttr = cspNonce ? ` nonce="${cspNonce}"` : ""; this.#styles.forEach((bucket, name) => { if (name === "injected") { customInjectedStyles = ``; } else { styles.push(``); } }); this.#scripts.forEach((bucket, name) => { scripts.push(``); }); return { styles: `${styles.join("\n")} ${customInjectedStyles}`, scripts: scripts.join("\n") }; } /** * Collects styles and scripts for components as we render * them. */ async #collectStylesAndScripts(templateName) { if (!this.#styles.has(templateName)) { const styles = await this.#knownTemplates[templateName].getStyles(); if (styles) { this.#styles.set(templateName, styles); } } if (!this.#scripts.has(templateName)) { const script = await this.#knownTemplates[templateName].getScript(); if (script) { this.#scripts.set(templateName, script); } } } /** * Returns the HTML for a given template */ async #tmplToHTML(templateName, props) { const component = this.#knownTemplates[templateName]; if (!component) { throw new Error(`Invalid template "${templateName}"`); } await this.#collectStylesAndScripts(templateName); return component.toHTML(props); } /** * Returns the ANSI output for a given template */ async #tmplToANSI(templateName, props) { const component = this.#knownTemplates[templateName]; if (!component) { throw new Error(`Invalid template "${templateName}"`); } return component.toANSI(props); } /** * Define a custom component to be used in place of the default component. * Overriding components allows you control the HTML layout, styles and * the frontend scripts of an HTML fragment. */ use(templateName, component) { this.#knownTemplates[templateName] = component; return this; } /** * Inject custom styles to the document. Injected styles are * always placed after the global and the components style * tags. */ injectStyles(cssFragment) { let injectedStyles = this.#styles.get("injected") ?? ""; injectedStyles += ` ${cssFragment}`; this.#styles.set("injected", injectedStyles); return this; } /** * Returns the HTML output for the given parsed error */ async toHTML(props) { const html = await this.#tmplToHTML("layout", { title: props.title, ide: props.ide, cspNonce: props.cspNonce, children: async () => { const header = await this.#tmplToHTML("header", props); const info = await this.#tmplToHTML("errorInfo", props); const stackTrace = await this.#tmplToHTML("errorStack", { ide: process.env.EDITOR ?? "vscode", sourceCodeRenderer: (error, frame) => { return this.#tmplToHTML("errorStackSource", { error, frame, ide: props.ide, cspNonce: props.cspNonce }); }, ...props }); const cause = await this.#tmplToHTML("errorCause", props); const metadata = await this.#tmplToHTML("errorMetadata", props); return `${header}${info}${stackTrace}${cause}${metadata}`; } }); const { scripts, styles } = this.#getStylesAndScripts(props.cspNonce); return html.replace("", styles).replace("", scripts); } /** * Returns the ANSI output to be printed on the terminal */ async toANSI(props) { const ansiOutput = await this.#tmplToANSI("layout", { title: props.title, children: async () => { const header = await this.#tmplToANSI("header", {}); const info = await this.#tmplToANSI("errorInfo", props); const stackTrace = await this.#tmplToANSI("errorStack", { ide: process.env.EDITOR ?? "vscode", sourceCodeRenderer: (error, frame) => { return this.#tmplToANSI("errorStackSource", { error, frame }); }, ...props }); const cause = await this.#tmplToANSI("errorCause", props); const metadata = await this.#tmplToANSI("errorMetadata", props); return `${header}${info}${stackTrace}${cause}${metadata}`; } }); return ansiOutput; } }; // src/youch.ts var Youch = class { /** * Properties to be shared with the Error parser */ #sourceLoader; #parsers = []; #transformers = []; /** * Manage templates used for converting error to the HTML * output */ templates = new Templates(false); /** * Define metadata to be displayed alongside the error output */ metadata = new Metadata(); /** * Creates an instance of the ErrorParser and applies the * source loader, parsers and transformers on it */ #createErrorParser(options) { const errorParser = new ErrorParser(options); if (this.#sourceLoader) { errorParser.defineSourceLoader(this.#sourceLoader); } this.#parsers.forEach((parser) => errorParser.useParser(parser)); this.#transformers.forEach((transformer) => errorParser.useTransformer(transformer)); return errorParser; } /** * Defines the request properties as a metadata group */ #defineRequestMetadataGroup(request) { if (!request || Object.keys(request).length === 0) { return; } this.metadata.group("Request", { ...request.url ? { url: { key: "URL", value: request.url } } : {}, ...request.method ? { method: { key: "Method", value: request.method } } : {}, ...request.headers ? { headers: Object.keys(request.headers).map((key) => { const value = request.headers[key]; return { key, value: key === "cookie" ? { ...cookie.parse(value) } : value }; }) } : {} }); } /** * Define custom implementation for loading the source code * of a stack frame. */ defineSourceLoader(loader) { this.#sourceLoader = loader; return this; } /** * Define a custom parser. Parsers are executed before the * error gets parsed and provides you with an option to * modify the error */ useParser(parser) { this.#parsers.push(parser); return this; } /** * Define a custom transformer. Transformers are executed * after the error has been parsed and can mutate the * properties of the parsed error. */ useTransformer(transformer) { this.#transformers.push(transformer); return this; } /** * Parses error to JSON */ async toJSON(error, options) { options = { ...options }; return this.#createErrorParser({ offset: options.offset }).parse(error); } /** * Render error to HTML */ async toHTML(error, options) { options = { ...options }; this.#defineRequestMetadataGroup(options.request); const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error); return this.templates.toHTML({ title: options.title ?? "An error has occurred", ide: options.ide ?? process.env.IDE ?? "vscode", cspNonce: options.cspNonce, error: parsedError, metadata: this.metadata }); } /** * Render error to ANSI output */ async toANSI(error, options) { options = { ...options }; const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error); return this.templates.toANSI({ title: "", error: parsedError, metadata: this.metadata }); } }; export { BaseComponent, Metadata, Youch };