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.
94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
import { execFile as callback } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { randomUUID } from "node:crypto";
|
|
import { mkdir, mkdtemp, lstat, rename, rm, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { materializeWorkspaceSources, workspaceSourcePin, type WorkspaceSourcePin } from "./source-closure.js";
|
|
|
|
const exec = promisify(callback);
|
|
|
|
/** Developer workflow, not an ordinary Workspace Agent's implicit permission
|
|
* to publish against canonical repositories. Creates independent Git/jj copies,
|
|
* with instance binding outside committed source. No Graph is started here.
|
|
*/
|
|
export async function prepareDevelopmentSources(options: {
|
|
source: WorkspaceSourcePin;
|
|
directory: string;
|
|
cacheDirectory: string;
|
|
locations?: Record<string, string>;
|
|
}) {
|
|
const source = workspaceSourcePin(options.source);
|
|
const destination = path.resolve(options.directory);
|
|
const cache = path.resolve(options.cacheDirectory);
|
|
if (cache === destination || cache.startsWith(destination + path.sep))
|
|
throw Error("The retained source cache must live outside the disposable workbench");
|
|
if (
|
|
await lstat(destination).catch((error: NodeJS.ErrnoException) => {
|
|
if (error.code !== "ENOENT") throw error;
|
|
return null;
|
|
})
|
|
)
|
|
throw Error("Choose a new development workbench directory; existing checkouts are never overwritten");
|
|
const closure = await materializeWorkspaceSources({
|
|
source,
|
|
directory: options.cacheDirectory,
|
|
locations: options.locations,
|
|
});
|
|
const origins = new Set<string>();
|
|
for (const entry of closure.resources) {
|
|
const identity = `${entry.kind}:${entry.repository}`;
|
|
if (origins.has(identity)) throw Error("Multiple revisions of one repository require explicit editable selection");
|
|
origins.add(identity);
|
|
}
|
|
const parent = path.dirname(destination);
|
|
await mkdir(parent, { recursive: true });
|
|
const staging = await mkdtemp(path.join(parent, ".development-sources-"));
|
|
const workspaceId = randomUUID();
|
|
try {
|
|
const resources = [];
|
|
for (const entry of closure.resources) {
|
|
const relative = entry.kind === "workspace" ? "root" : "resources/" + entry.directory;
|
|
const checkout = path.join(staging, relative);
|
|
await mkdir(path.dirname(checkout), { recursive: true });
|
|
await exec("git", [
|
|
"clone",
|
|
"--no-hardlinks",
|
|
"--",
|
|
path.join(path.resolve(options.cacheDirectory), entry.directory),
|
|
checkout,
|
|
]);
|
|
await exec("git", ["remote", "set-url", "origin", entry.repository], { cwd: checkout });
|
|
await exec("git", ["checkout", "--detach", entry.commit], { cwd: checkout });
|
|
await exec("jj", ["git", "init", "--colocate"], {
|
|
cwd: checkout,
|
|
env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_SUBTREE_PUBLISH: "0" },
|
|
});
|
|
await exec("jj", ["describe", "-m", "Develop canonical application source"], {
|
|
cwd: checkout,
|
|
env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_SUBTREE_PUBLISH: "0" },
|
|
});
|
|
if (entry.kind !== "workspace")
|
|
resources.push({
|
|
kind: entry.kind,
|
|
directory: relative,
|
|
source: { resolver: "git", repository: entry.repository, commit: entry.commit },
|
|
});
|
|
}
|
|
await mkdir(path.join(staging, ".quixos"), { mode: 0o700 });
|
|
await writeFile(
|
|
path.join(staging, ".quixos/resource-graph.json"),
|
|
JSON.stringify({ formatVersion: 1, workspaceId, resources }, null, 2) + "\n",
|
|
{ mode: 0o600 },
|
|
);
|
|
await writeFile(
|
|
path.join(staging, ".quixos/development-source.json"),
|
|
JSON.stringify({ source, workspaceId }, null, 2) + "\n",
|
|
{ mode: 0o600 },
|
|
);
|
|
await rename(staging, destination);
|
|
return { directory: destination, source, workspaceId, repositories: closure.resources.length };
|
|
} finally {
|
|
await rm(staging, { recursive: true, force: true });
|
|
}
|
|
}
|