app/cli/lib/repo.ts
zzmq.dev@gmail.com/zz / app / cli / lib / repo.ts
@ 69f3dd4b
Download
Ladder Summary
- file The module supplies a small CLI‑side repository manager that parses a “owner/repo” slug, maps it to a deterministic location under the user’s `~/.zz` directory, and can clone or fast‑forward the repository using the native `git` command line; it also offers helpers to detect whether a repository has already been “onboarded” (i.e. contains the ladder anchor note) and to infer a GitHub slug from the current working directory. **Exported symbols** - `ResolvedRepo` (interface) – describes the resolved paths for a repo. - `parseSlug(slug: string)` – validates and splits a “owner/repo” string. - `resolveRepo(slug: string)` – returns a `ResolvedRepo` with `repoDir` and `stateDir` under `~/.zz`. - `ensureCloned(repo: ResolvedRepo, remoteUrl?: string)` – clones the repo if missing (or re‑clones in remote mode). - `syncClone(repo: ResolvedRepo)` – fetches and hard‑resets the local clone to `origin/main`. - `isLocallyOnboarded(repo: ResolvedRepo): Promise<boolean>` – checks for the ladder anchor note in the repo. - `inferGitHubSlugFromDir(dir?: string): string | null` – walks up to the nearest git repository and extracts a GitHub “owner/repo” slug from its `origin` remote.
// Shared repo resolution for CLI commands: parses "owner/repo", clones it
// locally if it isn't already (real `git clone`, not isomorphic-git --
// the CLI runs on a human's own machine, not in a Worker, so there's no
// reason not to use the fastest tool available for this one step; only the
// Ladder's own reads/writes go through the Workers-compatible engine).
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
const appRoot = join(here, "..", "..");
export interface ResolvedRepo {
slug: string; // "owner/repo"
name: string; // "repo"
repoDir: string; // real clone
stateDir: string; // our own .zz/ state alongside it
}
export function parseSlug(slug: string): { owner: string; name: string } {
if (typeof slug !== "string" || !slug) {
throw new Error(`expected a "repo" argument shaped "owner/repo", got ${JSON.stringify(slug)} -- every tool call that touches a repo needs one, even if you already passed it on an earlier call.`);
}
const parts = slug.split("/");
if (parts.length !== 2 || !parts[0] || !parts[1]) {
throw new Error(`expected "owner/repo", got "${slug}"`);
}
return { owner: parts[0], name: parts[1] };
}
export function resolveRepo(slug: string): ResolvedRepo {
const { name } = parseSlug(slug);
return {
slug,
name,
repoDir: join(appRoot, "demo-repos", name),
stateDir: join(appRoot, "state", name),
};
}
export function ensureCloned(repo: ResolvedRepo): void {
if (existsSync(repo.repoDir)) return;
console.error(`cloning ${repo.slug}...`);
execFileSync("git", ["clone", "--quiet", `https://github.com/${repo.slug}.git`, repo.repoDir], { stdio: "inherit" });
}