| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
- // 独立复算 manifest 校验(与 runtimeAssets.js 的 validateManifest 同规则)
- // 并追加「文件是否真实可达」检查 —— 这一项曾因遗漏导致整批图标不显示:
- // manifest 的 path 写错时,Vite dev 会把不存在的路径兜底成 200 + index.html,
- // 状态码仍是 200,光看格式校验与 HTTP 状态都发现不了,只有对磁盘路径 exists 才拦得住。
- import { readFileSync, existsSync, statSync } from 'node:fs'
- import { createHash } from 'node:crypto'
- const manifest = JSON.parse(readFileSync('public/cockpit/assets/manifest.json', 'utf8'))
- const REQUIRED = ['key', 'path', 'source', 'format', 'dimensions', 'version', 'hash', 'status', 'fallbackKey', 'core']
- const STATUSES = new Set(['formal', 'temporary', 'derived', 'fallback'])
- const HASH = /^sha256:[0-9a-f]{64}$/
- const safe = (p) => typeof p === 'string' && p.length > 0 && !p.startsWith('/') && !p.includes('..') && !/^[a-z][a-z0-9+.-]*:/i.test(p)
- // 运行时解析层拼的是 `${BASE_URL}cockpit/${path}`,所以磁盘基准是 public/cockpit
- const RUNTIME_ROOT = 'public/cockpit'
- const errors = []
- const keys = new Set()
- for (const e of manifest.entries) {
- for (const f of REQUIRED) if (!Object.prototype.hasOwnProperty.call(e, f)) errors.push(`${e.key}: 缺字段 ${f}`)
- if (keys.has(e.key)) errors.push(`${e.key}: 重复 key`)
- keys.add(e.key)
- if (!safe(e.path)) errors.push(`${e.key}: 路径不安全`)
- if (!HASH.test(e.hash)) errors.push(`${e.key}: hash 格式非法`)
- if (!STATUSES.has(e.status)) errors.push(`${e.key}: status 非法`)
- if (!(e.dimensions.width > 0 && e.dimensions.height > 0)) errors.push(`${e.key}: dimensions 非法`)
- if (e.fallbackKey !== null && !keys.has(e.fallbackKey)) errors.push(`${e.key}: fallback 缺失`)
- // ① 文件真实存在(拼出的运行时路径必须命中磁盘)
- const abs = `${RUNTIME_ROOT}/${e.path}`
- if (!existsSync(abs)) {
- errors.push(`${e.key}: 文件不存在 -> ${abs}`)
- continue
- }
- // ② 不是 dev server 的 HTML 兜底页
- const buf = readFileSync(abs)
- if (buf.length > 0 && buf[0] === 0x3c /* '<' */ && buf.subarray(0, 15).toString().includes('!DOCTYPE')) {
- errors.push(`${e.key}: 取到的是 HTML 文档而非素材(路径指向了 dev 兜底页)`)
- continue
- }
- // ③ 内容 hash 与登记值一致
- const actual = createHash('sha256').update(buf).digest('hex')
- if (`sha256:${actual}` !== e.hash) errors.push(`${e.key}: hash 不匹配(文件已被改动,需重跑 build-manifest.mjs)`)
- }
- console.log('entries:', manifest.entries.length, '| release:', manifest.release)
- console.log(errors.length ? `FAIL:\n - ${errors.join('\n - ')}` : 'manifest 校验通过(含文件可达性与 hash 一致性)')
- console.log('status 分布:', manifest.entries.map((e) => `${e.key}=${e.status}`).join(' '))
- if (errors.length) process.exitCode = 1
|