/** * error-overlay.ts — Dev 模式 HTML 错误覆盖层渲染器 * * 接受一个 Error 对象和 projectRoot 路径,生成完整的 HTML 错误覆盖层字符串。 * 功能: * - 解析 Error.stack(--enable-source-maps 翻译后为 .ts 路径) * - 区分用户代码帧(展开 + 源码上下文)与内部帧(折叠) * - 支持 dark / light 主题 * - vscode:// 链接直接跳转到出错行 * - VextValidationError 专属:字段级错误列表 * - 最多展示 N 帧(防止超深调用栈撑爆 HTML) * * 设计约束: * - 不 import 任何框架类型(error-overlay.ts 是框架内部工具,不暴露到公共 API) * - 使用 fs.readFileSync 同步读取(仅 dev 模式错误路径,性能可接受) * - 渲染失败时抛出异常,由外层 error-handler.ts 的 try/catch 降级到 JSON * * @module lib/dev/error-overlay * @see requirements/dev-error-overlay/02-技术方案.md §2.1 */ /** * 解析后的堆栈帧 */ export interface StackFrame { /** 函数名(未知时为 '') */ fn: string; /** 绝对文件路径(--enable-source-maps 翻译后为 .ts) */ file: string; /** 行号(1-based) */ line: number; /** 列号(1-based) */ col: number; } /** * 源码上下文(出错行 ±3 行) */ export interface SourceContext { lines: Array<{ /** 行号(1-based) */ num: number; /** 行内容(原始文本) */ content: string; /** 是否为出错行(高亮) */ isError: boolean; }>; } /** * renderDevErrorPage 的可选配置 */ export interface DevOverlayOptions { /** 主题:深色 / 浅色(默认 'dark') */ theme?: "dark" | "light"; /** 最多显示的堆栈帧数(默认 25) */ maxFrames?: number; } /** * HTML 特殊字符转义(防止 XSS) */ export declare function escapeHtml(str: string): string; /** * 解析 Error.stack 字符串为 StackFrame 数组 * * 支持两种格式: * - " at FunctionName (file:line:col)" * - " at file:line:col" * - Windows 路径:反斜杠会被处理 * - node: 内置模块:file 以 "node:" 开头 */ export declare function parseStackTrace(stack: string): StackFrame[]; /** * 将帧文件路径解析为绝对路径 * * --enable-source-maps 翻译后的堆栈路径存在两种偏差: * A. 相对路径(如 "../src/routes/index.ts") * B. 绝对但层级偏移(如 "E:/MySelf/src/routes/index.ts", * 实际应为 "E:/MySelf/vext-test/src/routes/index.ts") * * 这是因为 esbuild sourcemap 的 sources 路径相对于编译产物目录(.vext/dev/), * Node.js --enable-source-maps 解析时可能丢失中间层级。 * * 解析策略(通用后缀匹配,按优先级): * 1. 已是绝对路径且文件存在 → 直接返回 * 2. "node:" 前缀 → 直接返回 * 3. 从最长尾部到最短(至少 2 段)逐一拼接 projectRoot 检查是否存在 * e.g. "E:\MySelf\src\routes\index.ts" → 尝试 projectRoot/MySelf/src/routes/index.ts(否) * → 尝试 projectRoot/src/routes/index.ts(是!) * 该策略覆盖 src/、plugins/、middlewares/、services/ 等任意子目录 * 4. 相对路径 → path.resolve(projectRoot, file) * 5. 以上都失败 → 返回原始路径(graceful 降级) */ export declare function resolveFramePath(file: string, projectRoot: string): string; /** * 判断帧是否为用户代码(而非框架内部或 node_modules) * * 条件: * 1. file 以 projectRoot 开头 * 2. file 不包含 'node_modules' * 3. file 不以 'node:' 开头(Node.js 内置模块) * 4. file 不包含 '.vext/dev/'(未翻译的编译产物,--enable-source-maps 应已翻译) */ export declare function isUserFrame(frame: StackFrame, projectRoot: string): boolean; /** * 读取文件中指定行号 ±3 行的上下文 * * 边界处理: * - 文件不存在 → 返回 null(E2) * - 文件读取失败 → 返回 null(E3) * - 行号超出范围 → 取可用范围 */ export declare function getSourceContext(file: string, errorLine: number): SourceContext | null; /** * renderDevErrorPage — 生成完整的 HTML 错误覆盖层字符串 * * @param err 抛出的错误(任意类型,非 Error 对象会被转换) * @param projectRoot 用户项目根目录(绝对路径),用于区分用户帧/内部帧 * @param options 可选配置(主题、最大帧数) * @returns 完整的 HTML 字符串(可直接作为 HTTP 响应体) */ export declare function renderDevErrorPage(err: unknown, projectRoot: string, options?: DevOverlayOptions): string;