fix(preview+pipeline): 资产键编码归一——7 张全角名图片预览 404 修复;证据文档叙述修正
spec 复审发现(浏览器可见缺陷):manifest 资产行 exact_key 有 7 例为裸形 (文件名含全角括号/顿号/间隔号)——html/redirect 键源数据侧已编码、asset 键来自文件名本体是裸形;浏览器对裸非 ASCII 一律百分号编码后发请求,预览 按请求字节逐字匹配 → 裸键永不命中 → 两页 3 张图破图(dist 文件在盘; 生产面 nginx 先解码不受影响)。修补 pipeline.toManifestRoute:exact_key 归一为 URL 线形(可打印 ASCII 原样、其余 UTF-8 百分号编码大写 hex), target_file 仍盘上裸名、DB/渲染快照域不变。 回归三层:build-pipeline 注入裸全角资产行(红验证);preview-server 2c 非 ASCII redirect → Location → 资产终端 200;e2e 断言 ④c(7 条全查 跟随到终端 200)+ ⑦c(manifest 2772 键全 ASCII 不变量)。spec 复审复跑 (新库全链 job e059bf41)23/23 PASS,浏览器实测 3 张原破图全部加载。 证据文档:§5.2 失败计数修正(5×500+2×mojibake=7 例)、§2 ①c 措辞对齐 断言实义(抽前 3 标题)、§5.5 修补记录、计时/产物/job 面更新至复跑值。 验证:vitest 382 + 主题 26 全绿(408);typecheck 过;lint 裸跑零告警。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+32
-1
@@ -608,6 +608,28 @@ async function cmdChain(opts) {
|
||||
JSON.stringify(a4),
|
||||
a4.every((x) => x.status === 301 && x.locationMatch),
|
||||
)
|
||||
// spec 复审修补回归:非 ASCII redirect 跟随到资产终端——Location 编码形请求须命中
|
||||
// manifest 资产行(exact_key 已归一为同一编码线形)→ 200 + 图片 MIME(浏览器真实路径)
|
||||
const nonAsciiRedirects = redirectRows.filter((r) => /\P{ASCII}/u.test(r.location ?? ''))
|
||||
const a4c = []
|
||||
for (const row of nonAsciiRedirects) {
|
||||
const hop = await client.get(row.exact_key)
|
||||
const terminalKey = asciiLocationMirror(row.location ?? '')
|
||||
const terminal = await client.get(terminalKey)
|
||||
a4c.push({
|
||||
request: row.exact_key,
|
||||
location: terminalKey,
|
||||
hopStatus: hop.status,
|
||||
terminalStatus: terminal.status,
|
||||
terminalContentType: terminal.headers['content-type'] ?? null,
|
||||
})
|
||||
}
|
||||
record(
|
||||
`④c 非 ASCII redirect → 资产终端(${String(a4c.length)} 条全查)`,
|
||||
'301 → 跟随 Location 编码形 → 终端 200 + 图片 MIME(exact_key 编码线形命中)',
|
||||
JSON.stringify(a4c),
|
||||
a4c.length > 0 && a4c.every((x) => x.hopStatus === 301 && x.terminalStatus === 200 && String(x.terminalContentType).startsWith('image/')),
|
||||
)
|
||||
|
||||
// ===== 断言 ⑤ sitemap/robots(盘读) =====
|
||||
console.log('[assert] ⑤ sitemap/robots …')
|
||||
@@ -654,6 +676,15 @@ async function cmdChain(opts) {
|
||||
`ok=${String(redirectResults.length - redirectFailures.length)}/${String(redirectRows.length)}${redirectFailures.length === 0 ? '' : ` 首失败=${JSON.stringify(redirectFailures.slice(0, 5))}`}`,
|
||||
redirectFailures.length === 0 && redirectRows.length === 1746,
|
||||
)
|
||||
// spec 复审修补不变量:manifest 全键编码线形(纯 ASCII)——裸非 ASCII 键永不匹配
|
||||
// 浏览器请求形(浏览器对裸非 ASCII 一律百分号编码后发送)
|
||||
const nonAsciiKeys = manifest.routes.filter((r) => /\P{ASCII}/u.test(r.exact_key)).map((r) => r.exact_key)
|
||||
record(
|
||||
'⑦manifest 键线形',
|
||||
`全 ${String(manifest.routes.length)} 键纯 ASCII(编码线形——裸非 ASCII 键 = 预览永不命中)`,
|
||||
nonAsciiKeys.length === 0 ? 'nonAsciiKeys=0' : `nonAsciiKeys=${JSON.stringify(nonAsciiKeys.slice(0, 5))}`,
|
||||
nonAsciiKeys.length === 0,
|
||||
)
|
||||
|
||||
// ===== 断言 ⑨ 门禁全绿(validation_runs PASS) =====
|
||||
console.log('[assert] ⑨ 门禁 …')
|
||||
@@ -698,7 +729,7 @@ async function cmdChain(opts) {
|
||||
sqlCounts,
|
||||
timings: Object.fromEntries(Object.entries(timings).map(([k, v]) => [k, Math.round(v)])),
|
||||
assertions: rows,
|
||||
detail: { a1, a2, a3, a4, smoke: { htmlTotal: htmlRows.length, htmlFailures: htmlFailures.length, redirectTotal: redirectRows.length, redirectFailures: redirectFailures.length } },
|
||||
detail: { a1, a2, a3, a4, a4c, smoke: { htmlTotal: htmlRows.length, htmlFailures: htmlFailures.length, redirectTotal: redirectRows.length, redirectFailures: redirectFailures.length } },
|
||||
buildReport: {
|
||||
distPath: buildReport.artifacts.distPath,
|
||||
distFileCount: buildReport.artifacts.distFileCount,
|
||||
|
||||
Reference in New Issue
Block a user