zz_

app/cli/lib/watchedRepoResolution.ts

zzmq.dev@gmail.com/zz / app / cli / lib / watchedRepoResolution.ts @ 5e5720fb Download
Ladder Summary
  • file The module implements the logic for determining which GitHub repository slugs the current **zz** session should consider “watched,” combining explicit `--repos` arguments, a logged‑in server‑side list, the user’s `~/.zz/config.json` fallback, and finally a local discovery of running zz processes and the current directory. It prints onboarding guidance when no local repos are found and never performs any cloning itself. **Exported members** - `resolveWatchedRepoSlugs` – async function that returns an array of repo slugs or `null` (the “nothing to show” case) based on the priority rules described above.
// Shared "which repos am I watching" resolution -- originally `zz
// top`'s own logic, extracted so `zz work --assist`'s ask picker (which
// needs this same answer, but must never eagerly clone anything the way
// `zz work`'s normal body does) can reuse it verbatim instead of
// drifting out of sync with `zz top`'s own behavior.
import { execFileSync } from "node:child_process";
import { resolveRepo, isLocallyOnboarded, inferGitHubSlugFromCwd } from "./repo.js";
import { configuredRepos } from "./config.js";
import { apiFetch } from "./api.js";
import { listRunningRepos } from "./processRegistry.js";
import type { BackendMode } from "./backendMode.js";

/** No explicit config/--repos, local mode: union whatever other local `zz work`/`zz review` processes are watching with the current directory, if it's an onboarded GitHub-backed repo. Prints onboarding guidance and returns null (meaning "nothing to show, caller should exit/show empty") rather than ever pretending there's something to watch. */
async function discoverLocalRepos(): Promise<string[] | null> {
  const running = listRunningRepos();
  const cwdSlug = inferGitHubSlugFromCwd();
  const cwdRepo = cwdSlug ? resolveRepo(cwdSlug) : null;
  const cwdOnboarded = cwdRepo ? await isLocallyOnboarded(cwdRepo) : false;
  const repos = cwdOnboarded && cwdSlug ? [...new Set([...running, cwdSlug])] : running;
  if (repos.length > 0) return repos;

  if (cwdSlug && !cwdOnboarded) {
    console.log(`Nothing to show yet. This directory looks like it's "${cwdSlug}" on GitHub, but it isn't onboarded into zz locally. Run:\n\n  zz ladder build ${cwdSlug}\n\nthen try again.`);
  } else if (cwdRepo === null && cwdSlugLooksLikeGitRepo()) {
    console.log(`Nothing to show yet. This directory is a git repo, but its origin isn't a GitHub remote -- \`zz ladder build\` (zz's only onboarding path right now) needs one. Run \`zz work --local <owner/repo>\`/\`zz review --local <owner/repo>\` directly on a repo you've already onboarded, or use Explore (local mode browses the filesystem) from a terminal where one is nearby.`);
  } else {
    console.log("Nothing to show yet -- no other local zz process is running, and this directory isn't a git repo. Run `zz work`/`zz review` on a repo, or `zz ladder build owner/repo` to onboard one from GitHub, then try again.");
  }
  return null;
}

// A plain git-repo check (no GitHub remote needed) just to pick the
// right wording above -- inferGitHubSlugFromCwd() returning null is
// ambiguous between "not a git repo" and "git repo, no GitHub
// remote," and those two deserve different guidance.
function cwdSlugLooksLikeGitRepo(): boolean {
  try {
    execFileSync("git", ["rev-parse", "--show-toplevel"], { stdio: "ignore" });
    return true;
  } catch {
    return false;
  }
}

/**
 * Priority: --repos (explicit, always wins) > the server's own
 * per-identity list, if logged in (never mixed with a local config file
 * that isn't scoped per-identity -- confirmed live: logging in as a
 * second account still showed the first account's repos, because
 * ~/.zz/config.json was being checked unconditionally before login
 * state at all) > ~/.zz/config.json (the fallback for the two cases
 * with no server-side list to use instead: remote but not logged in, or
 * local mode) > local discovery > empty.
 *
 * Never clones or otherwise touches disk beyond resolving paths --
 * callers that need an actual working tree (zz work's normal body, an
 * assist session once an ask is picked) do that themselves, deliberately
 * separate from "what am I watching" so browsing/picking never triggers
 * it as a side effect.
 *
 * Returns null only for the local-mode "truly nothing to watch" case
 * (discoverLocalRepos already printed onboarding guidance) -- callers
 * that would otherwise render a pointless empty view should treat that
 * as "print nothing more, exit," same as `zz top` always has; an empty
 * array (not logged in remotely, no config) is a normal state worth
 * still rendering a view for.
 */
export async function resolveWatchedRepoSlugs(mode: BackendMode, options: { repos?: string[] }): Promise<string[] | null> {
  if (options.repos && options.repos.length > 0) return options.repos;
  if (mode.useRemote && mode.identity) {
    return (await apiFetch<{ repos: { slug: string }[] }>("GET", "/api/me/repos")).repos.map((r) => r.slug);
  }
  const configured = configuredRepos();
  if (configured) return configured;
  if (mode.useRemote) return []; // not logged in, no config -- empty dashboard
  return discoverLocalRepos();
}