import { createHash } from "node:crypto"; import { readFile, readdir } from "node:fs/promises"; import { resolve } from "node:path"; const root = resolve(import.meta.dirname, ".."); const sourcePath = resolve(root, "crates/nana-domain/src/lib.rs"); const hashPath = resolve(root, "contracts/.source.sha256"); const schemaDirectory = resolve(root, "contracts/schema"); const source = await readFile(sourcePath); const expectedHash = (await readFile(hashPath, "utf8")).trim(); const actualHash = createHash("sha256").update(source).digest("hex"); if (actualHash !== expectedHash) { throw new Error( "Generated contracts are stale. Install Rust and run `pnpm contracts:generate`." ); } const schemaFiles = (await readdir(schemaDirectory)) .filter((name) => name.endsWith(".schema.json")) .sort(); const required = [ "app-info.schema.json", "branch-list.schema.json", "character-card.schema.json", "demo-pack-summary.schema.json", "fork-branch-request.schema.json", "fork-branch-result.schema.json", "item-spec.schema.json", "lapp-connection-test-result.schema.json", "lapp-settings.schema.json", "persona.schema.json", "player-view.schema.json", "plot-module.schema.json", "presentation-snapshot.schema.json", "rename-branch-request.schema.json", "resource-bundle.schema.json", "resource-header.schema.json", "runtime-state.schema.json", "story-node.schema.json", "switch-branch-request.schema.json", "switch-branch-result.schema.json", "turn-failure.schema.json", "turn-request.schema.json", "turn-result.schema.json", "update-lapp-settings-request.schema.json", "world-book.schema.json" ]; if (JSON.stringify(schemaFiles) !== JSON.stringify(required)) { throw new Error( `Generated schema manifest mismatch.\nExpected: ${required.join(", ")}\nActual: ${schemaFiles.join(", ")}` ); } for (const name of schemaFiles) { const value = JSON.parse(await readFile(resolve(schemaDirectory, name), "utf8")); if (typeof value !== "object" || value === null || value.$schema === undefined) { throw new Error(`Invalid generated schema: contracts/schema/${name}`); } } const typeScript = await readFile(resolve(root, "contracts/ts/index.ts"), "utf8"); if (!typeScript.startsWith("// @generated by crates/nana-contracts")) { throw new Error("contracts/ts/index.ts is not marked as generated"); } console.log(`Verified ${schemaFiles.length} schemas and TypeScript DTO source hash.`);