chore: reduce design pipeline to runtime asset checks

This commit is contained in:
2026-08-12 18:24:40 +08:00
parent 964262f693
commit 5dabcd175e
30 changed files with 106 additions and 1698 deletions
@@ -1,9 +1,8 @@
import { createHash } from 'node:crypto'
import { readFile, readdir } from 'node:fs/promises'
import path from 'node:path'
import { validateAssetBuildManifest } from './asset-build-manifest.mjs'
const formalManifestKinds = new Set(['asset-build-manifest', 'runtime-asset-inventory'])
const formalManifestKinds = new Set(['runtime-asset-inventory'])
const assertOnlyFields = (value, fields, label) => {
for (const field of Object.keys(value)) {
if (!fields.has(field)) throw new Error(`${label} contains unknown field: ${field}`)
@@ -88,25 +87,7 @@ export const expandRuntimeAssetInventory = async (rootManifestPath, workspace) =
manifests.push(absolutePath)
const manifest = await readManifest(absolutePath)
if (manifest.kind === 'asset-build-manifest') {
validateAssetBuildManifest(manifest, workspacePath)
for (const asset of manifest.assets) {
addAsset(
{
id: asset.id,
output: asset.output,
width: asset.outputPixels.width,
height: asset.outputPixels.height,
// 不透明构建模式依法不声明 alpha;只有透明模式存在该对象,避免把“字段缺失”误判为清单损坏。
alpha: asset.alpha?.required ?? false,
maxBytes: asset.quality.maxBytes,
provenance: 'generated-from-manifest',
rebuildable: true,
},
absolutePath,
)
}
} else if (manifest.kind === 'runtime-asset-inventory') {
if (manifest.kind === 'runtime-asset-inventory') {
assertOnlyFields(manifest, new Set(['schemaVersion', 'kind', 'scope', 'imports', 'assets']), 'runtime manifest')
if (manifest.schemaVersion !== 1) throw new Error('runtime inventory schemaVersion must be 1')
if (typeof manifest.scope !== 'string' || manifest.scope.trim() === '') throw new Error('runtime inventory scope is required')
@@ -136,6 +117,23 @@ export const validateRuntimeAssetRegistry = async (rootManifestPath, workspace,
const inventory = await expandRuntimeAssetInventory(rootManifestPath, workspacePath)
const registered = new Set(inventory.manifests.map((manifest) => path.resolve(manifest)))
for (const asset of inventory.assets) {
let binary
try {
binary = await readFile(resolveInsideWorkspace(workspacePath, asset.output, `${asset.id}.output`))
} catch (error) {
if (error.code === 'ENOENT') throw new Error(`missing runtime asset: ${asset.output}`)
throw error
}
if (binary.length !== asset.bytes) {
throw new Error(`${asset.id}.bytes does not match ${asset.output}`)
}
const digest = createHash('sha256').update(binary).digest('hex')
if (digest !== asset.sha256) {
throw new Error(`${asset.id}.sha256 does not match ${asset.output}`)
}
}
// 顶层注册表必须覆盖目录内每一份正式 owner。这样新增清单若没有接入全局图会立即失败,
// output 与 id 的唯一性也就不再局限于某个业务域的 imports 闭包。
for (const entry of await readdir(directory, { withFileTypes: true })) {