cli/src/cli.tsblame
View source
e93a9781#!/usr/bin/env node
e93a9782
59e66673import { log } from "@clack/prompts";
e93a9784import { authLogin } from "./commands/auth-login.js";
e93a9785import { authStatus } from "./commands/auth-status.js";
e93a9786import { whoami } from "./commands/whoami.js";
8d8e8157import { clone } from "./commands/clone.js";
8d8e8158import { init } from "./commands/init.js";
0d9d7239import { status } from "./commands/status.js";
e93a97810import { repoList } from "./commands/repo-list.js";
e93a97811import { repoCreate } from "./commands/repo-create.js";
e93a97812import { instanceCreate } from "./commands/instance-create.js";
69d1a7213import { ciRuns } from "./commands/ci-runs.js";
69d1a7214import { ciStatus } from "./commands/ci-status.js";
69d1a7215import { ciLogs } from "./commands/ci-logs.js";
69d1a7216import { ciTrigger } from "./commands/ci-trigger.js";
69d1a7217import { ciCancel } from "./commands/ci-cancel.js";
e93a97818
e93a97819const USAGE = `Usage: grove <command>
e93a97820
e93a97821Commands:
8d8e81522 init Create a Grove repo and initialize Sapling in current directory
8d8e81523 clone Clone a Grove repository
0d9d72324 status Show current repository info
e93a97825 auth login Authenticate with Grove (opens browser)
e93a97826 auth status Show current authentication status
e93a97827 whoami Show current user
e93a97828 repo list List repositories
e93a97829 repo create Create a repository
e93a97830 instance create Register a Grove instance
69d1a7231 ci runs List pipeline runs
69d1a7232 ci status Show pipeline run details
69d1a7233 ci logs Show step logs
69d1a7234 ci trigger Manually trigger pipelines
69d1a7235 ci cancel Cancel a running pipeline
e93a97836
e93a97837Options:
e93a97838 --help Show this help message
e93a97839 --version Show version`;
e93a97840
0d9d72341const COMMAND_HELP: Record<string, string> = {
0d9d72342 init: `Usage: grove init [directory] [options]
0d9d72343
0d9d72344Create a Grove repository and initialize Sapling in the current directory.
0d9d72345If the directory contains a .git repo, imports full git history.
0d9d72346
0d9d72347Options:
0d9d72348 --owner <org> Set the repository owner (org name)
0d9d72349 --description <desc> Set the repository description
0d9d72350 --private Create a private repository
0d9d72351 --branch <branch> Set the default branch name`,
0d9d72352
0d9d72353 clone: `Usage: grove clone <owner/repo|repo> [destination]
0d9d72354
0d9d72355Clone a Grove repository into a new directory.
0d9d72356If only a repo name is given (no owner/), looks up the owner via the API.`,
0d9d72357
0d9d72358 status: `Usage: grove status
0d9d72359
0d9d72360Show info about the current Grove repository (name, owner, branch, URL).`,
0d9d72361
0d9d72362 whoami: `Usage: grove whoami
0d9d72363
0d9d72364Show the currently authenticated user.`,
0d9d72365
0d9d72366 "auth login": `Usage: grove auth login [--hub <url>]
0d9d72367
0d9d72368Authenticate with Grove by opening the browser.
0d9d72369
0d9d72370Options:
0d9d72371 --hub <url> Override the Grove hub URL`,
0d9d72372
0d9d72373 "auth status": `Usage: grove auth status
0d9d72374
0d9d72375Show current authentication status, token expiry, and hub URL.`,
0d9d72376
0d9d72377 "repo list": `Usage: grove repo list
0d9d72378
0d9d72379List all repositories you have access to.`,
0d9d72380
0d9d72381 "repo create": `Usage: grove repo create <name> [options]
0d9d72382
0d9d72383Create a new repository on Grove without initializing a local directory.
0d9d72384Use 'grove init' to create and initialize in one step.
0d9d72385
0d9d72386Options:
0d9d72387 --owner <org> Set the repository owner (org name)
0d9d72388 --description <desc> Set the repository description
0d9d72389 --branch <branch> Set the default branch name
0d9d72390 --private Create a private repository
0d9d72391 --no-seed Skip creating an initial commit`,
0d9d72392
0d9d72393 "instance create": `Usage: grove instance create --region <region> --size <size> [options]
0d9d72394
0d9d72395Register a new Grove instance.
0d9d72396
0d9d72397Options:
0d9d72398 --name <name> Instance name (default: "grove")
0d9d72399 --region <region> Region (required)
0d9d723100 --size <size> Instance size (required)
0d9d723101 --ip <ip> IP address
0d9d723102 --domain <domain> Domain name`,
0d9d723103
0d9d723104 "ci runs": `Usage: grove ci runs [options]
0d9d723105
0d9d723106List pipeline runs for the current repository.
0d9d723107
0d9d723108Options:
0d9d723109 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
0d9d723110 --status <status> Filter by status (passed, failed, running, pending)
0d9d723111 --limit <n> Number of runs to show (default: 10)`,
0d9d723112
0d9d723113 "ci status": `Usage: grove ci status [run-id] [options]
0d9d723114
0d9d723115Show details and steps for a pipeline run.
0d9d723116If no run ID is given, shows the latest run.
0d9d723117
0d9d723118Options:
0d9d723119 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
0d9d723120
0d9d723121 "ci logs": `Usage: grove ci logs <run-id> <step-index> [options]
0d9d723122
0d9d723123Show logs for a specific step in a pipeline run.
0d9d723124
0d9d723125Options:
0d9d723126 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
0d9d723127
0d9d723128 "ci trigger": `Usage: grove ci trigger [options]
0d9d723129
0d9d723130Manually trigger pipelines for the current repository.
0d9d723131
0d9d723132Options:
0d9d723133 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
0d9d723134 --ref <branch> Branch to trigger on`,
0d9d723135
0d9d723136 "ci cancel": `Usage: grove ci cancel <run-id> [options]
0d9d723137
0d9d723138Cancel a running pipeline.
0d9d723139
0d9d723140Options:
0d9d723141 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
0d9d723142};
0d9d723143
0d9d723144function showHelp(command: string): boolean {
0d9d723145 const help = COMMAND_HELP[command];
0d9d723146 if (help) {
0d9d723147 console.log(help);
0d9d723148 return true;
0d9d723149 }
0d9d723150 return false;
0d9d723151}
0d9d723152
e93a978153const args = process.argv.slice(2);
e93a978154const cmd = args[0];
e93a978155const sub = args[1];
e93a978156const rest = args.slice(2);
e93a978157
0d9d723158function wantsHelp(a: string[]): boolean {
0d9d723159 return a.includes("--help") || a.includes("-h");
0d9d723160}
0d9d723161
e93a978162async function main() {
e93a978163 if (!cmd || cmd === "--help" || cmd === "-h") {
e93a978164 console.log(USAGE);
e93a978165 process.exit(0);
e93a978166 }
e93a978167
e93a978168 if (cmd === "--version" || cmd === "-v") {
e93a978169 const { readFile } = await import("node:fs/promises");
e93a978170 const { fileURLToPath } = await import("node:url");
e93a978171 const { dirname, join } = await import("node:path");
e93a978172 const dir = dirname(fileURLToPath(import.meta.url));
e93a978173 const pkg = JSON.parse(await readFile(join(dir, "..", "package.json"), "utf-8"));
e93a978174 console.log(`grove ${pkg.version}`);
e93a978175 process.exit(0);
e93a978176 }
e93a978177
e93a978178 if (cmd === "auth") {
0d9d723179 if (wantsHelp(args)) {
0d9d723180 const key = sub && !sub.startsWith("-") ? `auth ${sub}` : undefined;
0d9d723181 if (key && showHelp(key)) process.exit(0);
0d9d723182 console.log(`Usage: grove auth <login|status>\n\nSubcommands:\n login Authenticate with Grove (opens browser)\n status Show current authentication status`);
0d9d723183 process.exit(0);
0d9d723184 }
e93a978185 if (sub === "login") return authLogin(rest);
e93a978186 if (sub === "status") return authStatus();
59e6667187 log.error(`Unknown command: auth ${sub || ""}\nRun: grove auth --help`);
e93a978188 process.exit(1);
e93a978189 }
e93a978190
0d9d723191 if (cmd === "init") {
0d9d723192 if (wantsHelp(args)) { showHelp("init"); process.exit(0); }
0d9d723193 return init(args.slice(1));
0d9d723194 }
0d9d723195 if (cmd === "clone") {
0d9d723196 if (wantsHelp(args)) { showHelp("clone"); process.exit(0); }
0d9d723197 return clone(args.slice(1));
0d9d723198 }
0d9d723199 if (cmd === "status") {
0d9d723200 if (wantsHelp(args)) { showHelp("status"); process.exit(0); }
0d9d723201 return status();
0d9d723202 }
0d9d723203 if (cmd === "whoami") {
0d9d723204 if (wantsHelp(args)) { showHelp("whoami"); process.exit(0); }
0d9d723205 return whoami();
0d9d723206 }
e93a978207
e93a978208 if (cmd === "repo") {
0d9d723209 if (wantsHelp(args)) {
0d9d723210 const key = sub && !sub.startsWith("-") ? `repo ${sub}` : undefined;
0d9d723211 if (key && showHelp(key)) process.exit(0);
0d9d723212 console.log(`Usage: grove repo <list|create>\n\nSubcommands:\n list List repositories\n create Create a repository`);
0d9d723213 process.exit(0);
0d9d723214 }
e93a978215 if (sub === "list" || sub === "ls") return repoList();
e93a978216 if (sub === "create") return repoCreate(rest);
59e6667217 log.error(`Unknown command: repo ${sub || ""}\nRun: grove repo --help`);
e93a978218 process.exit(1);
e93a978219 }
e93a978220
e93a978221 if (cmd === "instance") {
0d9d723222 if (wantsHelp(args)) {
0d9d723223 const key = sub && !sub.startsWith("-") ? `instance ${sub}` : undefined;
0d9d723224 if (key && showHelp(key)) process.exit(0);
0d9d723225 console.log(`Usage: grove instance <create>\n\nSubcommands:\n create Register a Grove instance`);
0d9d723226 process.exit(0);
0d9d723227 }
e93a978228 if (sub === "create") return instanceCreate(rest);
59e6667229 log.error(`Unknown command: instance ${sub || ""}\nRun: grove instance --help`);
e93a978230 process.exit(1);
e93a978231 }
e93a978232
69d1a72233 if (cmd === "ci") {
0d9d723234 if (wantsHelp(args)) {
0d9d723235 const key = sub && !sub.startsWith("-") ? `ci ${sub}` : undefined;
0d9d723236 if (key && showHelp(key)) process.exit(0);
0d9d723237 console.log(`Usage: grove ci <runs|status|logs|trigger|cancel>\n\nSubcommands:\n runs List pipeline runs\n status Show pipeline run details\n logs Show step logs\n trigger Manually trigger pipelines\n cancel Cancel a running pipeline`);
0d9d723238 process.exit(0);
0d9d723239 }
69d1a72240 if (sub === "runs" || sub === "ls") return ciRuns(rest);
69d1a72241 if (sub === "status" || sub === "show") return ciStatus(rest);
69d1a72242 if (sub === "logs" || sub === "log") return ciLogs(rest);
69d1a72243 if (sub === "trigger" || sub === "run") return ciTrigger(rest);
69d1a72244 if (sub === "cancel") return ciCancel(rest);
59e6667245 log.error(`Unknown command: ci ${sub || ""}\nRun: grove ci --help`);
69d1a72246 process.exit(1);
69d1a72247 }
69d1a72248
59e6667249 log.error(`Unknown command: ${cmd}\nRun: grove --help`);
e93a978250 process.exit(1);
e93a978251}
e93a978252
e93a978253main().catch((err) => {
59e6667254 log.error(err.message || String(err));
e93a978255 process.exit(1);
e93a978256});