import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; const [manifestPath, publicRoot] = process.argv.slice(2); if (!manifestPath || !publicRoot) throw new Error("usage: check-manifest-assets.mjs MANIFEST PUBLIC_ROOT"); const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); if (manifest.schemaVersion === 2 && (typeof manifest.releaseId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(manifest.releaseId))) { throw new Error("schema v2 engine manifest has no valid releaseId"); } const resources = manifest.schemaVersion === 2 ? manifest.variants.flatMap((variant) => Object.values(variant.resources)) : manifest.wasm ?? []; const uniqueResources = [...new Map(resources.map((resource) => [resource.url, resource])).values()]; for (const resource of uniqueResources) { const relativePath = resource.url.replace(/^\/+/, ""); const filePath = path.join(publicRoot, relativePath); const actual = crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex"); if (actual !== String(resource.sha256).toLowerCase()) { throw new Error(`manifest hash mismatch: ${resource.url} expected=${resource.sha256} actual=${actual}`); } } console.log(`manifest-assets-ok resources=${uniqueResources.length}`);