9.1 KB276 lines
Blame
1#!/usr/bin/env node
2
3import { log } from "@clack/prompts";
4import { authLogin } from "./commands/auth-login.js";
5import { authStatus } from "./commands/auth-status.js";
6import { whoami } from "./commands/whoami.js";
7import { clone } from "./commands/clone.js";
8import { init } from "./commands/init.js";
9import { status } from "./commands/status.js";
10import { repoList } from "./commands/repo-list.js";
11import { repoCreate } from "./commands/repo-create.js";
12import { instanceCreate } from "./commands/instance-create.js";
13import { ciRuns } from "./commands/ci-runs.js";
14import { ciStatus } from "./commands/ci-status.js";
15import { ciLogs } from "./commands/ci-logs.js";
16import { ciTrigger } from "./commands/ci-trigger.js";
17import { ciCancel } from "./commands/ci-cancel.js";
18import { doctor } from "./commands/doctor.js";
19
20const USAGE = `Usage: grove <command>
21
22Commands:
23 init Create a Grove repo and initialize Sapling in current directory
24 clone Clone a Grove repository
25 status Show current repository info
26 auth login Authenticate with Grove (opens browser)
27 auth status Show current authentication status
28 whoami Show current user
29 repo list List repositories
30 repo create Create a repository
31 instance create Register a Grove instance
32 doctor Check your Grove setup for common issues
33 ci runs List pipeline runs
34 ci status Show pipeline run details
35 ci logs Show step logs
36 ci trigger Manually trigger pipelines
37 ci cancel Cancel a running pipeline
38
39Options:
40 --help Show this help message
41 --version Show version`;
42
43const COMMAND_HELP: Record<string, string> = {
44 init: `Usage: grove init [directory] [options]
45
46Create a Grove repository and initialize Sapling in the current directory.
47If the directory contains a .git repo, imports full git history.
48
49Options:
50 --owner <org> Set the repository owner (org name)
51 --description <desc> Set the repository description
52 --private Create a private repository
53 --branch <branch> Set the default branch name`,
54
55 clone: `Usage: grove clone <owner/repo|repo> [destination]
56
57Clone a Grove repository into a new directory.
58If only a repo name is given (no owner/), looks up the owner via the API.`,
59
60 status: `Usage: grove status
61
62Show info about the current Grove repository (name, owner, branch, URL).`,
63
64 whoami: `Usage: grove whoami
65
66Show the currently authenticated user.`,
67
68 "auth login": `Usage: grove auth login [--hub <url>]
69
70Authenticate with Grove by opening the browser.
71
72Options:
73 --hub <url> Override the Grove hub URL`,
74
75 "auth status": `Usage: grove auth status
76
77Show current authentication status, token expiry, and hub URL.`,
78
79 "repo list": `Usage: grove repo list
80
81List all repositories you have access to.`,
82
83 "repo create": `Usage: grove repo create <name> [options]
84
85Create a new repository on Grove without initializing a local directory.
86Use 'grove init' to create and initialize in one step.
87
88Options:
89 --owner <org> Set the repository owner (org name)
90 --description <desc> Set the repository description
91 --branch <branch> Set the default branch name
92 --private Create a private repository
93 --no-seed Skip creating an initial commit`,
94
95 "instance create": `Usage: grove instance create --region <region> --size <size> [options]
96
97Register a new Grove instance.
98
99Options:
100 --name <name> Instance name (default: "grove")
101 --region <region> Region (required)
102 --size <size> Instance size (required)
103 --ip <ip> IP address
104 --domain <domain> Domain name`,
105
106 "ci runs": `Usage: grove ci runs [options]
107
108List pipeline runs for the current repository.
109
110Options:
111 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
112 --status <status> Filter by status (passed, failed, running, pending)
113 --limit <n> Number of runs to show (default: 10)`,
114
115 "ci status": `Usage: grove ci status [run-id] [options]
116
117Show details and steps for a pipeline run.
118If no run ID is given, shows the latest run.
119
120Options:
121 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
122
123 "ci logs": `Usage: grove ci logs <run-id> <step-index> [options]
124
125Show logs for a specific step in a pipeline run.
126
127Options:
128 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
129
130 "ci trigger": `Usage: grove ci trigger [options]
131
132Manually trigger pipelines for the current repository.
133
134Options:
135 --repo <owner/repo> Specify repository (default: inferred from .sl/config)
136 --ref <branch> Branch to trigger on`,
137
138 "ci cancel": `Usage: grove ci cancel <run-id> [options]
139
140Cancel a running pipeline.
141
142Options:
143 --repo <owner/repo> Specify repository (default: inferred from .sl/config)`,
144
145 doctor: `Usage: grove doctor
146
147Check your Grove setup for common issues.
148
149Checks:
150 - Grove config and authentication
151 - Auth token validity
152 - TLS certificates
153 - Sapling (sl) installation and PATH
154 - Hub reachability
155 - Repository config (if inside a repo)`,
156};
157
158function showHelp(command: string): boolean {
159 const help = COMMAND_HELP[command];
160 if (help) {
161 console.log(help);
162 return true;
163 }
164 return false;
165}
166
167const args = process.argv.slice(2);
168const cmd = args[0];
169const sub = args[1];
170const rest = args.slice(2);
171
172function wantsHelp(a: string[]): boolean {
173 return a.includes("--help") || a.includes("-h");
174}
175
176async function main() {
177 if (!cmd || cmd === "--help" || cmd === "-h") {
178 console.log(USAGE);
179 process.exit(0);
180 }
181
182 if (cmd === "--version" || cmd === "-v") {
183 const { readFile } = await import("node:fs/promises");
184 const { fileURLToPath } = await import("node:url");
185 const { dirname, join } = await import("node:path");
186 const dir = dirname(fileURLToPath(import.meta.url));
187 const pkg = JSON.parse(await readFile(join(dir, "..", "package.json"), "utf-8"));
188 console.log(`grove ${pkg.version}`);
189 process.exit(0);
190 }
191
192 if (cmd === "auth") {
193 if (wantsHelp(args)) {
194 const key = sub && !sub.startsWith("-") ? `auth ${sub}` : undefined;
195 if (key && showHelp(key)) process.exit(0);
196 console.log(`Usage: grove auth <login|status>\n\nSubcommands:\n login Authenticate with Grove (opens browser)\n status Show current authentication status`);
197 process.exit(0);
198 }
199 if (sub === "login") return authLogin(rest);
200 if (sub === "status") return authStatus();
201 log.error(`Unknown command: auth ${sub || ""}\nRun: grove auth --help`);
202 process.exit(1);
203 }
204
205 if (cmd === "init") {
206 if (wantsHelp(args)) { showHelp("init"); process.exit(0); }
207 return init(args.slice(1));
208 }
209 if (cmd === "clone") {
210 if (wantsHelp(args)) { showHelp("clone"); process.exit(0); }
211 return clone(args.slice(1));
212 }
213 if (cmd === "status") {
214 if (wantsHelp(args)) { showHelp("status"); process.exit(0); }
215 return status();
216 }
217 if (cmd === "whoami") {
218 if (wantsHelp(args)) { showHelp("whoami"); process.exit(0); }
219 return whoami();
220 }
221
222 if (cmd === "repo") {
223 if (wantsHelp(args)) {
224 const key = sub && !sub.startsWith("-") ? `repo ${sub}` : undefined;
225 if (key && showHelp(key)) process.exit(0);
226 console.log(`Usage: grove repo <list|create>\n\nSubcommands:\n list List repositories\n create Create a repository`);
227 process.exit(0);
228 }
229 if (sub === "list" || sub === "ls") return repoList();
230 if (sub === "create") return repoCreate(rest);
231 log.error(`Unknown command: repo ${sub || ""}\nRun: grove repo --help`);
232 process.exit(1);
233 }
234
235 if (cmd === "instance") {
236 if (wantsHelp(args)) {
237 const key = sub && !sub.startsWith("-") ? `instance ${sub}` : undefined;
238 if (key && showHelp(key)) process.exit(0);
239 console.log(`Usage: grove instance <create>\n\nSubcommands:\n create Register a Grove instance`);
240 process.exit(0);
241 }
242 if (sub === "create") return instanceCreate(rest);
243 log.error(`Unknown command: instance ${sub || ""}\nRun: grove instance --help`);
244 process.exit(1);
245 }
246
247 if (cmd === "doctor") {
248 if (wantsHelp(args)) { showHelp("doctor"); process.exit(0); }
249 return doctor();
250 }
251
252 if (cmd === "ci") {
253 if (wantsHelp(args)) {
254 const key = sub && !sub.startsWith("-") ? `ci ${sub}` : undefined;
255 if (key && showHelp(key)) process.exit(0);
256 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`);
257 process.exit(0);
258 }
259 if (sub === "runs" || sub === "ls") return ciRuns(rest);
260 if (sub === "status" || sub === "show") return ciStatus(rest);
261 if (sub === "logs" || sub === "log") return ciLogs(rest);
262 if (sub === "trigger" || sub === "run") return ciTrigger(rest);
263 if (sub === "cancel") return ciCancel(rest);
264 log.error(`Unknown command: ci ${sub || ""}\nRun: grove ci --help`);
265 process.exit(1);
266 }
267
268 log.error(`Unknown command: ${cmd}\nRun: grove --help`);
269 process.exit(1);
270}
271
272main().catch((err) => {
273 log.error(err.message || String(err));
274 process.exit(1);
275});
276