#!/usr/bin/env node /** * derive-facts.mjs — extract mechanical facts from the parent repo and write * them as a typed TS module. Run as `prebuild`. The same logic also runs in * the content-drift cron against raw.githubusercontent.com so the deployed * worker can detect repo→site drift between deploys. * * This script delegates all derivation logic to facts-lib.mjs so that * check-facts.mjs can reuse the same code for the CI drift gate. */ import { writeFileSync, readFileSync, existsSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { buildFacts } from "./facts-lib.mjs"; const __dirname = dirname(fileURLToPath(import.meta.url)); const target = resolve(__dirname, "..", "lib", "facts.generated.ts"); const out = buildFacts(); // Preserve the committed `generatedAt` when every *checked* fact is unchanged, // so a clean rebuild doesn't dirty the tracked file on every run. The drift // gate (check-facts.mjs) ignores generatedAt + latestRelease; we mirror that // volatile set here. Only when a real fact changes do we stamp a fresh time. const VOLATILE = new Set(["generatedAt", "latestRelease"]); function readCommittedFacts() { if (!existsSync(target)) return null; const src = readFileSync(target, "utf-8"); const m = src.match(/export const FACTS\s*:\s*\w+\s*=\s*([\s\S]*?);?\s*$/); if (!m) return null; try { return JSON.parse(m[1]); } catch { return null; } } const committed = readCommittedFacts(); if (committed && typeof committed.generatedAt === "string") { const sameChecked = Object.keys(out).every( (k) => VOLATILE.has(k) || JSON.stringify(out[k]) === JSON.stringify(committed[k]), ); if (sameChecked) out.generatedAt = committed.generatedAt; } // latestRelease is intentionally null at build time — populated at runtime by the drift cron. const RUNTIME_ONLY = new Set(["latestRelease"]); const missing = Object.entries(out).filter( ([k, v]) => k !== "generatedAt" && !RUNTIME_ONLY.has(k) && (v == null || (Array.isArray(v) && v.length === 0)), ); if (missing.length > 0) { console.warn("[derive-facts] missing values:", missing.map(([k]) => k).join(", ")); } const ts = `// AUTO-GENERATED by web/scripts/derive-facts.mjs at prebuild. // DO NOT EDIT — re-run \`npm run prebuild\` (or just \`npm run build\`) after changing the parent repo. // To override at runtime, write the same shape to KV under key "facts:current". export interface ProviderFact { id: string; label: string; env: string } export interface RepoFacts { generatedAt: string; version: string | null; crates: string[]; sandboxBackends: string[]; providers: ProviderFact[]; defaultModel: string | null; nodeEngines: string | null; toolCount: number | null; license: string | null; latestRelease: string | null; } export const FACTS: RepoFacts = ${JSON.stringify(out, null, 2)}; `; writeFileSync(target, ts); console.log(`[derive-facts] wrote ${target}`); console.log( `[derive-facts] version=${out.version} crates=${out.crates.length} ` + `providers=${out.providers.length} sandboxes=${out.sandboxBackends.length} ` + `default-model=${out.defaultModel} node=${out.nodeEngines} ` + `tools=${out.toolCount} license=${out.license}`, );