import fs from "node:fs/promises"; import path from "node:path"; import type { ScaffoldRecipe } from "./scaffold-recipes.js"; import type { StructuralRequest } from "./structural-plan.js"; export async function scaffoldController( root: string, prefix: string, spec: ScaffoldRecipe, ): Promise { const entry = { id: spec.id, ...spec.controller }; const fields = ["id", "subject", "snapshot", "remote", "input"]; if ( Object.keys(entry).some((key) => ![...fields, "registry"].includes(key)) || (entry.registry !== undefined && typeof entry.registry !== "string") || fields.some((key) => typeof (entry as Record)[key] !== "string") || !["unit", "value"].includes(entry.input ?? "") ) throw Error("Controller requires id, subject, snapshot, remote and input (unit/value)"); const file = prefix + "quixos.controllers.json"; let before: string | undefined; try { before = await fs.readFile(path.join(root, file), "utf8"); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } const catalog = before ? JSON.parse(before) : { schemaVersion: 1, controllers: [] }; if ( catalog.schemaVersion !== 1 || !Array.isArray(catalog.controllers) || catalog.controllers.length >= 1000 || catalog.controllers.some((c: { id: string }) => c.id === entry.id) ) throw Error("Invalid catalog or duplicate controller ID"); catalog.controllers.push(entry); const content = JSON.stringify(catalog, null, 2) + "\n"; return { kind: "package", source: spec.source, resourceRoot: spec.directory, validation: "syntax", files: [before === undefined ? { file, create: content } : { file, expected: before, replace: content }], }; }