UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

169 lines (166 loc) • 6.75 kB
import * as fs from "node:fs"; import { isAbsolute, join } from "node:path"; import { fileURLToPath } from "node:url"; import { stripVTControlCharacters } from "node:util"; import { escape } from "html-escaper"; import { bold, underline } from "kleur/colors"; import { removeLeadingForwardSlashWindows } from "../../path.js"; import { normalizePath } from "../../viteUtils.js"; import { AggregateError } from "../errors.js"; import { AstroErrorData } from "../index.js"; import { codeFrame } from "../printer.js"; import { normalizeLF } from "../utils.js"; function collectErrorMetadata(e, rootFolder) { const err = AggregateError.is(e) || Array.isArray(e.errors) ? e.errors : [e]; err.forEach((error) => { if (e.stack) { const stackInfo = collectInfoFromStacktrace(e); try { error.stack = stripVTControlCharacters(stackInfo.stack); } catch { } error.loc = stackInfo.loc; error.plugin = stackInfo.plugin; error.pluginCode = stackInfo.pluginCode; } const normalizedFile = normalizePath(error.loc?.file || ""); const normalizedRootFolder = removeLeadingForwardSlashWindows(rootFolder?.pathname || ""); if (error.loc?.file && rootFolder && (!normalizedFile?.startsWith(normalizedRootFolder) || !isAbsolute(normalizedFile))) { error.loc.file = join(fileURLToPath(rootFolder), error.loc.file); } if (error.loc && (!error.frame || !error.fullCode)) { try { const fileContents = fs.readFileSync(error.loc.file, "utf8"); if (!error.frame) { const frame = codeFrame(fileContents, error.loc); error.frame = stripVTControlCharacters(frame); } if (!error.fullCode) { error.fullCode = fileContents; } } catch { } } error.hint = generateHint(e); if (error.message) { try { error.message = stripVTControlCharacters(error.message); } catch { } } }); if (!AggregateError.is(e) && Array.isArray(e.errors)) { e.errors.forEach((buildError, i) => { const { location, pluginName, text } = buildError; if (text) { try { err[i].message = text; } catch { } } if (location) { err[i].loc = { file: location.file, line: location.line, column: location.column }; err[i].id = err[0].id || location?.file; } if (err[i].frame) { const errorLines = err[i].frame?.trim().split("\n"); if (errorLines) { err[i].frame = !/^\d/.test(errorLines[0]) ? errorLines?.slice(1).join("\n") : err[i].frame; } } const possibleFilePath = location?.file ?? err[i].id; if (possibleFilePath && err[i].loc && (!err[i].frame || !err[i].fullCode)) { try { const fileContents = fs.readFileSync(possibleFilePath, "utf8"); if (!err[i].frame) { err[i].frame = codeFrame(fileContents, { ...err[i].loc, file: possibleFilePath }); } err[i].fullCode = fileContents; } catch { err[i].fullCode = err[i].pluginCode; } } if (pluginName) { err[i].plugin = pluginName; } err[i].hint = generateHint(err[0]); }); } return err[0]; } function generateHint(err) { const commonBrowserAPIs = ["document", "window"]; if (/Unknown file extension "\.(?:jsx|vue|svelte|astro|css)" for /.test(err.message)) { return "You likely need to add this package to `vite.ssr.noExternal` in your astro config file."; } else if (commonBrowserAPIs.some((api) => err.toString().includes(api))) { const hint = `Browser APIs are not available on the server. ${err.loc?.file?.endsWith(".astro") ? "Move your code to a <script> tag outside of the frontmatter, so the code runs on the client." : "If the code is in a framework component, try to access these objects after rendering using lifecycle methods or use a `client:only` directive to make the component exclusively run on the client."} See https://docs.astro.build/en/guides/troubleshooting/#document-or-window-is-not-defined for more information. `; return hint; } return err.hint; } function collectInfoFromStacktrace(error) { let stackInfo = { stack: error.stack, plugin: error.plugin, pluginCode: error.pluginCode, loc: error.loc }; stackInfo.stack = normalizeLF(error.stack); const stackText = stripVTControlCharacters(error.stack); if (!stackInfo.loc || !stackInfo.loc.column && !stackInfo.loc.line) { const possibleFilePath = error.loc?.file || error.pluginCode || error.id || // TODO: this could be better, `src` might be something else stackText.split("\n").find((ln) => ln.includes("src") || ln.includes("node_modules")); const source = possibleFilePath?.replace?.(/^[^(]+\(([^)]+).*$/, "$1").replace(/^\s+at\s+/, ""); let file = source?.replace(/:\d+/g, ""); const location = /:(\d+):(\d+)/.exec(source) ?? []; const line = location[1]; const column = location[2]; if (file && line && column) { try { file = fileURLToPath(file); } catch { } stackInfo.loc = { file, line: Number.parseInt(line), column: Number.parseInt(column) }; } } if (!stackInfo.plugin) { stackInfo.plugin = /withastro\/astro\/packages\/integrations\/([\w-]+)/i.exec(stackText)?.at(1) || /(@astrojs\/[\w-]+)\/(server|client|index)/i.exec(stackText)?.at(1) || void 0; } stackInfo.stack = cleanErrorStack(error.stack); return stackInfo; } function cleanErrorStack(stack) { return stack.split(/\n/).map((l) => l.replace(/\/@fs\//g, "/")).join("\n"); } function getDocsForError(err) { if (err.name !== "UnknownError" && err.name in AstroErrorData) { return `https://docs.astro.build/en/reference/errors/${getKebabErrorName(err.name)}/`; } return void 0; function getKebabErrorName(errorName) { return errorName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); } } const linkRegex = /\[([^[]+)\]\((.*)\)/g; const boldRegex = /\*\*(.+)\*\*/g; const urlRegex = / ((?:https?|ftp):\/\/[-\w+&@#\\/%?=~|!:,.;]*[-\w+&@#\\/%=~|])/gi; const codeRegex = /`([^`]+)`/g; function renderErrorMarkdown(markdown, target) { if (target === "html") { return escape(markdown).replace(linkRegex, `<a href="$2" target="_blank">$1</a>`).replace(boldRegex, "<b>$1</b>").replace(urlRegex, ' <a href="$1" target="_blank">$1</a>').replace(codeRegex, "<code>$1</code>"); } else { return markdown.replace(linkRegex, (_, m1, m2) => `${bold(m1)} ${underline(m2)}`).replace(urlRegex, (fullMatch) => ` ${underline(fullMatch.trim())}`).replace(boldRegex, (_, m1) => `${bold(m1)}`); } } export { collectErrorMetadata, getDocsForError, renderErrorMarkdown };