fix(POSTV1-06): 恢复贴纸与字体运行时资源链路
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run

This commit is contained in:
suyx
2026-08-05 16:58:45 +08:00
parent 7de633d4c9
commit d9e39702e0
16 changed files with 12179 additions and 10 deletions
+93
View File
@@ -0,0 +1,93 @@
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import {
createPublicAssetResolver,
readConfiguredAssetRoot,
validateReadOnlyAssetRoot,
type PublicAssetEntry,
type PublicAssetResolver,
} from "./local-data-root.js";
const rootRef = "p0a_runtime_assets";
const schemaVersion = "DadaRuntimeAssets/v1";
const assetIdPattern = /^[a-z0-9][a-z0-9_-]{2,119}$/i;
const releasePattern = /^[a-z0-9][a-z0-9._-]{0,79}$/i;
const shaPattern = /^[a-f0-9]{64}$/i;
export interface RuntimeAssetState {
checked_at: string;
configured: boolean;
pause_reason: "asset_manifest_invalid" | "asset_root_missing" | "asset_root_state_missing" | null;
status: "active" | "unavailable";
}
export interface LoadedRuntimeAssets {
publicAssets?: PublicAssetResolver;
state: RuntimeAssetState;
}
function parseRuntimeManifest(bytes: Buffer): PublicAssetEntry[] {
const value = JSON.parse(bytes.toString("utf8")) as Record<string, unknown>;
if (value.schema_version !== schemaVersion || value.source !== "external_read_only" || value.root_ref !== rootRef) {
throw new Error("runtime_asset_manifest_invalid");
}
if (!Array.isArray(value.entries) || value.entries.length === 0) throw new Error("runtime_asset_manifest_invalid");
return value.entries.map((candidate) => {
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) throw new Error("runtime_asset_manifest_invalid");
const entry = candidate as Record<string, unknown>;
if (
typeof entry.assetId !== "string" || !assetIdPattern.test(entry.assetId)
|| typeof entry.mimeType !== "string" || !/^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/i.test(entry.mimeType)
|| typeof entry.relativePath !== "string" || entry.relativePath.includes("\\") || entry.relativePath.split("/").includes("..")
|| typeof entry.resourceVersion !== "string" || !releasePattern.test(entry.resourceVersion)
|| entry.rootRef !== rootRef
|| typeof entry.sha256 !== "string" || !shaPattern.test(entry.sha256)
) throw new Error("runtime_asset_manifest_invalid");
return entry as unknown as PublicAssetEntry;
});
}
function unavailable(
configured: boolean,
pauseReason: Exclude<RuntimeAssetState["pause_reason"], null>,
checkedAt: string,
): LoadedRuntimeAssets {
return { state: { checked_at: checkedAt, configured, pause_reason: pauseReason, status: "unavailable" } };
}
export function loadConfiguredRuntimeAssets(input: {
configFile: string;
dataRoot: string;
trustedManifestPath: string;
clock?: () => number;
}): LoadedRuntimeAssets {
const checkedAt = new Date((input.clock ?? Date.now)()).toISOString();
let assetRoot: string;
try {
assetRoot = readConfiguredAssetRoot(input.configFile);
} catch {
return unavailable(false, "asset_root_state_missing", checkedAt);
}
if (!existsSync(input.trustedManifestPath)) return unavailable(true, "asset_manifest_invalid", checkedAt);
try {
const trustedBytes = readFileSync(input.trustedManifestPath);
const entries = parseRuntimeManifest(trustedBytes);
const validatedRoot = validateReadOnlyAssetRoot({
dataRoot: input.dataRoot,
expectedSha256: createHash("sha256").update(trustedBytes).digest("hex"),
manifestRelativePath: "manifest.json",
root: assetRoot,
rootRef,
});
if (!validatedRoot.ok) {
return unavailable(true, validatedRoot.reason === "asset_root_missing" ? "asset_root_missing" : "asset_manifest_invalid", checkedAt);
}
return {
publicAssets: createPublicAssetResolver({ entries, roots: [validatedRoot] }),
state: { checked_at: checkedAt, configured: true, pause_reason: null, status: "active" },
};
} catch {
return unavailable(true, "asset_manifest_invalid", checkedAt);
}
}