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";
17fe7cd18import { doctor } from "./commands/doctor.js";
e93a97819
e93a97820const USAGE = `Usage: grove <command>
e93a97821
e93a97822Commands:
8d8e81523 init Create a Grove repo and initialize Sapling in current directory
8d8e81524 clone Clone a Grove repository
0d9d72325 status Show current repository info
e93a97826 auth login Authenticate with Grove (opens browser)
e93a97827 auth status Show current authentication status
e93a97828 whoami Show current user
e93a97829 repo list List repositories
e93a97830 repo create Create a repository
e93a97831 instance create Register a Grove instance
17fe7cd32 doctor Check your Grove setup for common issues
69d1a7233 ci runs List pipeline runs
69d1a7234 ci status Show pipeline run details
69d1a7235 ci logs Show step logs
69d1a7236 ci trigger Manually trigger pipelines
69d1a7237 ci cancel Cancel a running pipeline
e93a97838
e93a97839Options:
e93a97840 --help Show this help message
e93a97841 --version Show version`;
e93a97842
0d9d72343const COMMAND_HELP: Record<string, string> = {
0d9d72344 init: `Usage: grove init [directory] [options]
0d9d72345
0d9d72346Create a Grove repository and initialize Sapling in the current directory.
0d9d72347If the directory contains a .git repo, imports full git history.
0d9d72348
0d9d72349Options:
0d9d72350 --owner <org> Set the repository owner (org name)
0d9d72351 --description <desc> Set the repository description
0d9d72352 --private Create a private repository
0d9d72353 --branch <branch> Set the default branch name`,
0d9d72354
0d9d72355 clone: `Usage: grove clone <owner/repo|repo> [destination]
0d9d72356
0d9d72357Clone a Grove repository into a new directory.
0d9d72358If only a repo name is given (no owner/), looks up the owner via the API.`,
0d9d72359
0d9d72360 status: `Usage: grove status
0d9d72361
0d9d72362Show info about the current Grove repository (name, owner, branch, URL).`,
0d9d72363
0d9d72364 whoami: `Usage: grove whoami
0d9d72365
0d9d72366Show the currently authenticated user.`,
0d9d72367
0d9d72368 "auth login": `Usage: grove auth login [--hub <url>]
0d9d72369
0d9d72370Authenticate with Grove by opening the browser.
0d9d72371
0d9d72372Options:
0d9d72373 --hub <url> Override the Grove hub URL`,
0d9d72374
0d9d72375 "auth status": `Usage: grove auth status
0d9d72376
0d9d72377Show current authentication status, token expiry, and hub URL.`,
0d9d72378
0d9d72379 "repo list": `Usage: grove repo list
0d9d72380
0d9d72381List all repositories you have access to.`,
0d9d72382
0d9d72383 "repo create": `Usage: grove repo create <name> [options]
0d9d72384
0d9d72385Create a new repository on Grove without initializing a local directory.
0d9d72386Use 'grove init' to create and initialize in one step.
0d9d72387
0d9d72388Options:
0d9d72389 --owner <org> Set the repository owner (org name)
0d9d72390 --description <desc> Set the repository description
0d9d72391 --branch <branch> Set the default branch name
0d9d72392 --private Create a private repository
0d9d72393 --no-seed Skip creating an initial commit`,
0d9d72394
0d9d72395 "instance create": `Usage: grove instance create --region <region> --size <size> [options]
0d9d72396
0d9d72397Register a new Grove instance.
0d9d72398
0d9d72399Options:
0d9d723100 --name <name> Instance name (default: "grove")
0d9d723101 --region <region> Region (required)
0d9d723102 --size <size> Instance size (required)
0d9d723103 --ip <ip> IP address
0d9d723104 --domain <domain> Domain name`,
0d9d723105
0d9d723106 "ci runs": `Usage: grove ci runs [options]
0d9d723107
0d9d723108List pipeline runs for the current repository.
0d9d723109
0d9d723110Options:
0d9d723111 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
0d9d723112 --status <status> Filter by status (passed, failed, running, pending)
0d9d723113 --limit <n> Number of runs to show (default: 10)`,
0d9d723114
0d9d723115 "ci status": `Usage: grove ci status [run-id] [options]
0d9d723116
0d9d723117Show details and steps for a pipeline run.
0d9d723118If no run ID is given, shows the latest run.
0d9d723119
0d9d723120Options:
0d9d723121 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
0d9d723122
0d9d723123 "ci logs": `Usage: grove ci logs <run-id> <step-index> [options]
0d9d723124
0d9d723125Show logs for a specific step in a pipeline run.
0d9d723126
0d9d723127Options:
0d9d723128 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
0d9d723129
0d9d723130 "ci trigger": `Usage: grove ci trigger [options]
0d9d723131
0d9d723132Manually trigger pipelines for the current repository.
0d9d723133
0d9d723134Options:
0d9d723135 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
0d9d723136 --ref <branch> Branch to trigger on`,
0d9d723137
0d9d723138 "ci cancel": `Usage: grove ci cancel <run-id> [options]
0d9d723139
0d9d723140Cancel a running pipeline.
0d9d723141
0d9d723142Options:
0d9d723143 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
17fe7cd144
17fe7cd145 doctor: `Usage: grove doctor
17fe7cd146
17fe7cd147Check your Grove setup for common issues.
17fe7cd148
17fe7cd149Checks:
17fe7cd150 - Grove config and authentication
17fe7cd151 - Auth token validity
17fe7cd152 - TLS certificates
17fe7cd153 - Sapling (sl) installation and PATH
17fe7cd154 - Hub reachability
17fe7cd155 - Repository config (if inside a repo)`,
0d9d723156};
0d9d723157
0d9d723158function showHelp(command: string): boolean {
0d9d723159 const help = COMMAND_HELP[command];
0d9d723160 if (help) {
0d9d723161 console.log(help);
0d9d723162 return true;
0d9d723163 }
0d9d723164 return false;
0d9d723165}
0d9d723166
e93a978167const args = process.argv.slice(2);
e93a978168const cmd = args[0];
e93a978169const sub = args[1];
e93a978170const rest = args.slice(2);
e93a978171
0d9d723172function wantsHelp(a: string[]): boolean {
0d9d723173 return a.includes("--help") || a.includes("-h");
0d9d723174}
0d9d723175
e93a978176async function main() {
e93a978177 if (!cmd || cmd === "--help" || cmd === "-h") {
e93a978178 console.log(USAGE);
e93a978179 process.exit(0);
e93a978180 }
e93a978181
e93a978182 if (cmd === "--version" || cmd === "-v") {
e93a978183 const { readFile } = await import("node:fs/promises");
e93a978184 const { fileURLToPath } = await import("node:url");
e93a978185 const { dirname, join } = await import("node:path");
e93a978186 const dir = dirname(fileURLToPath(import.meta.url));
e93a978187 const pkg = JSON.parse(await readFile(join(dir, "..", "package.json"), "utf-8"));
e93a978188 console.log(`grove ${pkg.version}`);
e93a978189 process.exit(0);
e93a978190 }
e93a978191
e93a978192 if (cmd === "auth") {
0d9d723193 if (wantsHelp(args)) {
0d9d723194 const key = sub && !sub.startsWith("-") ? `auth ${sub}` : undefined;
0d9d723195 if (key && showHelp(key)) process.exit(0);
0d9d723196 console.log(`Usage: grove auth <login|status>\n\nSubcommands:\n login Authenticate with Grove (opens browser)\n status Show current authentication status`);
0d9d723197 process.exit(0);
0d9d723198 }
e93a978199 if (sub === "login") return authLogin(rest);
e93a978200 if (sub === "status") return authStatus();
59e6667201 log.error(`Unknown command: auth ${sub || ""}\nRun: grove auth --help`);
e93a978202 process.exit(1);
e93a978203 }
e93a978204
0d9d723205 if (cmd === "init") {
0d9d723206 if (wantsHelp(args)) { showHelp("init"); process.exit(0); }
0d9d723207 return init(args.slice(1));
0d9d723208 }
0d9d723209 if (cmd === "clone") {
0d9d723210 if (wantsHelp(args)) { showHelp("clone"); process.exit(0); }
0d9d723211 return clone(args.slice(1));
0d9d723212 }
0d9d723213 if (cmd === "status") {
0d9d723214 if (wantsHelp(args)) { showHelp("status"); process.exit(0); }
0d9d723215 return status();
0d9d723216 }
0d9d723217 if (cmd === "whoami") {
0d9d723218 if (wantsHelp(args)) { showHelp("whoami"); process.exit(0); }
0d9d723219 return whoami();
0d9d723220 }
e93a978221
e93a978222 if (cmd === "repo") {
0d9d723223 if (wantsHelp(args)) {
0d9d723224 const key = sub && !sub.startsWith("-") ? `repo ${sub}` : undefined;
0d9d723225 if (key && showHelp(key)) process.exit(0);
0d9d723226 console.log(`Usage: grove repo <list|create>\n\nSubcommands:\n list List repositories\n create Create a repository`);
0d9d723227 process.exit(0);
0d9d723228 }
e93a978229 if (sub === "list" || sub === "ls") return repoList();
e93a978230 if (sub === "create") return repoCreate(rest);
59e6667231 log.error(`Unknown command: repo ${sub || ""}\nRun: grove repo --help`);
e93a978232 process.exit(1);
e93a978233 }
e93a978234
e93a978235 if (cmd === "instance") {
0d9d723236 if (wantsHelp(args)) {
0d9d723237 const key = sub && !sub.startsWith("-") ? `instance ${sub}` : undefined;
0d9d723238 if (key && showHelp(key)) process.exit(0);
0d9d723239 console.log(`Usage: grove instance <create>\n\nSubcommands:\n create Register a Grove instance`);
0d9d723240 process.exit(0);
0d9d723241 }
e93a978242 if (sub === "create") return instanceCreate(rest);
59e6667243 log.error(`Unknown command: instance ${sub || ""}\nRun: grove instance --help`);
e93a978244 process.exit(1);
e93a978245 }
e93a978246
17fe7cd247 if (cmd === "doctor") {
17fe7cd248 if (wantsHelp(args)) { showHelp("doctor"); process.exit(0); }
17fe7cd249 return doctor();
17fe7cd250 }
17fe7cd251
69d1a72252 if (cmd === "ci") {
0d9d723253 if (wantsHelp(args)) {
0d9d723254 const key = sub && !sub.startsWith("-") ? `ci ${sub}` : undefined;
0d9d723255 if (key && showHelp(key)) process.exit(0);
0d9d723256 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`);
0d9d723257 process.exit(0);
0d9d723258 }
69d1a72259 if (sub === "runs" || sub === "ls") return ciRuns(rest);
69d1a72260 if (sub === "status" || sub === "show") return ciStatus(rest);
69d1a72261 if (sub === "logs" || sub === "log") return ciLogs(rest);
69d1a72262 if (sub === "trigger" || sub === "run") return ciTrigger(rest);
69d1a72263 if (sub === "cancel") return ciCancel(rest);
59e6667264 log.error(`Unknown command: ci ${sub || ""}\nRun: grove ci --help`);
69d1a72265 process.exit(1);
69d1a72266 }
69d1a72267
59e6667268 log.error(`Unknown command: ${cmd}\nRun: grove --help`);
e93a978269 process.exit(1);
e93a978270}
e93a978271
e93a978272main().catch((err) => {
59e6667273 log.error(err.message || String(err));
e93a978274 process.exit(1);
e93a978275});