#!/usr/bin/env node import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { computeBaseConfigSchemaResponse } from "../src/config/schema-base.js"; const GENERATED_BY = "scripts/generate-base-config-schema.ts"; const DEFAULT_OUTPUT_PATH = "src/config/schema.base.generated.ts"; function readIfExists(filePath: string): string | null { try { return fs.readFileSync(filePath, "utf8"); } catch { return null; } } function formatTypeScriptModule(source: string, outputPath: string): string { const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const formatter = spawnSync( process.platform === "win32" ? "pnpm.cmd" : "pnpm", ["exec", "oxfmt", "--stdin-filepath", outputPath], { cwd: repoRoot, input: source, encoding: "utf8", }, ); if (formatter.status !== 0) { const details = formatter.stderr?.trim() || formatter.stdout?.trim() || "unknown formatter failure"; throw new Error(`failed to format generated base config schema: ${details}`); } return formatter.stdout; } export function renderBaseConfigSchemaModule(params?: { generatedAt?: string }): string { const payload = computeBaseConfigSchemaResponse({ generatedAt: params?.generatedAt ?? new Date().toISOString(), }); return formatTypeScriptModule( `// Auto-generated by ${GENERATED_BY}. Do not edit directly. import type { BaseConfigSchemaResponse } from "./schema-base.js"; export const GENERATED_BASE_CONFIG_SCHEMA = ${JSON.stringify(payload, null, 2)} as const satisfies BaseConfigSchemaResponse; `, DEFAULT_OUTPUT_PATH, ); } export function writeBaseConfigSchemaModule(params?: { repoRoot?: string; outputPath?: string; check?: boolean; }): { changed: boolean; wrote: boolean; outputPath: string } { const repoRoot = path.resolve( params?.repoRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."), ); const outputPath = path.resolve(repoRoot, params?.outputPath ?? DEFAULT_OUTPUT_PATH); const current = readIfExists(outputPath); const generatedAt = current?.match(/generatedAt:\s*"([^"]+)"/u)?.[1] ?? current?.match(/"generatedAt":\s*"([^"]+)"/u)?.[1] ?? new Date().toISOString(); const next = renderBaseConfigSchemaModule({ generatedAt }); const changed = current !== next; if (params?.check) { return { changed, wrote: false, outputPath }; } if (changed) { fs.writeFileSync(outputPath, next, "utf8"); } return { changed, wrote: changed, outputPath }; } const args = new Set(process.argv.slice(2)); if (args.has("--check") && args.has("--write")) { throw new Error("Use either --check or --write, not both."); } if (import.meta.url === new URL(process.argv[1] ?? "", "file://").href) { const result = writeBaseConfigSchemaModule({ check: args.has("--check") }); if (result.changed) { if (args.has("--check")) { console.error( `[base-config-schema] stale generated output at ${path.relative(process.cwd(), result.outputPath)}`, ); process.exitCode = 1; } else { console.log(`[base-config-schema] wrote ${path.relative(process.cwd(), result.outputPath)}`); } } }