import { createHash } from "node:crypto"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import process from "node:process"; import { z } from "zod"; import { ROUTE_REGISTRY, ROUTE_RUNTIME_CONTRACT, } from "../src/features/installed-feature-contracts.js"; import { runtimeConfigSchema } from "../src/bootstrap/runtime-config-schema.js"; const packageJson = JSON.parse(await readFile("package.json", "utf8")); const packageManagerVersion = packageJson.packageManager.split("@").at(-1); const buildId = process.env.VITE_BUILD_ID ?? "local-build"; const commitSha = process.env.VITE_COMMIT_SHA ?? "local"; const releaseId = process.env.RELEASE_ID ?? "local-release"; const runnerImage = process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`; const builtAt = new Date().toISOString(); const viteManifest = await readFile("dist/.vite/manifest.json", "utf8"); const viteManifestObject = /** @type {Record} */ ( JSON.parse(viteManifest) ); const assetManifestHash = createHash("sha256") .update(viteManifest) .digest("hex"); const runtimeConfig = JSON.parse(await readFile("dist/config.json", "utf8")); /** @type {Record} */ const routeChunks = {}; for (const definition of Object.values(ROUTE_REGISTRY)) { const runtime = /** @type {Record} */ ( ROUTE_RUNTIME_CONTRACT )[definition.routeId]; const asset = Object.values(viteManifestObject).find( (entry) => entry.name === runtime?.moduleId && entry.isDynamicEntry, ); if (!runtime || !asset?.file) { throw new Error(`Missing built route chunk: ${definition.routeId}`); } routeChunks[definition.chunkId] = asset.file; } const runtimeConfigJsonSchema = z.toJSONSchema(runtimeConfigSchema); runtimeConfig.BUILD_ID = buildId; runtimeConfig.RELEASE_ID = releaseId; const manifest = { schemaVersion: 1, buildId, commitSha, generatedAt: builtAt, buildContext: { nodeVersion: process.version, packageManagerVersion, runnerImage, }, outputs: { directory: "dist", viteManifest: "dist/.vite/manifest.json", routeChunks, runtimeConfigSchema: "dist/runtime-config.schema.json", }, }; const releaseManifest = { schemaVersion: 1, appVersion: packageJson.version, buildId, commitSha, configSchemaVersion: runtimeConfig.CONFIG_SCHEMA_VERSION, apiContractVersion: runtimeConfig.API_CONTRACT_VERSION, assetManifestHash, releaseId, builtAt, routeChunks, }; await mkdir("artifacts/release", { recursive: true }); await writeFile("dist/config.json", `${JSON.stringify(runtimeConfig, null, 2)}\n`); await writeFile( "dist/release-manifest.json", `${JSON.stringify(releaseManifest, null, 2)}\n`, ); await writeFile( "dist/runtime-config.schema.json", `${JSON.stringify(runtimeConfigJsonSchema, null, 2)}\n`, ); await writeFile( "artifacts/release/runtime-config.schema.json", `${JSON.stringify(runtimeConfigJsonSchema, null, 2)}\n`, ); await writeFile( "artifacts/release/build-manifest.json", `${JSON.stringify(manifest, null, 2)}\n`, );