Add exact recursive source checkout, independent Git/jj developer workbenches and source-only backup/recovery commands, with Workspace instance binding outside source. Qualify two empty Reader Graphs, packaged source commands, Agent VM compilation, exact Gitea read grants and reconciled Reader settings/annotation stability. Publish checked Reader Package/root revisions to canonical review branches and retention tags, retain all 57 repositories independently in private versioned storage, verify downloaded-backup recovery and advance the example pin. Add bounded Central pin acquisition and selection-specific read-only identities, qualified against all 57 canonical repositories without write authority. Replace regex fork dependency discovery with compiler-parsed lock imports and rewrite proposals; preserve source identities and unrelated files, passing instance binding through packed and ordinary host checks. Hosted run-pin versus authoring preparation and maintained duplicate removal remain pending. Live source selections, Template defaults and default source branches are unchanged.
77 lines
3.2 KiB
TypeScript
77 lines
3.2 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" | "build";
|
|
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[]; workspaceId?: string };
|
|
if (
|
|
graph.workspaceId !== undefined &&
|
|
(typeof graph.workspaceId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}$/.test(graph.workspaceId))
|
|
)
|
|
throw new Error("Managed workbench has an invalid Workspace identity");
|
|
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", "build"].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,
|
|
workspaceId: graph.workspaceId,
|
|
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;
|
|
}
|
|
}
|