Files
quixos-protocol/src/capability-language/authoring-context.ts
T

50 lines
2.6 KiB
TypeScript
Raw Normal View History

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<string>(), directories = new Set<string>(["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;
}
}