import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { generateArtifactSchemas } from "../../scripts/generate-artifact-schemas.ts"; import { assertMatchesJsonSchema } from "../../scripts/lib/json-schema.ts"; const temporaryDirectories: string[] = []; async function temporaryDirectory(): Promise { const directory = await mkdtemp(path.join(tmpdir(), "artifact-schemas-")); temporaryDirectories.push(directory); return directory; } afterEach(async () => { await Promise.all( temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true }), ), ); }); async function json(path: string): Promise { return JSON.parse(await readFile(path, "utf8")) as unknown; } describe("checked-in JSON Schema execution", () => { it("accepts a build manifest and rejects undeclared output fields", async () => { const schema = await json("schemas/artifacts/build-manifest.schema.json"); const manifest = { schemaVersion: 1, buildId: "build-1", commitSha: "commit-1", releaseId: "release-1", moduleInventoryHash: "inventory-hash", generatedAt: "2026-08-01T00:00:00.000Z", buildContext: { nodeVersion: "v24.11.0", packageManagerVersion: "11.17.0", runnerImage: "test-runner", sourceDateEpoch: null, }, outputs: { directory: "dist", viteManifest: "dist/.vite/manifest.json", moduleInventory: "artifacts/quality/vite-module-inventory.json", routeChunks: { home: "assets/home.js" }, runtimeConfigSchema: "dist/runtime-config.schema.json", }, }; expect(() => assertMatchesJsonSchema(schema, manifest, "build manifest"), ).not.toThrow(); expect(() => assertMatchesJsonSchema( schema, { ...manifest, undocumented: true }, "build manifest", ), ).toThrow(/checked-in JSON Schema/u); }); it("resolves local schema definitions in the recipe catalog", async () => { const [schema, catalog] = await Promise.all([ json("schemas/config/frontend-capability-recipes.schema.json"), json("config/recipes/frontend-capability-recipes.json"), ]); expect(() => assertMatchesJsonSchema(schema, catalog, "optional recipe catalog"), ).not.toThrow(); }); it("generates deterministic draft 2020-12 artifact schemas and detects drift", async () => { const root = await temporaryDirectory(); await generateArtifactSchemas({ root }); const generatedPath = path.join( root, "schemas/artifacts/dependency-inventory.schema.json", ); const generated = await readFile(generatedPath, "utf8"); expect(generated.endsWith("\n")).toBe(true); expect(JSON.parse(generated)).toMatchObject({ $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", additionalProperties: false, }); await generateArtifactSchemas({ root, check: true }); await writeFile(generatedPath, `${generated.trimEnd()} \n`, "utf8"); await expect( generateArtifactSchemas({ root, check: true }), ).rejects.toThrow(/dependency-inventory\.schema\.json/u); }); it.each([ { name: "dependency inventory", path: "schemas/artifacts/dependency-inventory.schema.json", value: { schemaVersion: 2, packageManager: "pnpm@11.17.0", lockfileSha256: "a".repeat(64), dependencyCount: 1, directDependencyCount: 1, dependencies: [ { name: "zod", version: "4.4.3", direct: true, scope: "production", optional: false, license: "MIT", integrity: `sha512-${"a".repeat(86)}`, dependencies: [], }, ], }, }, { name: "registry snapshot", path: "schemas/artifacts/registry-snapshot.schema.json", value: { schemaVersion: 2, generatedAt: "2026-08-01T00:00:00.000Z", baselineDigest: "b".repeat(64), currentDigest: "c".repeat(64), compatibility: { impact: "none", changes: [] }, failures: [], registries: Array.from({ length: 11 }, (_, index) => ({ registryId: `registry-${index}`, owner: "platform", source: `src/registry-${index}.ts`, rowCount: 0, contract: {}, rows: {}, })), }, }, { name: "supply-chain verification", path: "schemas/artifacts/supply-chain-verification.schema.json", value: { schemaVersion: 1, localStatus: "PASS", promotionStatus: "FAIL_UNVERIFIED", lockfileSha256: "d".repeat(64), sourceSetSha256: "e".repeat(64), distSha256: "f".repeat(64), sbomSha256: "0".repeat(64), dependencyDiff: { added: [], removed: [], changed: [], upgrades: [], }, highRiskReview: [], vulnerabilityStatus: "FAIL_UNVERIFIED", provenanceAttestationStatus: "FAIL_UNVERIFIED", failures: [], }, }, ])("accepts $name and rejects undeclared top-level fields", async (fixture) => { const schema = await json(fixture.path); expect(() => assertMatchesJsonSchema(schema, fixture.value, fixture.name), ).not.toThrow(); expect(() => assertMatchesJsonSchema( schema, { ...fixture.value, undocumented: true }, fixture.name, ), ).toThrow(/checked-in JSON Schema/u); if (fixture.name === "dependency inventory") { const dependency = fixture.value.dependencies?.[0]; if (dependency === undefined) { throw new TypeError("dependency inventory fixture is incomplete"); } expect(() => assertMatchesJsonSchema( schema, { ...fixture.value, dependencies: [ { ...dependency, undocumented: true, }, ], }, fixture.name, ), ).toThrow(/checked-in JSON Schema/u); expect(() => assertMatchesJsonSchema( schema, { ...fixture.value, dependencyCount: 0, directDependencyCount: 0, dependencies: [], }, fixture.name, ), ).toThrow(/checked-in JSON Schema/u); } if (fixture.name === "registry snapshot") { expect(() => assertMatchesJsonSchema( schema, { ...fixture.value, failures: [], registries: [] }, fixture.name, ), ).toThrow(/checked-in JSON Schema/u); expect(() => assertMatchesJsonSchema( schema, { ...fixture.value, baselineDigest: null, compatibility: { impact: "not-evaluated", changes: [] }, failures: ["missing registry source"], registries: [], }, fixture.name, ), ).not.toThrow(); } }); });