@devdogsuga/devtools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +44 -0
  2. package/bin/devtools-ci-bare.mjs +48 -0
  3. package/bin/devtools-ci.mjs +17 -0
  4. package/bin/devtools.mjs +27 -0
  5. package/dist/args.d.ts +26 -0
  6. package/dist/args.js +78 -0
  7. package/dist/bws/bw.d.ts +9 -0
  8. package/dist/bws/bw.js +49 -0
  9. package/dist/bws/client.d.ts +43 -0
  10. package/dist/bws/client.js +363 -0
  11. package/dist/bws/environments.d.ts +96 -0
  12. package/dist/bws/environments.js +111 -0
  13. package/dist/bws/pace.d.ts +27 -0
  14. package/dist/bws/pace.js +43 -0
  15. package/dist/bws/retry.d.ts +12 -0
  16. package/dist/bws/retry.js +39 -0
  17. package/dist/bws/store.d.ts +1 -0
  18. package/dist/bws/store.js +32 -0
  19. package/dist/bws/token.d.ts +31 -0
  20. package/dist/bws/token.js +88 -0
  21. package/dist/bws/vault.d.ts +29 -0
  22. package/dist/bws/vault.js +200 -0
  23. package/dist/catalog.d.ts +55 -0
  24. package/dist/catalog.js +81 -0
  25. package/dist/cf/commands.d.ts +1 -0
  26. package/dist/cf/commands.js +182 -0
  27. package/dist/cf/local-env.d.ts +44 -0
  28. package/dist/cf/local-env.js +148 -0
  29. package/dist/ci.d.ts +1 -0
  30. package/dist/ci.js +319 -0
  31. package/dist/cli.d.ts +11 -0
  32. package/dist/cli.js +1077 -0
  33. package/dist/commands.d.ts +255 -0
  34. package/dist/commands.js +1279 -0
  35. package/dist/completions.d.ts +4 -0
  36. package/dist/completions.js +110 -0
  37. package/dist/cron/commands.d.ts +47 -0
  38. package/dist/cron/commands.js +372 -0
  39. package/dist/cron/discovery.d.ts +55 -0
  40. package/dist/cron/discovery.js +85 -0
  41. package/dist/cron/schema.d.ts +45 -0
  42. package/dist/cron/schema.js +35 -0
  43. package/dist/db/config-push.d.ts +8 -0
  44. package/dist/db/config-push.js +11 -0
  45. package/dist/db/connection.d.ts +79 -0
  46. package/dist/db/connection.js +114 -0
  47. package/dist/db/exec.d.ts +1 -0
  48. package/dist/db/exec.js +11 -0
  49. package/dist/db/generate-migration.d.ts +1 -0
  50. package/dist/db/generate-migration.js +67 -0
  51. package/dist/db/generate-types.d.ts +1 -0
  52. package/dist/db/generate-types.js +4 -0
  53. package/dist/db/introspect.d.ts +1 -0
  54. package/dist/db/introspect.js +113 -0
  55. package/dist/db/local-env.d.ts +65 -0
  56. package/dist/db/local-env.js +23 -0
  57. package/dist/db/new-migration.d.ts +1 -0
  58. package/dist/db/new-migration.js +8 -0
  59. package/dist/db/run.d.ts +70 -0
  60. package/dist/db/run.js +158 -0
  61. package/dist/db/seed-buckets.d.ts +2 -0
  62. package/dist/db/seed-buckets.js +4 -0
  63. package/dist/db/seed-production.d.ts +8 -0
  64. package/dist/db/seed-production.js +73 -0
  65. package/dist/db/seed-roles.d.ts +7 -0
  66. package/dist/db/seed-roles.js +31 -0
  67. package/dist/db/session-refresh.d.ts +43 -0
  68. package/dist/db/session-refresh.js +40 -0
  69. package/dist/deploy/migrations.d.ts +20 -0
  70. package/dist/deploy/migrations.js +68 -0
  71. package/dist/deploy/orphans.d.ts +20 -0
  72. package/dist/deploy/orphans.js +177 -0
  73. package/dist/deploy/preflight.d.ts +53 -0
  74. package/dist/deploy/preflight.js +189 -0
  75. package/dist/deploy/report.d.ts +26 -0
  76. package/dist/deploy/report.js +70 -0
  77. package/dist/deploy/require-planner.d.ts +2 -0
  78. package/dist/deploy/require-planner.js +60 -0
  79. package/dist/deploy/require-token.d.ts +15 -0
  80. package/dist/deploy/require-token.js +78 -0
  81. package/dist/deploy/secrets-file.d.ts +17 -0
  82. package/dist/deploy/secrets-file.js +167 -0
  83. package/dist/deploy/write-env.d.ts +40 -0
  84. package/dist/deploy/write-env.js +393 -0
  85. package/dist/docs/index-pages.d.ts +36 -0
  86. package/dist/docs/index-pages.js +184 -0
  87. package/dist/doctor.d.ts +62 -0
  88. package/dist/doctor.js +225 -0
  89. package/dist/emails/commands.d.ts +15 -0
  90. package/dist/emails/commands.js +130 -0
  91. package/dist/emails/email-types.d.ts +13 -0
  92. package/dist/emails/email-types.js +1 -0
  93. package/dist/emails/fixtures.d.ts +20 -0
  94. package/dist/emails/fixtures.js +20 -0
  95. package/dist/env/audit.d.ts +206 -0
  96. package/dist/env/audit.js +436 -0
  97. package/dist/env/cloudflare.d.ts +21 -0
  98. package/dist/env/cloudflare.js +61 -0
  99. package/dist/env/commands.d.ts +99 -0
  100. package/dist/env/commands.js +612 -0
  101. package/dist/env/discovery.d.ts +15 -0
  102. package/dist/env/discovery.js +226 -0
  103. package/dist/env/document.d.ts +119 -0
  104. package/dist/env/document.js +323 -0
  105. package/dist/env/example.d.ts +68 -0
  106. package/dist/env/example.js +603 -0
  107. package/dist/env/selection.d.ts +105 -0
  108. package/dist/env/selection.js +226 -0
  109. package/dist/environment.d.ts +92 -0
  110. package/dist/environment.js +206 -0
  111. package/dist/fingerprint.d.ts +10 -0
  112. package/dist/fingerprint.js +18 -0
  113. package/dist/gen/campus-map.d.ts +1 -0
  114. package/dist/gen/campus-map.js +473 -0
  115. package/dist/gen/commands.d.ts +1 -0
  116. package/dist/gen/commands.js +25 -0
  117. package/dist/gen/hypno.d.ts +1 -0
  118. package/dist/gen/hypno.js +134 -0
  119. package/dist/gh/client.d.ts +80 -0
  120. package/dist/gh/client.js +258 -0
  121. package/dist/gh/environments.d.ts +83 -0
  122. package/dist/gh/environments.js +178 -0
  123. package/dist/gh/rulesets/actors.d.ts +14 -0
  124. package/dist/gh/rulesets/actors.js +68 -0
  125. package/dist/gh/rulesets/api.d.ts +15 -0
  126. package/dist/gh/rulesets/api.js +99 -0
  127. package/dist/gh/rulesets/commands.d.ts +3 -0
  128. package/dist/gh/rulesets/commands.js +203 -0
  129. package/dist/gh/rulesets/desired.d.ts +112 -0
  130. package/dist/gh/rulesets/desired.js +247 -0
  131. package/dist/gh/rulesets/diff.d.ts +65 -0
  132. package/dist/gh/rulesets/diff.js +134 -0
  133. package/dist/gh/rulesets/types.d.ts +76 -0
  134. package/dist/gh/rulesets/types.js +13 -0
  135. package/dist/gh/settings/api.d.ts +40 -0
  136. package/dist/gh/settings/api.js +172 -0
  137. package/dist/gh/settings/commands.d.ts +1 -0
  138. package/dist/gh/settings/commands.js +245 -0
  139. package/dist/gh/settings/desired.d.ts +53 -0
  140. package/dist/gh/settings/desired.js +39 -0
  141. package/dist/gh/settings/diff.d.ts +57 -0
  142. package/dist/gh/settings/diff.js +156 -0
  143. package/dist/gh/settings/types.d.ts +92 -0
  144. package/dist/gh/settings/types.js +11 -0
  145. package/dist/gh/settings/workflows.d.ts +41 -0
  146. package/dist/gh/settings/workflows.js +124 -0
  147. package/dist/grantRoot.d.ts +42 -0
  148. package/dist/grantRoot.js +89 -0
  149. package/dist/help.d.ts +18 -0
  150. package/dist/help.js +169 -0
  151. package/dist/images/commands.d.ts +35 -0
  152. package/dist/images/commands.js +233 -0
  153. package/dist/images/events.d.ts +36 -0
  154. package/dist/images/events.js +152 -0
  155. package/dist/images/graphics.d.ts +70 -0
  156. package/dist/images/graphics.js +233 -0
  157. package/dist/images/open-graph-types.d.ts +75 -0
  158. package/dist/images/open-graph-types.js +1 -0
  159. package/dist/images/prompts.d.ts +34 -0
  160. package/dist/images/prompts.js +100 -0
  161. package/dist/images/render.d.ts +21 -0
  162. package/dist/images/render.js +57 -0
  163. package/dist/images/select.d.ts +64 -0
  164. package/dist/images/select.js +96 -0
  165. package/dist/instance.d.ts +67 -0
  166. package/dist/instance.js +129 -0
  167. package/dist/invocation.d.ts +49 -0
  168. package/dist/invocation.js +78 -0
  169. package/dist/launch-ci.d.ts +1 -0
  170. package/dist/launch-ci.js +86 -0
  171. package/dist/launch.d.ts +29 -0
  172. package/dist/launch.js +371 -0
  173. package/dist/menu.d.ts +31 -0
  174. package/dist/menu.js +277 -0
  175. package/dist/newsletter/commands.d.ts +16 -0
  176. package/dist/newsletter/commands.js +311 -0
  177. package/dist/newsletter/imap.d.ts +17 -0
  178. package/dist/newsletter/imap.js +127 -0
  179. package/dist/newsletter/loopback.d.ts +24 -0
  180. package/dist/newsletter/loopback.js +100 -0
  181. package/dist/newsletter/oauth.d.ts +26 -0
  182. package/dist/newsletter/oauth.js +130 -0
  183. package/dist/newsletter/smtp.d.ts +23 -0
  184. package/dist/newsletter/smtp.js +133 -0
  185. package/dist/oauth/browser.d.ts +1 -0
  186. package/dist/oauth/browser.js +13 -0
  187. package/dist/oauth/config.d.ts +24 -0
  188. package/dist/oauth/config.js +24 -0
  189. package/dist/oauth/connect-url.d.ts +17 -0
  190. package/dist/oauth/connect-url.js +23 -0
  191. package/dist/oauth/db.d.ts +37 -0
  192. package/dist/oauth/db.js +108 -0
  193. package/dist/oauth/device.d.ts +93 -0
  194. package/dist/oauth/device.js +190 -0
  195. package/dist/oauth/discovery.d.ts +28 -0
  196. package/dist/oauth/discovery.js +54 -0
  197. package/dist/oauth/env-file.d.ts +6 -0
  198. package/dist/oauth/env-file.js +35 -0
  199. package/dist/oauth/exchange.d.ts +43 -0
  200. package/dist/oauth/exchange.js +87 -0
  201. package/dist/oauth/label.d.ts +1 -0
  202. package/dist/oauth/label.js +25 -0
  203. package/dist/oauth/loopback.d.ts +52 -0
  204. package/dist/oauth/loopback.js +162 -0
  205. package/dist/oauth/pkce.d.ts +6 -0
  206. package/dist/oauth/pkce.js +34 -0
  207. package/dist/oauth/target.d.ts +54 -0
  208. package/dist/oauth/target.js +102 -0
  209. package/dist/oauth/transport.d.ts +41 -0
  210. package/dist/oauth/transport.js +32 -0
  211. package/dist/oauth/wizard.d.ts +33 -0
  212. package/dist/oauth/wizard.js +571 -0
  213. package/dist/pick.d.ts +6 -0
  214. package/dist/pick.js +64 -0
  215. package/dist/planner/checks.d.ts +26 -0
  216. package/dist/planner/checks.js +104 -0
  217. package/dist/planner/commands.d.ts +10 -0
  218. package/dist/planner/commands.js +367 -0
  219. package/dist/planner/db.d.ts +15 -0
  220. package/dist/planner/db.js +33 -0
  221. package/dist/planner/role.d.ts +33 -0
  222. package/dist/planner/role.js +68 -0
  223. package/dist/repo/peers.d.ts +24 -0
  224. package/dist/repo/peers.js +114 -0
  225. package/dist/repo/preflight.d.ts +109 -0
  226. package/dist/repo/preflight.js +266 -0
  227. package/dist/repo/resolve.d.ts +41 -0
  228. package/dist/repo/resolve.js +152 -0
  229. package/dist/repo/root.d.ts +23 -0
  230. package/dist/repo/root.js +91 -0
  231. package/dist/repo/source.d.ts +14 -0
  232. package/dist/repo/source.js +54 -0
  233. package/dist/repo/tsx-loader.d.ts +6 -0
  234. package/dist/repo/tsx-loader.js +41 -0
  235. package/dist/run/pick.d.ts +60 -0
  236. package/dist/run/pick.js +559 -0
  237. package/dist/setup.d.ts +1 -0
  238. package/dist/setup.js +152 -0
  239. package/dist/stack.d.ts +48 -0
  240. package/dist/stack.js +290 -0
  241. package/dist/telemetry.d.ts +34 -0
  242. package/dist/telemetry.js +93 -0
  243. package/dist/tier.d.ts +40 -0
  244. package/dist/tier.js +93 -0
  245. package/dist/tsconfig.typecheck.tsbuildinfo +1 -0
  246. package/dist/ui.d.ts +25 -0
  247. package/dist/ui.js +51 -0
  248. package/dist/workers.d.ts +5 -0
  249. package/dist/workers.js +35 -0
  250. package/dist/workflows/commands.d.ts +44 -0
  251. package/dist/workflows/commands.js +668 -0
  252. package/env.ts +158 -0
  253. package/package.json +80 -0
