mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 16:16:58 +10:00
109 lines
15 KiB
TypeScript
109 lines
15 KiB
TypeScript
|
|
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 <Box flexDirection="column" padding={1}>
|
||
|
|
<Box>
|
||
|
|
<Text backgroundColor="ansi:red" color="ansi:white">
|
||
|
|
{' '}
|
||
|
|
ERROR{' '}
|
||
|
|
</Text>
|
||
|
|
|
||
|
|
<Text> {error.message}</Text>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{origin && filePath && <Box marginTop={1}>
|
||
|
|
<Text dim>
|
||
|
|
{filePath}:{origin.line}:{origin.column}
|
||
|
|
</Text>
|
||
|
|
</Box>}
|
||
|
|
|
||
|
|
{origin && excerpt && <Box marginTop={1} flexDirection="column">
|
||
|
|
{excerpt.map(({
|
||
|
|
line: line_0,
|
||
|
|
value
|
||
|
|
}) => <Box key={line_0}>
|
||
|
|
<Box width={lineWidth + 1}>
|
||
|
|
<Text dim={line_0 !== origin.line} backgroundColor={line_0 === origin.line ? 'ansi:red' : undefined} color={line_0 === origin.line ? 'ansi:white' : undefined}>
|
||
|
|
{String(line_0).padStart(lineWidth, ' ')}:
|
||
|
|
</Text>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
<Text key={line_0} backgroundColor={line_0 === origin.line ? 'ansi:red' : undefined} color={line_0 === origin.line ? 'ansi:white' : undefined}>
|
||
|
|
{' ' + value}
|
||
|
|
</Text>
|
||
|
|
</Box>)}
|
||
|
|
</Box>}
|
||
|
|
|
||
|
|
{error.stack && <Box marginTop={1} flexDirection="column">
|
||
|
|
{error.stack.split('\n').slice(1).map(line_1 => {
|
||
|
|
const parsedLine = getStackUtils().parseLine(line_1);
|
||
|
|
|
||
|
|
// If the line from the stack cannot be parsed, we print out the unparsed line.
|
||
|
|
if (!parsedLine) {
|
||
|
|
return <Box key={line_1}>
|
||
|
|
<Text dim>- </Text>
|
||
|
|
<Text bold>{line_1}</Text>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
return <Box key={line_1}>
|
||
|
|
<Text dim>- </Text>
|
||
|
|
<Text bold>{parsedLine.function}</Text>
|
||
|
|
<Text dim>
|
||
|
|
{' '}
|
||
|
|
({cleanupPath(parsedLine.file) ?? ''}:{parsedLine.line}:
|
||
|
|
{parsedLine.column})
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
})}
|
||
|
|
</Box>}
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjb2RlRXhjZXJwdCIsIkNvZGVFeGNlcnB0IiwicmVhZEZpbGVTeW5jIiwiUmVhY3QiLCJTdGFja1V0aWxzIiwiQm94IiwiVGV4dCIsImNsZWFudXBQYXRoIiwicGF0aCIsInJlcGxhY2UiLCJwcm9jZXNzIiwiY3dkIiwic3RhY2tVdGlscyIsImdldFN0YWNrVXRpbHMiLCJpbnRlcm5hbHMiLCJub2RlSW50ZXJuYWxzIiwiUHJvcHMiLCJlcnJvciIsIkVycm9yIiwiRXJyb3JPdmVydmlldyIsInN0YWNrIiwic3BsaXQiLCJzbGljZSIsInVuZGVmaW5lZCIsIm9yaWdpbiIsInBhcnNlTGluZSIsImZpbGVQYXRoIiwiZmlsZSIsImV4Y2VycHQiLCJsaW5lV2lkdGgiLCJsaW5lIiwic291cmNlQ29kZSIsIk1hdGgiLCJtYXgiLCJTdHJpbmciLCJsZW5ndGgiLCJtZXNzYWdlIiwiY29sdW1uIiwibWFwIiwidmFsdWUiLCJwYWRTdGFydCIsInBhcnNlZExpbmUiLCJmdW5jdGlvbiJdLCJzb3VyY2VzIjpbIkVycm9yT3ZlcnZpZXcudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjb2RlRXhjZXJwdCwgeyB0eXBlIENvZGVFeGNlcnB0IH0gZnJvbSAnY29kZS1leGNlcnB0J1xuaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAnZnMnXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgU3RhY2tVdGlscyBmcm9tICdzdGFjay11dGlscydcbmltcG9ydCBCb3ggZnJvbSAnLi9Cb3guanMnXG5pbXBvcnQgVGV4dCBmcm9tICcuL1RleHQuanMnXG5cbi8qIGVzbGludC1kaXNhYmxlIGN1c3RvbS1ydWxlcy9uby1wcm9jZXNzLWN3ZCAtLSBzdGFjayB0cmFjZSBmaWxlOi8vIHBhdGhzIGFyZSByZWxhdGl2ZSB0byB0aGUgcmVhbCBPUyBjd2QsIG5vdCB0aGUgdmlydHVhbCBjd2QgKi9cblxuLy8gRXJyb3IncyBzb3VyY2UgZmlsZSBpcyByZXBvcnRlZCBhcyBmaWxlOi8vL2hvbWUvdXNlci9maWxlLmpzXG4vLyBUaGlzIGZ1bmN0aW9uIHJlbW92ZXMgdGhlIGZpbGU6Ly9bY3dkXSBwYXJ0XG5jb25zdCBjbGVhbnVwUGF0aCA9IChwYXRoOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcgfCB1bmRlZmluZWQgPT4ge1xuICByZXR1cm4gcGF0aD8ucmVwbGFjZShgZmlsZTovLyR7cHJvY2Vzcy5jd2QoKX0vYCwgJycpXG59XG5cbmxldCBzdGFja1V0aWxzOiBTdGFja1V0aWxzIHwgdW5kZWZpbmVkXG5mdW5jdGlvbiBnZXRTdGFja1V0aWxzKCk6IFN0YWNrVXRpbHMge1xuICByZXR1cm4gKHN0YWNrVXRpbHMgPz89IG5ldyBTdGFja1V0aWxzKHtcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gICAgaW50ZXJuYWxzOiBTdGFja1V0aWxzLm5vZGVJbnRlcm5hbHMoKSxcbiAgfSkpXG59XG5cbi8qIGVzbGludC1lbmFibGUgY3VzdG9tLXJ1bGVzL25vLXByb2Nlc3MtY3dkICovXG5cbnR5cGUgUHJvcHMgPSB7XG4gIHJlYWRvbmx5IGVycm9yOiBFcnJvclxufVxuXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBFcnJvck92ZXJ2aWV3KHsgZXJyb3IgfTogUHJvcHMpIHtcbiAgY29uc3Qgc3RhY2sgPSBlcnJvci5zdGFjayA/IGVycm9yLnN0YWNrLnNwbGl0KCdcXG4nKS5zbGljZSgxKSA6IHVuZGVmaW5lZFxuICBjb25zdCBvcmlnaW4gPSBzdGFjayA/IGdldFN0YWNrVXRpbHMoKS5wYXJzZUxpbmUoc3RhY2tbMF0hKSA6IHVuZGVmaW5lZFxuICBjb25zdCBmaWxlUGF0aCA9IGNsZWFudXBQYXRoKG9yaWdpbj8uZmlsZSlcbiAgbGV0IGV4Y2VycHQ6IENvZGVFeGNlcnB0W10gfCB1bmRlZmluZWRcbiAgbGV0IGxpbmVXaWR0aCA9IDBcblxuICBpZiAoZmlsZVBhdGggJiYgb3JpZ2luPy5saW5lKSB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjdXN0b20tcnVsZXMvbm8tc3luYy1mcyAtLSBzeW5jIHJlbmRlciBwYXRoOyBlcnJvciBvdmVybGF5IGNhbid0IGdvIGFzeW5jIHdpdGhvdXQgc3VzcGVuc2UgcmVzdHJ1Y3R1cmluZ1xuICAgICAgY29uc3Qgc291cmNlQ29kZSA9IHJlYWRGaWxlU3luYyhmaWxlUGF0aCwgJ3V0ZjgnKVxuICAgICAgZXhjZXJwdCA9IGNvZGVFeGNlcnB0KHNvdXJjZUNvZGUsIG9yaWdpbi5saW5lKVxuXG4gICAgICBpZiAoZXhjZXJwdCkge1xuICAgICAgICBmb3IgKGNvbnN0IHsgbGluZSB9IG9mIGV4Y2VycHQpIHtcbiAgICAgICAgICBsaW5lV2lkdGggPSBNYXRoLm1heChsaW5lV2lkdGgsIFN0cmluZyhsaW5lKS5sZW5ndGgpXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIGZpbGUgbm90IHJlYWRhYmxlIOKAlCBza2lwIHNvdXJjZSBjb250ZXh0XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBwYWRkaW5nPXsxfT5cbiAgICAgIDxCb3g+XG4gICAgICAgIDxUZXh0IGJhY2tncm91bmRDb2xvcj1cImFuc2k6cmVkXCIgY29sb3I9XCJhbnNpOndoaXRlXCI+XG4gICAgICAgICAgeycgJ31cbiAgICAgICAgICBFUlJPUnsnICd9XG4gICAgICAgIDwvVGV4dD5cblxuICAgICAgICA8VGV4dD4ge2Vycm9yLm1lc3NhZ2V9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG5cbiAgICAgIHtvcmlnaW4gJiYgZmlsZVBhdGggJiYgKFxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQgZGltPlxuICAgICAgICAgICAge2ZpbGVQYXRofTp7b3JpZ2luLmxpbmV9OntvcmlnaW4uY29sdW1ufVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICB7b3JpZ2luICYmIGV4Y2VycHQgJiYgKFxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0gZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICAgIHtleGNlcnB0Lm1hcCgoeyBsaW5lLCB2YWx1ZSB9KSA9PiAoXG4gICAgICAgICAgICA8Qm94IGtleT17bGluZX0+XG4gICAgICAgICAgICAgIDxCb3ggd2lkdGg9e2xpbmVXaWR0aCArIDF9PlxuICAgICAgICAgICAgICAgIDxUZXh0XG4gICAgICAgICAgICAgICAgICBkaW09e2xpbmUgIT09IG9yaWdpbi5saW5lfVxuICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZENvbG9yPXtcbiAgICAgICAgICA
|