71 lines
2.8 KiB
TypeScript
71 lines
2.8 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;
|
|
}
|
|
}
|