@@ -0,0 +1,1279 @@
1
+ /**
2
+ * The command tree, as data.
3
+ *
4
+ * One declaration, three readers:
5
+ *
6
+ * * `help.ts` renders it, one level at a time.
7
+ * * `menu.ts` walks its interactive commands and options without a second
8
+ * list to keep in step.
9
+ * * contributor documentation uses the same group and command vocabulary.
10
+ *
11
+ * It is deliberately inert: names, summaries and option shapes, no imports of
12
+ * anything that runs. `cli.ts` owns dispatch; the wizard turns a walk of this
13
+ * tree into an argv and hands it to that same dispatcher, which keeps menu and
14
+ * CLI behavior aligned by construction rather than by review.
15
+ *
16
+ * One exception: `WORKER_APP_CHOICES` imports the shared `WORKER_APPS` list
17
+ * (root `workers.json`) so `cf preview`/`typegen`/`build`'s `--app` choices
18
+ * cannot drift from the six-site source of truth. It is data — a string
19
+ * array read from a JSON file — not a function this file calls.
20
+ *
21
+ * ## What a summary is for
22
+ *
23
+ * Every `summary` is one line and is the ONLY thing `--help` prints for a
24
+ * command at the level above it. Rationale, target tables, credential lookup
25
+ * order and deploy internals live in `docs/`, not here: `--help` is a map, and
26
+ * a map that reprints the territory is the thing this replaced.
27
+ */
28
+ import { workerApps } from "./workers.js";
29
+ /**
30
+ * The `--app <slug>` choices shared by `cf preview`/`typegen`/`build`.
31
+ *
32
+ * `workerApps()` reads `workers.json` via `findRepoRoot()`, which throws
33
+ * outside a DevDogsUGA checkout — and this module (via `help.ts`/`menu.ts`)
34
+ * is imported for `devtools --help`, which must work from anywhere,
35
+ * `/tmp` included (see `workers.ts`'s header). The tree below is built once
36
+ * at import time regardless, so this falls back to an empty choice list
37
+ * rather than propagating the throw: `--help` still renders the command
38
+ * shape with no `--app` values listed, and any actual `cf preview --app …`
39
+ * invocation outside a repo fails with the same clear `RepoNotFoundError`
40
+ * it always would have, just a little further downstream.
41
+ */
42
+ const WORKER_APP_CHOICES = (() => {
43
+ try {
44
+ return workerApps().map((app) => ({ value: app, label: app }));
45
+ }
46
+ catch {
47
+ return [];
48
+ }
49
+ })();
50
+ /**
51
+ * How each scope reads, in the two places that draw it.
52
+ *
53
+ * `menu` sits inline in a hint, so it is one word. `help` heads a block of
54
+ * commands, so it can be a phrase. Both live here rather than in the renderers
55
+ * because they are labels, the same kind of data as a group title.
56
+ */
57
+ export const SCOPES = {
58
+ machine: { menu: "This machine", help: "Supabase on this machine" },
59
+ repo: { menu: "Repo", help: "files in the repo" },
60
+ endpoint: { menu: "Database", help: "the session's database (--tier)" },
61
+ infra: {
62
+ menu: "Hosted",
63
+ help: "hosted infrastructure, each naming its own connection",
64
+ },
65
+ };
66
+ // ── Exit codes ───────────────────────────────────────────────────────────────
67
+ /** Standard success. */
68
+ export const EXIT_OK = 0;
69
+ /** Command failed. */
70
+ export const EXIT_FAIL = 1;
71
+ /** `--check` found drift. Distinct from failure so scripts can tell them apart. */
72
+ export const EXIT_DRIFT = 2;
73
+ // ── Shared option shapes ─────────────────────────────────────────────────────
74
+ /**
75
+ * `docs index`'s delete acknowledgment — NOT a database selector.
76
+ *
77
+ * The db namespace's old `--target local|remote` flag is retired outright
78
+ * (the SESSION names the database now — see `db/connection.ts`), but `docs
79
+ * index` keeps this spelling for a different job: its prune deletes rows,
80
+ * and running that against a non-local `DB_URL` requires saying so out
81
+ * loud. The database itself still comes from the session's env.
82
+ */
83
+ export const DOCS_TARGET = {
84
+ flag: "--target",
85
+ value: "<local|remote>",
86
+ summary: "Acknowledge indexing a non-local DB_URL. Defaults to local-only.",
87
+ prompt: {
88
+ kind: "select",
89
+ message: "May this prune a non-local database?",
90
+ choices: [
91
+ { value: "local", label: "Local only", hint: "the default" },
92
+ { value: "remote", label: "Yes — the session's remote DB_URL" },
93
+ ],
94
+ },
95
+ };
96
+ /**
97
+ * The deployment-tier selector, for the commands that resolve a tier of
98
+ * their OWN (cron/workflows run against a chosen tier's env). The session's
99
+ * global `--tier` — `development:local|development:remote|staging|production`,
100
+ * stripped by the launcher before dispatch — is a superset of this
101
+ * vocabulary; this per-command flag still reads plain tiers.
102
+ */
103
+ export const TIER = {
104
+ flag: "--tier",
105
+ value: "<t>",
106
+ summary: "Which deployment tier. Asked for when absent.",
107
+ };
108
+ /** Print what would change; write nothing; exit 0. On every command that writes. */
109
+ export const DRY_RUN = {
110
+ flag: "--dry-run",
111
+ summary: "Print what would change, write nothing, exit 0.",
112
+ };
113
+ /** Verify correctness; write nothing; exit 2 on drift. */
114
+ export const CHECK = {
115
+ flag: "--check",
116
+ summary: "Verify, write nothing, exit 2 on drift.",
117
+ };
118
+ /**
119
+ * Machine-readable output to stdout.
120
+ *
121
+ * Promptless by design: the wizard is already interactive; a flag that switches
122
+ * its output format has no meaning there. `commands.test.ts` pins the promptless
123
+ * set with a "scripting-only" category for exactly this kind of flag.
124
+ */
125
+ export const JSON_FLAG = {
126
+ flag: "--json",
127
+ summary: "Print machine-readable JSON to stdout.",
128
+ };
129
+ const VAULT_TARGET = {
130
+ flag: "--target",
131
+ value: "<t>",
132
+ summary: "preflight, staging or production. Asked for when absent.",
133
+ // No prompt: `pick.ts` already owns this question, and it orders the list
134
+ // least- to most-dangerous so a reflexive Enter cannot select production.
135
+ // Duplicating it here would put production one keystroke closer.
136
+ };
137
+ /**
138
+ * The three flags every `run` task takes.
139
+ *
140
+ * None carries a `prompt`, and that is the point rather than an omission.
141
+ * `run` opens a multiselect of the apps defining the task, so a wizard that
142
+ * asked "every package?", "which filter?" and "which tier?" up front would
143
+ * ask the same question three times and let the answers disagree. `--tier`
144
+ * is promptless for a different reason than `--filter`/`--all`, though: it is
145
+ * not a question `run` asks itself elsewhere (unlike `cron run`'s own
146
+ * `--tier`, which opens a live picker) — it is a scripting input with no
147
+ * wizard equivalent, the same category `--json` sits in. `--help` still
148
+ * documents all three, which is where someone scripting this will look.
149
+ *
150
+ * Same reasoning as `VAULT_TARGET` above: the command owns the question, so the
151
+ * tree declares the flag and stays quiet.
152
+ */
153
+ const RUN_OPTIONS = [
154
+ {
155
+ flag: "--filter",
156
+ value: "<pkg>",
157
+ summary: "Limit to a package. pnpm's own flag; skips the question.",
158
+ },
159
+ {
160
+ flag: "--all",
161
+ summary: "Every package, unfiltered, with nothing asked.",
162
+ },
163
+ {
164
+ flag: "--tier",
165
+ value: "<t>",
166
+ summary: "Load a tier's env into the run. CLI-only; never prompted.",
167
+ },
168
+ ];
169
+ const ENV_FILE = {
170
+ flag: "--file",
171
+ value: "<path>",
172
+ summary: "Read and write this file instead of the target's own.",
173
+ };
174
+ /** Skip every interactive confirmation prompt. On every destructive command. */
175
+ export const YES = {
176
+ flag: "--yes",
177
+ summary: "Skip the confirmations.",
178
+ };
179
+ const ACCESS_TOKEN = {
180
+ flag: "--access-token",
181
+ value: "<token>",
182
+ summary: "Bitwarden Secrets Manager token. Prefer the vault or the env var.",
183
+ };
184
+ const DB_URL = {
185
+ flag: "--db-url",
186
+ value: "<url>",
187
+ summary: "Privileged connection. Defaults to .env.production's DB_URL.",
188
+ prompt: {
189
+ kind: "text",
190
+ message: "Connection URL? (blank uses .env.production's DB_URL)",
191
+ optional: true,
192
+ },
193
+ };
194
+ // ── The tree ─────────────────────────────────────────────────────────────────
195
+ const DECLARED_GROUPS = [
196
+ {
197
+ title: "Start here",
198
+ commands: [
199
+ {
200
+ name: "setup",
201
+ summary: "Check prerequisites and seed .env.",
202
+ hint: "run this first",
203
+ },
204
+ {
205
+ name: "completions",
206
+ summary: "Output a shell completion script for devtools.",
207
+ hint: "pipe to source or write to a file",
208
+ surface: "cli-only",
209
+ options: [
210
+ {
211
+ flag: "--shell",
212
+ value: "<bash|zsh>",
213
+ summary: "Target shell. Asked for when absent.",
214
+ prompt: {
215
+ kind: "select",
216
+ message: "Which shell?",
217
+ choices: [{ value: "bash" }, { value: "zsh" }],
218
+ },
219
+ },
220
+ ],
221
+ },
222
+ ],
223
+ },
224
+ {
225
+ title: "Workspace",
226
+ commands: [
227
+ {
228
+ name: "run",
229
+ summary: "Run a pnpm workspace task, asking which apps first.",
230
+ hint: "build, dev, lint…",
231
+ // The six with a root alias, which are the six a contributor types.
232
+ // NOT an exhaustive list of every package script: `run` forwards
233
+ // whatever name it is given, so `test:coverage` works without being
234
+ // listed here, and this CLI's own unrelated `deploy` command group
235
+ // stays out of a menu where a same-named package script would sit
236
+ // one line away from it.
237
+ subcommands: [
238
+ {
239
+ name: "build",
240
+ summary: "Compile every package an app needs.",
241
+ options: RUN_OPTIONS,
242
+ },
243
+ {
244
+ name: "dev",
245
+ summary: "Start the development servers.",
246
+ options: RUN_OPTIONS,
247
+ },
248
+ {
249
+ name: "typecheck",
250
+ summary: "Run tsc across the workspace.",
251
+ options: RUN_OPTIONS,
252
+ },
253
+ {
254
+ name: "lint",
255
+ summary: "Run ESLint across the workspace.",
256
+ options: RUN_OPTIONS,
257
+ },
258
+ {
259
+ name: "lint:fix",
260
+ summary: "Run ESLint and write what it can fix.",
261
+ options: RUN_OPTIONS,
262
+ },
263
+ {
264
+ name: "test",
265
+ summary: "Run the unit tests.",
266
+ options: RUN_OPTIONS,
267
+ },
268
+ ],
269
+ },
270
+ ],
271
+ },
272
+ {
273
+ title: "Brand",
274
+ commands: [
275
+ {
276
+ name: "emails",
277
+ summary: "Render populated transactional email previews.",
278
+ hint: "HTML or plain text, one template or all",
279
+ // Like `images`, this command owns its dependent questions: formats
280
+ // and output only make sense after the templates have been selected.
281
+ options: [
282
+ {
283
+ flag: "--format",
284
+ value: "<html,text>",
285
+ summary: "Outputs to write. Defaults to html.",
286
+ },
287
+ {
288
+ flag: "--out",
289
+ value: "<dir>",
290
+ summary: "Output directory. Defaults to ./email-previews.",
291
+ },
292
+ {
293
+ flag: "--dry-run",
294
+ summary: "List subjects and destination files without writing.",
295
+ },
296
+ ],
297
+ },
298
+ {
299
+ name: "images",
300
+ summary: "Render a club image at one or more sizes.",
301
+ hint: "brand/*, page/*, app/*, event/*, or * for all",
302
+ // No subcommands: graphics are positional, and several can be named at
303
+ // once. The command owns its interactive path because it can ask in CLI
304
+ // order — graphic, format, output — and derive each question from the
305
+ // previous answer. The outer wizard therefore dispatches bare `images`;
306
+ // these options remain here for help and scripted invocations.
307
+ options: [
308
+ {
309
+ flag: "--format",
310
+ value: "<a,b,…>",
311
+ summary: "Sizes to render: og, gdgc-wide, gdgc-square, savvycal, email-*, icon-*.",
312
+ // Not prompted here: the command asks itself, from the formats the
313
+ // chosen graphics actually support, which a static list cannot know.
314
+ },
315
+ {
316
+ flag: "--all-formats",
317
+ summary: "Every size the named graphics support.",
318
+ },
319
+ {
320
+ flag: "--out",
321
+ value: "<dir>",
322
+ summary: "Write everything into one directory, flat.",
323
+ },
324
+ {
325
+ flag: "--default-out",
326
+ summary: "Write each image where it belongs in the repo.",
327
+ },
328
+ {
329
+ flag: "--dry-run",
330
+ summary: "List what would be written, and what each size is for.",
331
+ },
332
+ ],
333
+ },
334
+ ],
335
+ },
336
+ {
337
+ title: "Moderation",
338
+ commands: [
339
+ {
340
+ name: "catalog",
341
+ summary: "List the report reasons and content types in the database.",
342
+ hint: "what can be reported here",
343
+ needs: "instance-running",
344
+ options: [JSON_FLAG],
345
+ },
346
+ {
347
+ name: "doctor",
348
+ summary: "Check an app's moderation integration.",
349
+ hint: "and whether the catalog holds up",
350
+ needs: "instance-running",
351
+ options: [
352
+ {
353
+ flag: "--app",
354
+ value: "<slug>",
355
+ summary: "App to check. Asked for when absent.",
356
+ },
357
+ JSON_FLAG,
358
+ ],
359
+ },
360
+ {
361
+ name: "roundtrip",
362
+ summary: "File a report, quarantine it, and check the freeze.",
363
+ hint: "end to end, then cleans up",
364
+ needs: "instance-running",
365
+ },
366
+ {
367
+ name: "grant-root",
368
+ summary: "Give an account every permission on your own database.",
369
+ needs: "instance-running",
370
+ options: [
371
+ {
372
+ flag: "--user",
373
+ value: "<email>",
374
+ summary: "Account to grant Root to. Asked for when absent.",
375
+ },
376
+ ],
377
+ },
378
+ ],
379
+ },
380
+ {
381
+ title: "Generate",
382
+ commands: [
383
+ {
384
+ name: "gen",
385
+ summary: "Regenerate committed, generated source.",
386
+ hint: "refresh a tracked artifact",
387
+ subcommands: [
388
+ {
389
+ name: "campus-map",
390
+ summary: "Rebuild the FindUs campus map from OpenStreetMap.",
391
+ hint: "occasional — rerun when OSM improves the area",
392
+ },
393
+ {
394
+ name: "hypno",
395
+ summary: "Bake the hero spiral into a pre-blurred raster.",
396
+ hint: "needs Playwright",
397
+ },
398
+ {
399
+ name: "og-assets",
400
+ summary: "Re-embed OG fonts, icons and brand art.",
401
+ hint: "after a brand, font or Phosphor bump",
402
+ },
403
+ {
404
+ name: "email-templates",
405
+ summary: "Recompile the transactional email chunks.",
406
+ hint: "render → tokenize → emit",
407
+ },
408
+ ],
409
+ },
410
+ ],
411
+ },
412
+ {
413
+ title: "Cron",
414
+ commands: [
415
+ {
416
+ name: "cron",
417
+ summary: "Cloudflare cron triggers: list schedules or fire one now.",
418
+ hint: "list schedules, run a job",
419
+ subcommands: [
420
+ {
421
+ name: "list",
422
+ summary: "Every registered cron: schedule, English description, routes.",
423
+ hint: "the audit view",
424
+ options: [
425
+ {
426
+ flag: "--app",
427
+ value: "<slug>",
428
+ summary: "Limit to one app. All apps if omitted.",
429
+ },
430
+ {
431
+ flag: "--tier",
432
+ value: "<t>",
433
+ summary: "Whose wrangler schedules to read. All tiers if omitted.",
434
+ prompt: {
435
+ kind: "select",
436
+ message: "Which tier's wrangler schedules?",
437
+ choices: [
438
+ { value: "development", hint: "the default" },
439
+ { value: "staging" },
440
+ { value: "production" },
441
+ ],
442
+ },
443
+ },
444
+ JSON_FLAG,
445
+ ],
446
+ },
447
+ {
448
+ name: "run",
449
+ summary: "Choose and fire a configured cron schedule now.",
450
+ hint: "pick a job from the Worker configuration",
451
+ options: [
452
+ {
453
+ flag: "--app",
454
+ value: "<slug>",
455
+ summary: "Limit the discovered jobs to one app.",
456
+ },
457
+ {
458
+ flag: "--tier",
459
+ value: "<t>",
460
+ summary: "development, staging or production. Asked when absent.",
461
+ },
462
+ {
463
+ flag: "--cron",
464
+ value: "<expr>",
465
+ summary: "Fire this schedule without opening the picker.",
466
+ },
467
+ YES,
468
+ ],
469
+ },
470
+ ],
471
+ },
472
+ {
473
+ name: "workflows",
474
+ summary: "Cloudflare Workflows: list configured bindings or trigger one.",
475
+ hint: "pick a workflow from wrangler.jsonc",
476
+ subcommands: [
477
+ {
478
+ name: "list",
479
+ summary: "List every Workflow binding declared by each Wrangler tier.",
480
+ options: [
481
+ {
482
+ flag: "--app",
483
+ value: "<slug>",
484
+ summary: "Limit to one app.",
485
+ },
486
+ {
487
+ flag: "--tier",
488
+ value: "<t>",
489
+ summary: "Limit to development, staging or production.",
490
+ },
491
+ JSON_FLAG,
492
+ ],
493
+ },
494
+ {
495
+ name: "run",
496
+ summary: "Choose and trigger a Workflow through Wrangler.",
497
+ hint: "local session or a deployed tier",
498
+ options: [
499
+ {
500
+ flag: "--app",
501
+ value: "<slug>",
502
+ summary: "Limit the discovered Workflows to one app.",
503
+ },
504
+ {
505
+ flag: "--tier",
506
+ value: "<t>",
507
+ summary: "development, staging or production. Asked when absent.",
508
+ },
509
+ {
510
+ flag: "--workflow",
511
+ value: "<name>",
512
+ summary: "Trigger this configured name without opening the picker.",
513
+ },
514
+ {
515
+ flag: "--params",
516
+ value: "<json>",
517
+ summary: "JSON parameters passed to the Workflow instance.",
518
+ },
519
+ {
520
+ flag: "--port",
521
+ value: "<n>",
522
+ summary: "Local Wrangler session port. Defaults to 8787.",
523
+ },
524
+ YES,
525
+ ],
526
+ },
527
+ {
528
+ name: "serve",
529
+ summary: "Start an app-scoped local Wrangler runtime until Ctrl+C.",
530
+ hint: "secure alternative to bare wrangler dev",
531
+ options: [
532
+ {
533
+ flag: "--app",
534
+ value: "<slug>",
535
+ summary: "Serve the development Workflow for this app.",
536
+ },
537
+ {
538
+ flag: "--port",
539
+ value: "<n>",
540
+ summary: "Local Wrangler session port. Defaults to 8787.",
541
+ },
542
+ ],
543
+ },
544
+ ],
545
+ },
546
+ ],
547
+ },
548
+ {
549
+ title: "GitHub",
550
+ commands: [
551
+ {
552
+ name: "github",
553
+ summary: "Reconcile the repository's rulesets and settings.",
554
+ hint: "rulesets: main, production, ~ALL, team/**, tags; settings: security, Actions, environments",
555
+ subcommands: [
556
+ {
557
+ name: "rulesets",
558
+ summary: "Diff the fixed rulesets against live GitHub; --apply to write.",
559
+ hint: "dry-run plan by default",
560
+ envFree: true,
561
+ options: [
562
+ {
563
+ flag: "--org",
564
+ value: "<org>",
565
+ summary: "GitHub org. Defaults to DevDogsUGA.",
566
+ },
567
+ {
568
+ flag: "--repo",
569
+ value: "<repo>",
570
+ summary: "Repository name. Defaults to DevDogsUGA.",
571
+ },
572
+ {
573
+ flag: "--app-slug",
574
+ value: "<slug>",
575
+ summary: "The GitHub App whose id bypasses team/** creation. Defaults to devdogs-platform.",
576
+ },
577
+ {
578
+ flag: "--apply",
579
+ summary: "Write the plan instead of only printing it.",
580
+ },
581
+ YES,
582
+ JSON_FLAG,
583
+ ],
584
+ },
585
+ {
586
+ name: "settings",
587
+ summary: "Diff security/Actions/environment settings vs live GitHub.",
588
+ hint: "dry-run plan by default",
589
+ envFree: true,
590
+ options: [
591
+ {
592
+ flag: "--org",
593
+ value: "<org>",
594
+ summary: "GitHub org. Defaults to DevDogsUGA.",
595
+ },
596
+ {
597
+ flag: "--repo",
598
+ value: "<repo>",
599
+ summary: "Repository name. Defaults to DevDogsUGA.",
600
+ },
601
+ {
602
+ flag: "--apply",
603
+ summary: "Write fixable drift instead of only printing it.",
604
+ },
605
+ YES,
606
+ JSON_FLAG,
607
+ ],
608
+ },
609
+ ],
610
+ },
611
+ ],
612
+ },
613
+ {
614
+ title: "Project setup",
615
+ commands: [
616
+ {
617
+ name: "oauth",
618
+ summary: 'Configure "Sign in with DevDogs" for this directory.',
619
+ hint: "works outside a DevDogsUGA checkout too",
620
+ // Talks only to whatever local Supabase project is running in `cwd`
621
+ // (see `oauth/db.ts`'s `detectLocalSupabase`) and, for a hosted
622
+ // target, to the platform's own OAuth endpoints — never a
623
+ // DevDogsUGA env file, database, or `DEPLOY_ENV`. TASK-345: a
624
+ // workshop repo with no DevDogsUGA checkout at all must still be
625
+ // able to run `pnpm dlx @devdogsuga/devtools oauth`, so this joins
626
+ // `github rulesets`/`github settings` in the catalog-driven bypass
627
+ // rather than the hardcoded `setup`/`completions` check — see
628
+ // `launch.ts`'s `isEnvFreeCommand`. Inside a checkout, the command's
629
+ // own hosted-target path still resolves a tier itself, lazily, only
630
+ // when a hosted target is actually chosen (see `oauth/wizard.ts`).
631
+ envFree: true,
632
+ options: [
633
+ {
634
+ flag: "--base-url",
635
+ value: "<url>",
636
+ summary: "DevDogs API URL. Only used by \"Paste credentials instead\".",
637
+ prompt: {
638
+ kind: "text",
639
+ message: "DevDogs API URL? (blank asks inside the wizard)",
640
+ optional: true,
641
+ },
642
+ },
643
+ {
644
+ flag: "--platform-url",
645
+ value: "<url>",
646
+ summary: "Platform URL for one-click connect. Defaults to https://devdogsuga.org.",
647
+ prompt: {
648
+ kind: "text",
649
+ message: "Platform URL? (blank uses the default)",
650
+ optional: true,
651
+ },
652
+ },
653
+ {
654
+ flag: "--device",
655
+ summary: "Force the device-code flow instead of the local loopback listener.",
656
+ },
657
+ {
658
+ flag: "--loopback",
659
+ summary: "Force the local loopback listener even over SSH/Codespaces/a dev container.",
660
+ },
661
+ ],
662
+ },
663
+ {
664
+ name: "docs",
665
+ summary: "The documentation search index.",
666
+ subcommands: [
667
+ {
668
+ name: "index",
669
+ summary: "Push the built docs artifact into the search index.",
670
+ hint: "prunes stale rows in the target database",
671
+ scope: "endpoint",
672
+ options: [DOCS_TARGET],
673
+ },
674
+ ],
675
+ },
676
+ ],
677
+ },
678
+ {
679
+ title: "Env files",
680
+ commands: [
681
+ {
682
+ name: "env",
683
+ summary: "One env file per target, synced to Bitwarden and GitHub.",
684
+ subcommands: [
685
+ {
686
+ name: "pull",
687
+ summary: "Bitwarden → the target's file, in place.",
688
+ options: [VAULT_TARGET, ENV_FILE, YES, ACCESS_TOKEN],
689
+ },
690
+ {
691
+ name: "push",
692
+ summary: "The target's file → Bitwarden and GitHub.",
693
+ options: [VAULT_TARGET, ENV_FILE, YES, ACCESS_TOKEN],
694
+ },
695
+ {
696
+ name: "audit",
697
+ summary: "Compare the file, Bitwarden, GitHub and Cloudflare.",
698
+ hint: "reads only",
699
+ options: [VAULT_TARGET, ENV_FILE, YES, ACCESS_TOKEN, JSON_FLAG],
700
+ },
701
+ {
702
+ name: "init",
703
+ summary: "Create a target file, or append newly declared keys.",
704
+ hint: "existing lines are never changed",
705
+ options: [
706
+ {
707
+ flag: "--target",
708
+ value: "<t>",
709
+ summary: "Which file to create. Defaults to development.",
710
+ prompt: {
711
+ kind: "select",
712
+ message: "Create a file for which target?",
713
+ choices: [
714
+ { value: "development", hint: ".env — the default" },
715
+ { value: "preflight", hint: ".env.preflight" },
716
+ { value: "staging", hint: ".env.staging" },
717
+ { value: "production", hint: "⚠️ .env.production" },
718
+ ],
719
+ },
720
+ },
721
+ {
722
+ flag: "--apps",
723
+ value: "<a,b,…>",
724
+ summary: "Which sections to render. Development only; asks.",
725
+ },
726
+ ],
727
+ },
728
+ {
729
+ name: "example",
730
+ summary: "Regenerate .env.example from the manifests.",
731
+ options: [
732
+ {
733
+ flag: "--check",
734
+ summary: "Verify it is current, as CI does. Writes nothing.",
735
+ prompt: {
736
+ kind: "confirm",
737
+ message: "Check only, without rewriting .env.example?",
738
+ initial: true,
739
+ },
740
+ },
741
+ ],
742
+ },
743
+ {
744
+ name: "reset",
745
+ summary: "Blank every value in .env, keeping each commented out.",
746
+ hint: "local only, no target",
747
+ options: [ENV_FILE, YES],
748
+ },
749
+ ],
750
+ },
751
+ {
752
+ // The Bitwarden CLI, not a command of this one: everything after `bw`
753
+ // is handed to it untouched. It is here because it is the login that
754
+ // `env pull` depends on, it ships as a devtools dependency, and the
755
+ // root `bw` alias it replaces was the last thing at the workspace root
756
+ // reaching into this package. Declaring no subcommands is deliberate:
757
+ // Bitwarden's commands are its own to document, and mirroring a slice
758
+ // of them here would go stale on their release schedule, not ours.
759
+ name: "bw",
760
+ summary: "Run the Bitwarden CLI. `bw login` is the one you want.",
761
+ hint: "passes everything through",
762
+ },
763
+ ],
764
+ },
765
+ {
766
+ title: "Database",
767
+ // One command, `db`. Its OWN subcommands carry the scopes — see the
768
+ // header comment on `Scope` and `db`'s `hint` below. Declared in scope
769
+ // order (machine, repo, endpoint, infra, then the unscoped escape hatch)
770
+ // so `--help` and the wizard render one contiguous block per scope
771
+ // instead of several.
772
+ commands: [
773
+ {
774
+ name: "db",
775
+ summary: "Supabase endpoints, and the databases inside them.",
776
+ hint: "start, migrate, reset, types…",
777
+ subcommands: [
778
+ // ── machine — Supabase on this machine ─────────────────────────
779
+ {
780
+ name: "start",
781
+ summary: "Start Supabase on this machine.",
782
+ hint: "boots the Docker containers",
783
+ scope: "machine",
784
+ when: "instance-stopped",
785
+ },
786
+ {
787
+ name: "connect",
788
+ summary: "Run `supabase link` against a hosted project ref.",
789
+ hint: "for driving the bare supabase CLI by hand",
790
+ scope: "machine",
791
+ },
792
+ {
793
+ name: "stop",
794
+ summary: "Shut it down, freeing its containers.",
795
+ hint: "your data survives",
796
+ scope: "machine",
797
+ // Not offered while nothing is running: "stop" against a stopped
798
+ // stack is the one shape of question a menu should never ask.
799
+ when: "instance-running",
800
+ },
801
+ {
802
+ name: "restart",
803
+ summary: "Stop it, then start it again.",
804
+ // The reason this exists rather than being a footnote on `reset`:
805
+ // `config.toml` is read at `supabase start`, so a reset replays
806
+ // migrations into containers still holding the old settings.
807
+ hint: "the only way to pick up config.toml — reset will not",
808
+ scope: "machine",
809
+ when: "instance-running",
810
+ },
811
+ // ── repo — files in the repo, no live connection ───────────────
812
+ {
813
+ name: "migration",
814
+ summary: "Migration files, empty or drafted from schema drift.",
815
+ scope: "repo",
816
+ subcommands: [
817
+ {
818
+ name: "new",
819
+ summary: "Create an empty timestamped migration file.",
820
+ },
821
+ {
822
+ name: "generate",
823
+ summary: "Generate a migration from an app's Drizzle schema drift.",
824
+ options: [
825
+ {
826
+ flag: "--app",
827
+ value: "<slug>",
828
+ summary: "Whose schema. Asked for when absent.",
829
+ },
830
+ ],
831
+ },
832
+ ],
833
+ },
834
+ // ── endpoint — the session's database ──────────────────────────
835
+ {
836
+ name: "status",
837
+ summary: "Report the session database's health, URLs and keys.",
838
+ hint: "reads only",
839
+ scope: "endpoint",
840
+ options: [JSON_FLAG],
841
+ },
842
+ {
843
+ name: "migrate",
844
+ summary: "Apply new migrations to the database.",
845
+ hint: "without erasing anything",
846
+ scope: "endpoint",
847
+ options: [YES],
848
+ },
849
+ {
850
+ name: "reset",
851
+ summary: "Rebuild the database: migrations, seeds, types, buckets.",
852
+ hint: "⚠️ erases the database first",
853
+ scope: "endpoint",
854
+ options: [YES],
855
+ },
856
+ {
857
+ name: "types",
858
+ summary: "Regenerate database.types.ts, format it, rebuild the package.",
859
+ hint: "after any schema change",
860
+ scope: "endpoint",
861
+ options: [],
862
+ },
863
+ {
864
+ name: "seed",
865
+ summary: "Seed storage buckets or the platform's role catalogue.",
866
+ scope: "endpoint",
867
+ subcommands: [
868
+ {
869
+ name: "buckets",
870
+ summary: "Create the storage buckets config.toml declares.",
871
+ options: [],
872
+ },
873
+ {
874
+ name: "roles",
875
+ summary: "Reconcile the platform's role catalogue.",
876
+ options: [],
877
+ },
878
+ {
879
+ name: "production",
880
+ summary: "Apply supabase/seed/production/*.sql — roles and officers.",
881
+ hint: "confirms before writing to a hosted database",
882
+ options: [YES],
883
+ },
884
+ ],
885
+ },
886
+ {
887
+ name: "introspect",
888
+ summary: "Pull an app's live schema into its generated Drizzle files.",
889
+ scope: "endpoint",
890
+ options: [
891
+ {
892
+ flag: "--app",
893
+ value: "<slug>",
894
+ summary: "App whose schema to pull. Asked for when absent.",
895
+ },
896
+ ],
897
+ },
898
+ {
899
+ name: "config",
900
+ summary: "The Supabase config.toml, pushed to the session's project.",
901
+ scope: "endpoint",
902
+ subcommands: [
903
+ {
904
+ name: "push",
905
+ summary: "Push config.toml to the session's hosted project.",
906
+ hint: "hosted sessions only",
907
+ options: [],
908
+ },
909
+ ],
910
+ },
911
+ // ── infra — hosted infrastructure, each naming its own connection
912
+ {
913
+ name: "planner",
914
+ summary: "The migration_planner role the preflight tier may hold.",
915
+ scope: "infra",
916
+ subcommands: [
917
+ {
918
+ name: "status",
919
+ summary: "Does the role exist, hold its two grants, and no more.",
920
+ hint: "reads only — start here",
921
+ options: [DB_URL, JSON_FLAG],
922
+ },
923
+ {
924
+ name: "create",
925
+ summary: "Mint the role, verify it live, write .env.preflight.",
926
+ options: [DB_URL],
927
+ },
928
+ {
929
+ name: "reset-password",
930
+ summary: "Rotate the password. There is no retrieve.",
931
+ options: [DB_URL, YES],
932
+ },
933
+ {
934
+ name: "drop",
935
+ summary: "Remove the role and blank the dead URL.",
936
+ hint: "the recovery path",
937
+ options: [DB_URL, YES],
938
+ },
939
+ ],
940
+ },
941
+ // ── unscoped — the escape hatch ─────────────────────────────────
942
+ {
943
+ name: "exec",
944
+ summary: "Run the Supabase CLI. Everything after -- passes through.",
945
+ hint: "the escape hatch",
946
+ },
947
+ ],
948
+ },
949
+ ],
950
+ },
951
+ {
952
+ title: "Cloudflare",
953
+ commands: [
954
+ {
955
+ name: "cf",
956
+ summary: "Develop and build an app on the Workers runtime.",
957
+ hint: "preview, typegen — deploys live in CI",
958
+ subcommands: [
959
+ {
960
+ name: "preview",
961
+ summary: "Build and serve an app on the Workers runtime.",
962
+ options: [
963
+ {
964
+ flag: "--app",
965
+ value: "<slug>",
966
+ summary: "App to preview. Asked for when absent.",
967
+ prompt: {
968
+ kind: "select",
969
+ message: "Which app?",
970
+ choices: WORKER_APP_CHOICES,
971
+ },
972
+ },
973
+ {
974
+ flag: "--tier",
975
+ value: "<t>",
976
+ summary: "Preview against a tier's env. Defaults to development.",
977
+ // No prompt: the runtime resolver (`tier.ts`) asks this
978
+ // itself, conditionally, for both the wizard and a direct
979
+ // CLI invocation — a `prompt` here would ask it twice, once
980
+ // on this screen and once when the command actually runs.
981
+ },
982
+ YES,
983
+ ],
984
+ },
985
+ {
986
+ name: "typegen",
987
+ summary: "Regenerate cloudflare-env.d.ts from the wrangler config.",
988
+ options: [
989
+ {
990
+ flag: "--app",
991
+ value: "<slug>",
992
+ summary: "App to run typegen for. Asked for when absent.",
993
+ prompt: {
994
+ kind: "select",
995
+ message: "Which app?",
996
+ choices: WORKER_APP_CHOICES,
997
+ },
998
+ },
999
+ {
1000
+ flag: "--check",
1001
+ summary: "Check types only — do not write.",
1002
+ prompt: {
1003
+ kind: "confirm",
1004
+ message: "Check only (do not write)?",
1005
+ initial: true,
1006
+ },
1007
+ },
1008
+ ],
1009
+ },
1010
+ {
1011
+ name: "build",
1012
+ summary: "Build an app's Worker bundle for a tier.",
1013
+ options: [
1014
+ {
1015
+ flag: "--app",
1016
+ value: "<slug>",
1017
+ summary: "App to build. Asked for when absent.",
1018
+ prompt: {
1019
+ kind: "select",
1020
+ message: "Which app?",
1021
+ choices: WORKER_APP_CHOICES,
1022
+ },
1023
+ },
1024
+ {
1025
+ flag: "--tier",
1026
+ value: "<t>",
1027
+ summary: "Which deployment tier to build for.",
1028
+ prompt: {
1029
+ kind: "select",
1030
+ message: "Which tier?",
1031
+ choices: [
1032
+ { value: "staging" },
1033
+ { value: "production", hint: "⚠️ live bundle" },
1034
+ ],
1035
+ },
1036
+ },
1037
+ ],
1038
+ },
1039
+ {
1040
+ name: "exec",
1041
+ summary: "Run wrangler. Everything after -- passes through.",
1042
+ hint: "the escape hatch",
1043
+ },
1044
+ ],
1045
+ },
1046
+ ],
1047
+ },
1048
+ ];
1049
+ /**
1050
+ * The contributor-facing navigation, grouped by the job somebody is doing.
1051
+ *
1052
+ * Command declarations stay close to their domain-specific option data above;
1053
+ * this projection is the one place that decides how the root help and menu
1054
+ * read. Paths do not change when a command moves between groups.
1055
+ */
1056
+ const declaredByName = new Map(DECLARED_GROUPS.flatMap((group) => group.commands).map((command) => [
1057
+ command.name,
1058
+ command,
1059
+ ]));
1060
+ function commands(...names) {
1061
+ return names.map((name) => {
1062
+ const command = declaredByName.get(name);
1063
+ if (!command)
1064
+ throw new Error(`Command "${name}" is not declared.`);
1065
+ return command;
1066
+ });
1067
+ }
1068
+ export const GROUPS = [
1069
+ {
1070
+ title: "Workspace",
1071
+ commands: commands("setup", "oauth", "run", "gen", "docs"),
1072
+ },
1073
+ {
1074
+ title: "Runtime & infrastructure",
1075
+ commands: commands("db", "cf", "cron", "workflows"),
1076
+ },
1077
+ {
1078
+ title: "Content & communications",
1079
+ commands: commands("images", "emails"),
1080
+ },
1081
+ {
1082
+ title: "Configuration & integrations",
1083
+ commands: commands("env", "bw"),
1084
+ },
1085
+ {
1086
+ title: "Moderation",
1087
+ commands: commands("catalog", "doctor", "roundtrip", "grant-root"),
1088
+ },
1089
+ {
1090
+ title: "GitHub",
1091
+ commands: commands("github"),
1092
+ },
1093
+ {
1094
+ title: "CLI utilities",
1095
+ commands: commands("completions"),
1096
+ },
1097
+ ];
1098
+ // ── CI command tree ───────────────────────────────────────────────────────────
1099
+ /**
1100
+ * The commands the `devtools-ci` bin exposes.
1101
+ *
1102
+ * Kept separate from `GROUPS` because CI commands are never reached from the
1103
+ * wizard and never rendered in `--help`. They live here so the docs build can
1104
+ * render a CI reference page from the same declaration, and so the tests that
1105
+ * guard the style guide can cover both trees without duplicating the pins.
1106
+ */
1107
+ export const CI_GROUPS = [
1108
+ {
1109
+ title: "Deploy",
1110
+ commands: [
1111
+ {
1112
+ name: "deploy",
1113
+ summary: "Deploy an app: token gate, per-app steps, upload.",
1114
+ options: [TIER, DRY_RUN],
1115
+ subcommands: [
1116
+ // ── App orchestrators ────────────────────────────────────────────
1117
+ {
1118
+ name: "platform",
1119
+ summary: "Deploy the platform app.",
1120
+ options: [TIER, DRY_RUN],
1121
+ },
1122
+ {
1123
+ name: "schedule-builder",
1124
+ summary: "Deploy the schedule-builder app.",
1125
+ options: [TIER, DRY_RUN],
1126
+ },
1127
+ {
1128
+ name: "sandbox",
1129
+ summary: "Deploy the sandbox app.",
1130
+ options: [TIER, DRY_RUN],
1131
+ },
1132
+ // ── Step commands ────────────────────────────────────────────────
1133
+ {
1134
+ name: "write-env",
1135
+ summary: "Compose .env.<DEPLOY_ENV> from the GitHub environment.",
1136
+ options: [
1137
+ {
1138
+ flag: "--source",
1139
+ value: "<manifest>",
1140
+ summary: "Compose one manifest's slice instead of all.",
1141
+ },
1142
+ ],
1143
+ },
1144
+ {
1145
+ name: "secrets-file",
1146
+ summary: "Write the --secrets-file wrangler uploads with a Worker.",
1147
+ options: [
1148
+ {
1149
+ flag: "--app",
1150
+ value: "<app>",
1151
+ summary: "Whose manifest declares the Worker's secrets.",
1152
+ },
1153
+ ],
1154
+ },
1155
+ {
1156
+ name: "orphans",
1157
+ summary: "Report Worker secrets nothing declares.",
1158
+ options: [
1159
+ {
1160
+ flag: "--prune",
1161
+ summary: "Delete them. production-apply only.",
1162
+ },
1163
+ ],
1164
+ },
1165
+ {
1166
+ name: "preflight",
1167
+ summary: "Classify the project: paused (skip) vs broken (fail).",
1168
+ },
1169
+ {
1170
+ name: "require-token",
1171
+ summary: "Refuse to deploy without CLOUDFLARE_API_TOKEN.",
1172
+ },
1173
+ {
1174
+ name: "require-planner",
1175
+ summary: "Refuse to plan unless DB_URL is the planner role.",
1176
+ },
1177
+ {
1178
+ name: "plan",
1179
+ summary: "Dry-run the migrations into the job summary.",
1180
+ options: [
1181
+ {
1182
+ flag: "--label",
1183
+ value: "<title>",
1184
+ summary: "Heading for the summary section.",
1185
+ },
1186
+ ],
1187
+ },
1188
+ {
1189
+ name: "migrate",
1190
+ summary: "Apply the migrations to DB_URL.",
1191
+ },
1192
+ ],
1193
+ },
1194
+ ],
1195
+ },
1196
+ ];
1197
+ // ── Lookup ───────────────────────────────────────────────────────────────────
1198
+ /** Every top-level command, in group order. */
1199
+ export const TOP_LEVEL = GROUPS.flatMap((group) => group.commands);
1200
+ /** Every top-level CI command, in group order. */
1201
+ export const CI_TOP_LEVEL = CI_GROUPS.flatMap((group) => group.commands);
1202
+ /**
1203
+ * Walks a path in the CI tree, returning `null` at the first miss.
1204
+ *
1205
+ * Identical to `findCommand` but over `CI_TOP_LEVEL` rather than `TOP_LEVEL`.
1206
+ */
1207
+ export function findCiCommand(path) {
1208
+ let nodes = CI_TOP_LEVEL;
1209
+ let found = null;
1210
+ for (const name of path) {
1211
+ const next = nodes.find((node) => node.name === name);
1212
+ if (!next)
1213
+ return null;
1214
+ found = next;
1215
+ nodes = next.subcommands ?? [];
1216
+ }
1217
+ return found;
1218
+ }
1219
+ /** The subcommand names under a CI path. */
1220
+ export function subcommandCiNames(path) {
1221
+ return (findCiCommand(path)?.subcommands ?? []).map((node) => node.name);
1222
+ }
1223
+ /**
1224
+ * Walks a path like `["env", "pull"]`, returning `null` at the first miss.
1225
+ *
1226
+ * Callers use `null` to mean "not a command", which is the same answer the
1227
+ * dispatcher gives, so an unknown name reads the same whichever notices first.
1228
+ */
1229
+ export function findCommand(path) {
1230
+ let nodes = TOP_LEVEL;
1231
+ let found = null;
1232
+ for (const name of path) {
1233
+ const next = nodes.find((node) => node.name === name);
1234
+ if (!next)
1235
+ return null;
1236
+ found = next;
1237
+ nodes = next.subcommands ?? [];
1238
+ }
1239
+ return found;
1240
+ }
1241
+ /** The group a top-level command sits in, for the wizard's first screen. */
1242
+ export function groupOf(name) {
1243
+ return GROUPS.find((group) => group.commands.some((command) => command.name === name));
1244
+ }
1245
+ /**
1246
+ * The subcommand names under a path, in the order they are declared.
1247
+ *
1248
+ * This is what the dispatchers in `cli.ts` validate against. A subcommand the
1249
+ * tree does not declare is refused by the CLI, and an interactive one it does
1250
+ * declare is in the menu. There is one list, and this reads it.
1251
+ */
1252
+ export function subcommandNames(path) {
1253
+ return (findCommand(path)?.subcommands ?? []).map((node) => node.name);
1254
+ }
1255
+ /** `pull, push, audit, init, example or reset`, for a refusal message. */
1256
+ export function subcommandList(path) {
1257
+ const names = subcommandNames(path);
1258
+ if (names.length <= 1)
1259
+ return names.join("");
1260
+ return `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
1261
+ }
1262
+ /**
1263
+ * Every command path in the tree, deepest names included.
1264
+ *
1265
+ * Used by the coverage test, and by anything that wants to enumerate the CLI
1266
+ * (the docs build's reference page is the intended second caller).
1267
+ */
1268
+ export function allPaths() {
1269
+ const paths = [];
1270
+ const visit = (nodes, prefix) => {
1271
+ for (const node of nodes) {
1272
+ const path = [...prefix, node.name];
1273
+ paths.push(path);
1274
+ visit(node.subcommands ?? [], path);
1275
+ }
1276
+ };
1277
+ visit(TOP_LEVEL, []);
1278
+ return paths;
1279
+ }