import codeExcerpt, { type CodeExcerpt } from 'code-excerpt' import { readFileSync } from 'fs' import React from 'react' import StackUtils from 'stack-utils' import Box from './Box.js' import Text from './Text.js' /* eslint-disable custom-rules/no-process-cwd -- stack trace file:// paths are relative to the real OS cwd, not the virtual cwd */ // Error's source file is reported as file:///home/user/file.js // This function removes the file://[cwd] part const cleanupPath = (path: string | undefined): string | undefined => { return path?.replace(`file://${process.cwd()}/`, '') } let stackUtils: StackUtils | undefined function getStackUtils(): StackUtils { return (stackUtils ??= new StackUtils({ cwd: process.cwd(), internals: StackUtils.nodeInternals(), })) } /* eslint-enable custom-rules/no-process-cwd */ type Props = { readonly error: Error } export default function ErrorOverview({ error }: Props) { const stack = error.stack ? error.stack.split('\n').slice(1) : undefined const origin = stack ? getStackUtils().parseLine(stack[0]!) : undefined const filePath = cleanupPath(origin?.file) let excerpt: CodeExcerpt[] | undefined let lineWidth = 0 if (filePath && origin?.line) { try { // eslint-disable-next-line custom-rules/no-sync-fs -- sync render path; error overlay can't go async without suspense restructuring const sourceCode = readFileSync(filePath, 'utf8') excerpt = codeExcerpt(sourceCode, origin.line) if (excerpt) { for (const { line } of excerpt) { lineWidth = Math.max(lineWidth, String(line).length) } } } catch { // file not readable — skip source context } } return ( {' '} ERROR{' '} {error.message} {origin && filePath && ( {filePath}:{origin.line}:{origin.column} )} {origin && excerpt && ( {excerpt.map(({ line, value }) => ( {String(line).padStart(lineWidth, ' ')}: {' ' + value} ))} )} {error.stack && ( {error.stack .split('\n') .slice(1) .map(line => { const parsedLine = getStackUtils().parseLine(line) // If the line from the stack cannot be parsed, we print out the unparsed line. if (!parsedLine) { return ( - {line} ) } return ( - {parsedLine.function} {' '} ({cleanupPath(parsedLine.file) ?? ''}:{parsedLine.line}: {parsedLine.column}) ) })} )} ) }