50 lines
2.6 KiB
TypeScript
50 lines
2.6 KiB
TypeScript
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;
|
||
|
|
}
|
||
|
|
}
|