import { readFile, realpath } from "node:fs/promises"; import path from "node:path"; import { loadQuixosLock, type GitSource } from "../resource-lock/index.js"; export type AuthoringResource = { kind: "workspace" | "interface" | "package"; directory: string; resourceId?: string; source?: GitSource; }; /** Registration, not directory co-location, defines the editable selection. */ export async function authoringContext(start: string) { let workbench = await realpath(start); for (;;) { let text: string | undefined; try { text = await readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } if (text !== undefined) { const graph = JSON.parse(text) as { resources: AuthoringResource[] }; if (!Array.isArray(graph.resources)) throw new Error("Managed resource inventory is malformed"); const resources: AuthoringResource[] = [{ kind: "workspace", directory: "root" }]; const identities = new Set(), directories = new Set(["root"]); for (const entry of graph.resources) { // Compiler graphs carry resolved paths; the authoring API presents // stable workbench-relative names and validates containment here. if (typeof entry.directory === "string" && path.isAbsolute(entry.directory)) entry.directory = path.relative(workbench, entry.directory); if ( !["interface", "package"].includes(entry.kind) || !entry.source || !/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.directory) ) { throw new Error("Invalid managed resource registration"); } const identity = `${entry.kind}\0${entry.resourceId ?? entry.source.repository}`; if (identities.has(identity) || directories.has(entry.directory)) { throw new Error(`Multiple editable selections for ${entry.resourceId ?? entry.source.repository}`); } identities.add(identity); directories.add(entry.directory); resources.push({ kind: entry.kind, directory: entry.directory, resourceId: entry.resourceId, source: entry.source, }); } return { workbench, resources, async baseline() { const result = await loadQuixosLock(path.join(workbench, "root/quixos.lock")); if (!result.ok) throw new Error( `Workspace source baseline is invalid: ${result.diagnostics.map((d) => d.message).join("; ")}`, ); return result.lock.quixos; }, }; } const parent = path.dirname(workbench); if (parent === workbench) throw new Error("Not in a managed workbench; select one with --workbench DIRECTORY"); workbench = parent; } }