// 构建报告 HTML 渲染(Plan 4 Task 6——"报告 HTML 先写"窄窗口修的渲染面): // 沿用 Plan 2/3 模板纪律(import 域 report-html.ts 同款): // - 对象层(build.json)保留原文——JSON 归档/机器消费的语义层; // - 渲染层对**所有**动态文本经 escapeHtml(<>&"' 全转义)——报告动态文本含站点键/ // 幂等键/路径等操作者可控串,未转义渲染会使报告本身成为注入载体; // - 静态骨架无 script/事件属性;计数与清单按确定序渲染。 import type { BuildReport } from './build.service.js' /** HTML 转义(渲染层唯一动态文本通道):<>&"' 全转义,& 最先避免二次转义陷阱 */ function escapeHtml(text: string): string { return text.replace(/[&<>"']/g, (ch) => { switch (ch) { case '&': return '&' case '<': return '<' case '>': return '>' case '"': return '"' default: return ''' } }) } const esc = escapeHtml /** 构建报告 → HTML 片段(结构:概览/门禁/环境/快照计数/产物/耗时/问题清单) */ export function renderBuildReportHtml(report: BuildReport): string { const lines: string[] = [] lines.push('

构建报告

') lines.push('

概览

') lines.push( ``, ) lines.push('

门禁

') lines.push( ``, ) lines.push('

构建环境

') lines.push( ``, ) lines.push('

快照

') lines.push( ``, ) lines.push('

产物

') lines.push( ``, ) lines.push('

问题清单

') if (report.issues.length === 0) { lines.push('

(无)

') } else { lines.push('') } lines.push('

耗时

') lines.push(``) return lines.join('\n') }