@devdogsuga/devtools 0.1.26 → 0.1.28

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 (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
package/dist/menu.js DELETED
@@ -1,300 +0,0 @@
1
- /**
2
- * The wizard: `pnpm devtools` with no arguments.
3
- *
4
- * ## The one property worth protecting
5
- *
6
- * **It builds an argv and hands it to the CLI's own dispatcher.** It does not
7
- * call command functions directly. That is what makes "the menu covers every
8
- * interactive command" structural instead of aspirational. The menu it replaced held a
9
- * hand-written list of ten entries beside a CLI that had grown to sixteen
10
- * top-level commands and thirty-one subcommands, so `env`, `planner` and
11
- * `docs index` were reachable only by someone who already knew their names.
12
- * A contributor who does not know a command name is the entire audience for
13
- * this file.
14
- *
15
- * Walking `commands.ts` means an interactive command added there is in the
16
- * menu the same day, with its options. Commands marked `cli-only` and deploy
17
- * commands in the separate `devtools-ci` bin do not appear here.
18
- */
19
- import { confirm, note, select, text } from "@clack/prompts";
20
- import { positionals } from "./args.js";
21
- import { findCommand, GROUPS, SCOPES, } from "./commands.js";
22
- import { takeMenuEnvHook } from "./env-entry.js";
23
- import { blockedBecause, describeEnvironment, isOffered, probeEnvironment, } from "./environment.js";
24
- import { beginInvocation, recordEnteredTier } from "./invocation.js";
25
- import { unwrap } from "./ui.js";
26
- /** Chosen when a submenu should return to the screen above it. */
27
- const BACK = Symbol("back");
28
- const BACK_OPTION = { value: BACK, label: "← Back" };
29
- // ── What this machine is offered ─────────────────────────────────────────────
30
- /**
31
- * The commands at one level that are worth showing right now.
32
- *
33
- * `when` is the only thing that removes an entry, and it removes very few;
34
- * see `isOffered`. Everything else stays on screen and explains itself
35
- * through `hintFor` below, because the menu's job is to help someone who does
36
- * not know a command's name, and it cannot do that for a command it declined
37
- * to draw.
38
- */
39
- function offered(nodes, env) {
40
- return nodes.filter((node) => node.surface !== "cli-only" && isOffered(node, env));
41
- }
42
- /**
43
- * The line beside a name, with whatever is standing in this command's way.
44
- *
45
- * The reader learns a command needs something standing in its way *before*
46
- * choosing it, instead of after a spinner and a connection error — `db
47
- * start`'s `when: "instance-stopped"` is the one still in the tree today. The
48
- * command stays selectable when it merely `needs` rather than requires the
49
- * condition to be shown at all: the check is a probe, the probe can be
50
- * wrong, and the command's own failure message is the authority on whether
51
- * it can run.
52
- */
53
- function hintFor(node, env) {
54
- const base = node.hint ?? node.summary;
55
- const blocked = blockedBecause(node, env);
56
- const said = blocked ? `${base} — ${blocked}` : base;
57
- // The scope leads, because in the one group that has scopes it is the thing
58
- // the reader is actually choosing between: `restart` and `reset` sit four
59
- // lines apart and act on different layers.
60
- return node.scope ? `${SCOPES[node.scope].menu} · ${said}` : said;
61
- }
62
- // ── Screens ──────────────────────────────────────────────────────────────────
63
- async function pickGroup(env) {
64
- // A group whose every command is hidden has nothing behind its door, so the
65
- // door is not drawn. No group in the tree can empty out today; this is here
66
- // so that a later `when` cannot leave a dead entry on the first screen.
67
- const groups = GROUPS.filter((group) => offered(group.commands, env).length > 0);
68
- const choice = unwrap(await select({
69
- message: "What would you like to do?",
70
- options: [
71
- ...groups.map((group) => ({
72
- value: group,
73
- label: group.title,
74
- // The group's own commands, so the first screen says what is behind
75
- // each door rather than making the reader open every one to find out.
76
- hint: offered(group.commands, env)
77
- .map((command) => command.name)
78
- .join(", "),
79
- })),
80
- { value: null, label: "Quit" },
81
- ],
82
- }));
83
- return choice;
84
- }
85
- async function pickCommand(group, env) {
86
- const commands = offered(group.commands, env);
87
- // A group with one command has nothing to choose; asking would be a screen
88
- // whose only real option is the one already implied by the group's title.
89
- if (commands.length === 1)
90
- return commands[0];
91
- return unwrap(await select({
92
- message: `${group.title}:`,
93
- options: [
94
- ...commands.map((command) => ({
95
- value: command,
96
- label: command.name,
97
- hint: hintFor(command, env),
98
- })),
99
- BACK_OPTION,
100
- ],
101
- }));
102
- }
103
- async function pickSubcommand(node, env) {
104
- return unwrap(await select({
105
- message: `${node.name}:`,
106
- options: [
107
- ...offered(node.subcommands ?? [], env).map((child) => ({
108
- value: child,
109
- label: child.name,
110
- hint: hintFor(child, env),
111
- })),
112
- BACK_OPTION,
113
- ],
114
- }));
115
- }
116
- // ── Options ──────────────────────────────────────────────────────────────────
117
- /**
118
- * Asks for one option, returning the argv fragment it contributes.
119
- *
120
- * An empty array is a real answer, not a failure: a declined confirm adds no
121
- * flag, and a blank optional text means "let the command decide", which is
122
- * how `--db-url` falls back to `.env.production`.
123
- */
124
- async function askOption(option) {
125
- const prompt = option.prompt;
126
- if (!prompt)
127
- return [];
128
- if (prompt.kind === "confirm") {
129
- // Yes adds the flag, always. `commands.ts` phrases every message so that
130
- // this needs no per-option inversion.
131
- const yes = unwrap(await confirm({ message: prompt.message, initialValue: prompt.initial }));
132
- return yes ? [option.flag] : [];
133
- }
134
- if (prompt.kind === "text") {
135
- const answer = unwrap(await text({
136
- message: prompt.message,
137
- placeholder: prompt.placeholder,
138
- defaultValue: "",
139
- })).trim();
140
- return answer ? [option.flag, answer] : [];
141
- }
142
- const choice = unwrap(await select({
143
- message: prompt.message,
144
- options: prompt.choices.map((c) => ({
145
- value: c.value,
146
- label: c.label ?? c.value,
147
- hint: c.hint,
148
- })),
149
- }));
150
- return [option.flag, choice];
151
- }
152
- async function askOptions(node) {
153
- const argv = [];
154
- for (const option of node.options ?? []) {
155
- argv.push(...(await askOption(option)));
156
- }
157
- return argv;
158
- }
159
- /**
160
- * Descends from `start` through its subcommand screens to a leaf, then asks
161
- * that leaf's options.
162
- *
163
- * Shared by both entries into a walk: the top-of-tree loop below, which calls
164
- * this once a group and its first command are chosen, and a resumed walk
165
- * (`walk`'s `startPath` branch), which calls it directly on a node the caller
166
- * already picked by argv. `while` rather than a single step because the tree
167
- * is two deep today and this does not care. The condition counts the OFFERED
168
- * children rather than all of them, so a node whose every subcommand is
169
- * hidden is treated as the leaf it has become instead of opening a screen
170
- * holding only "Back".
171
- */
172
- async function descendFrom(start, pathNames, env) {
173
- let node = start;
174
- const path = [...pathNames];
175
- while (offered(node.subcommands ?? [], env).length > 0) {
176
- const child = await pickSubcommand(node, env);
177
- if (child === BACK)
178
- return BACK;
179
- node = child;
180
- path.push(node.name);
181
- }
182
- return { node, argv: [...path, ...(await askOptions(node))] };
183
- }
184
- /**
185
- * Walks the tree to a leaf and its options, either from the top or resumed at
186
- * a known group.
187
- *
188
- * `startPath`, when given, is a path to a group node from `bareGroupStartPath`
189
- * — `["db"]` for `devtools db` — and lands here instead of at `pickGroup`.
190
- * There is no group screen above a resumed node (the caller already named it
191
- * by typing `db`), so BACK on its first subcommand screen has nowhere to
192
- * return to but out, unlike BACK from the top of the tree, which returns to
193
- * `pickGroup`.
194
- */
195
- async function walk(env, startPath) {
196
- if (startPath) {
197
- const start = findCommand(startPath);
198
- if (!start)
199
- return null; // defensive: the caller guarantees a group node
200
- const chosen = await descendFrom(start, [...startPath], env);
201
- return chosen === BACK ? null : chosen;
202
- }
203
- for (;;) {
204
- const group = await pickGroup(env);
205
- if (!group)
206
- return null;
207
- const first = await pickCommand(group, env);
208
- if (first === BACK)
209
- continue;
210
- const chosen = await descendFrom(first, [first.name], env);
211
- if (chosen === BACK)
212
- continue;
213
- return chosen;
214
- }
215
- }
216
- // ── Resuming at a node ───────────────────────────────────────────────────────
217
- /**
218
- * The command path when argv names a group with subcommands but no subcommand
219
- * token — what `devtools db` and `devtools db seed` are. Returns null for a
220
- * leaf command, an unknown token, or a bare invocation (which the no-argument
221
- * wizard already covers). The caller resumes the wizard at this node when
222
- * stdin is a TTY; a non-TTY caller keeps the dispatcher's "which of …?" exit.
223
- */
224
- export function bareGroupStartPath(argv) {
225
- const path = positionals(argv);
226
- if (path.length === 0)
227
- return null;
228
- const node = findCommand(path);
229
- if (!node || (node.subcommands ?? []).length === 0)
230
- return null;
231
- return path;
232
- }
233
- // ── Entry ────────────────────────────────────────────────────────────────────
234
- /**
235
- * Runs the wizard, returning the `outro()` line its command earned.
236
- *
237
- * `dispatch` is the CLI's own argv handler, injected rather than imported so
238
- * that `cli.ts` keeps a single definition of what each command does and the
239
- * tests can watch what a walk produces without running it. Its return value
240
- * passes straight through, the closing line or `null` for a failure already
241
- * explained, so a command reached from the menu signs off exactly as it does
242
- * from the command line.
243
- *
244
- * `options.startPath`, from `bareGroupStartPath`, skips straight to that
245
- * node's subcommand screen — see `walk`'s `startPath` branch.
246
- *
247
- * The deploy tier is NOT asked here any more. `src/launch.ts` resolves it
248
- * before `cli.ts` — and therefore this module — is even imported, and
249
- * exports `DEPLOY_ENV`/`DEV_DB` right away, so by the time the wizard opens
250
- * `process.env` already names the session exactly as if `--tier` had been
251
- * typed. Recording it for the "run it directly next time" line only needs to
252
- * read `process.env.DEPLOY_ENV` back.
253
- *
254
- * Entering that tier — loading its `.env.<tier>` file onto `process.env` —
255
- * is what's deferred: `launch.ts` registers a hook (`env-entry.ts`'s
256
- * `setMenuEnvHook`) instead of entering up front, so whatever changed while
257
- * the reader was still walking the menu (the local stack coming up in
258
- * another terminal, `.env.generated` being rewritten) is picked up rather
259
- * than missed. `takeMenuEnvHook()` below runs it right before the chosen
260
- * command dispatches — the ONE place in a menu walk an env value is ever
261
- * actually read. `undefined` (no hook registered — a typed command entered
262
- * already, or a test drives `runMenu` directly) just dispatches straight
263
- * through, unchanged from before this existed.
264
- */
265
- export async function runMenu(dispatch, env = probeEnvironment(), options = {}) {
266
- // Before the first question, not after a failure. Three lines saying what
267
- // this machine currently is explain why the database commands below are
268
- // flagged, and cost one glance on a healthy machine. Injected rather than
269
- // probed inside `walk` so the tests can drive a machine they describe
270
- // instead of the one they happen to run on.
271
- note(describeEnvironment(env), "This machine");
272
- const chosen = await walk(env, options.startPath);
273
- // Quitting is not a failure, but it has nothing to announce either.
274
- if (!chosen)
275
- return null;
276
- // Every step here was a prompt, so the built argv is the reproducible
277
- // command — a runner that prompts further (a bare `workflows run`) appends
278
- // the rest through `recordResolved`, and the entered tier rides along as the
279
- // `--tier` flag `recordEnteredTier` adds.
280
- beginInvocation(chosen.argv, true);
281
- // A development session that answered the "which development database?"
282
- // question records the QUALIFIED selector, so the "run it directly next
283
- // time" line reproduces the whole session — a bare `development` would
284
- // re-ask (or refuse, non-interactively) on a machine whose `.env` names a
285
- // remote DB_URL.
286
- const enteredTier = process.env.DEPLOY_ENV ?? "development";
287
- const devDb = process.env.DEV_DB;
288
- recordEnteredTier(enteredTier === "development" && (devDb === "local" || devDb === "remote")
289
- ? `development:${devDb}`
290
- : enteredTier);
291
- // The deferred entry, if `launch.ts` registered one — see this function's
292
- // header. Read-and-cleared in one call so a nested launcher (this same
293
- // command re-dispatching through its own `launch()`, e.g. `db start`'s
294
- // offline-stack offer) never inherits a hook meant for this walk.
295
- const enterEnvironment = takeMenuEnvHook();
296
- if (enterEnvironment) {
297
- return enterEnvironment(chosen.argv, () => dispatch(chosen.argv));
298
- }
299
- return dispatch(chosen.argv);
300
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * Does my app's moderation integration actually work?
3
- *
4
- * `conformance()` asks the database what it derived from your schema: is the
5
- * table addressable, can an author be attributed, does quarantine have a column
6
- * to write to, can a client still write that column. All of it is answerable
7
- * from the catalog, and `platform.conformance_check()` does the answering.
8
- *
9
- * The end-to-end round trip that used to live here (`quarantineRoundTrip`,
10
- * `moderation roundtrip`) is gone: the app repo's own CI covers that path —
11
- * file, quarantine, and check the freeze — more thoroughly than a devtools
12
- * command driven by hand ever did, and keeping two implementations of the
13
- * same assertion around was a drift risk with no offsetting benefit.
14
- *
15
- * Both `conformance()` and `catalog.ts`'s `readCatalog()` need to run as a
16
- * moderator, subject to the same RLS and `canModerate` checks a real one
17
- * would hit, rather than as `postgres` bypassing them. `withTemporaryModerator`
18
- * below is what supplies that account: a throwaway user, created through the
19
- * admin API and deleted again in a `finally`, rather than the seeded
20
- * `moderator@devdogs.test` persona this package used to sign in as — that
21
- * persona came from `supabase/seed/development/02_moderation.sql`, which only
22
- * ever ran on a local reset and is going away along with the rest of
23
- * `seed/development/` (see `persona.ts`'s header for the replacement).
24
- */
25
- import { type DevtoolsClient, type Instance } from "./instance.js";
26
- import type { CheckResult } from "./ui.js";
27
- interface ConformanceType {
28
- contentType: string;
29
- /** Schema-qualified, e.g. `platform.profile`. */
30
- tableName: string;
31
- checks: CheckResult[];
32
- }
33
- /** Runs `platform.conformance_check()` as the given moderator client. */
34
- export declare function conformance(client: DevtoolsClient, appSlug: string): Promise<ConformanceType[]>;
35
- /**
36
- * The role every devtools-granted moderator holds — `moderation check`'s
37
- * throwaway account and `persona moderator`'s longer-lived one alike, so
38
- * both resolve to the SAME role row rather than each accumulating their own.
39
- *
40
- * Reuses the id `supabase/seed/development/02_moderation.sql` used to seed
41
- * this same role under, on the theory that an existing local database that
42
- * still has that seeded row should be upserted into agreement rather than
43
- * given a second, redundant "Moderator" role.
44
- */
45
- export declare const MODERATOR_ROLE_ID = "00000000-0000-4000-9000-000000000001";
46
- /**
47
- * Creates (or updates) the Moderator role definition, and returns its id.
48
- *
49
- * `canModerate: true` and nothing else — not Root, not any of the other
50
- * `roles` permission columns — mirroring exactly what the seed persona held.
51
- * Idempotent (`upsert`) so repeated calls, from either caller above, never
52
- * race each other into two rows or fail on the second one.
53
- */
54
- export declare function ensureModeratorRole(admin: DevtoolsClient): Promise<string>;
55
- /** Grants the Moderator role to `userId`, creating the role definition first
56
- * if this is the first account to need it. */
57
- export declare function grantModerator(admin: DevtoolsClient, userId: string): Promise<void>;
58
- /**
59
- * Runs `fn` with a client signed in as a throwaway moderator, created for
60
- * this call and deleted again in a `finally` whether or not `fn` throws.
61
- *
62
- * The admin API's `createUser` bypasses the app's `before_user_created`
63
- * uga.edu hook (that hook only runs on the sign-up/OTP paths a real user
64
- * takes, not the service-role admin API), which is why a `@persona.test`
65
- * address works here at all — see `persona.ts`'s header for the same note
66
- * in more detail, since that is the command a contributor is more likely to
67
- * read first.
68
- */
69
- export declare function withTemporaryModerator<T>(instance: Instance, fn: (client: DevtoolsClient) => Promise<T>): Promise<T>;
70
- export {};
@@ -1,128 +0,0 @@
1
- /**
2
- * Does my app's moderation integration actually work?
3
- *
4
- * `conformance()` asks the database what it derived from your schema: is the
5
- * table addressable, can an author be attributed, does quarantine have a column
6
- * to write to, can a client still write that column. All of it is answerable
7
- * from the catalog, and `platform.conformance_check()` does the answering.
8
- *
9
- * The end-to-end round trip that used to live here (`quarantineRoundTrip`,
10
- * `moderation roundtrip`) is gone: the app repo's own CI covers that path —
11
- * file, quarantine, and check the freeze — more thoroughly than a devtools
12
- * command driven by hand ever did, and keeping two implementations of the
13
- * same assertion around was a drift risk with no offsetting benefit.
14
- *
15
- * Both `conformance()` and `catalog.ts`'s `readCatalog()` need to run as a
16
- * moderator, subject to the same RLS and `canModerate` checks a real one
17
- * would hit, rather than as `postgres` bypassing them. `withTemporaryModerator`
18
- * below is what supplies that account: a throwaway user, created through the
19
- * admin API and deleted again in a `finally`, rather than the seeded
20
- * `moderator@devdogs.test` persona this package used to sign in as — that
21
- * persona came from `supabase/seed/development/02_moderation.sql`, which only
22
- * ever ran on a local reset and is going away along with the rest of
23
- * `seed/development/` (see `persona.ts`'s header for the replacement).
24
- */
25
- import { adminClient, signedInClient, } from "./instance.js";
26
- /** Runs `platform.conformance_check()` as the given moderator client. */
27
- export async function conformance(client, appSlug) {
28
- const response = await client.rpc("conformance_check", {
29
- app_slug: appSlug,
30
- });
31
- if (response.error)
32
- throw new Error(response.error.message);
33
- // The generated types do not describe this function's return shape.
34
- const data = response.data;
35
- return (data ?? []);
36
- }
37
- /**
38
- * The role every devtools-granted moderator holds — `moderation check`'s
39
- * throwaway account and `persona moderator`'s longer-lived one alike, so
40
- * both resolve to the SAME role row rather than each accumulating their own.
41
- *
42
- * Reuses the id `supabase/seed/development/02_moderation.sql` used to seed
43
- * this same role under, on the theory that an existing local database that
44
- * still has that seeded row should be upserted into agreement rather than
45
- * given a second, redundant "Moderator" role.
46
- */
47
- export const MODERATOR_ROLE_ID = "00000000-0000-4000-9000-000000000001";
48
- /**
49
- * Creates (or updates) the Moderator role definition, and returns its id.
50
- *
51
- * `canModerate: true` and nothing else — not Root, not any of the other
52
- * `roles` permission columns — mirroring exactly what the seed persona held.
53
- * Idempotent (`upsert`) so repeated calls, from either caller above, never
54
- * race each other into two rows or fail on the second one.
55
- */
56
- export async function ensureModeratorRole(admin) {
57
- const { error } = await admin.from("roles").upsert({
58
- id: MODERATOR_ROLE_ID,
59
- title: "Moderator",
60
- description: "Created by `pnpm devtools`: works the report queue, and nothing else.",
61
- roleType: "custom",
62
- rank: 5000,
63
- canModerate: true,
64
- });
65
- if (error) {
66
- throw new Error(`Could not create the Moderator role: ${error.message}`);
67
- }
68
- return MODERATOR_ROLE_ID;
69
- }
70
- /** Grants the Moderator role to `userId`, creating the role definition first
71
- * if this is the first account to need it. */
72
- export async function grantModerator(admin, userId) {
73
- const roleId = await ensureModeratorRole(admin);
74
- const { error } = await admin.from("userRoles").insert({ userId, roleId });
75
- if (error) {
76
- throw new Error(`Could not grant the Moderator role: ${error.message}`);
77
- }
78
- }
79
- /** An email that unambiguously belongs to devtools, not a real account —
80
- * see `persona.ts`'s header for the `@persona.test` convention this shares. */
81
- function temporaryModeratorEmail() {
82
- return `devtools-moderator-${crypto.randomUUID().slice(0, 8)}@persona.test`;
83
- }
84
- /** A password nobody needs to remember — this account lives for the
85
- * duration of one command. */
86
- function randomPassword() {
87
- return crypto.randomUUID() + crypto.randomUUID();
88
- }
89
- /**
90
- * Runs `fn` with a client signed in as a throwaway moderator, created for
91
- * this call and deleted again in a `finally` whether or not `fn` throws.
92
- *
93
- * The admin API's `createUser` bypasses the app's `before_user_created`
94
- * uga.edu hook (that hook only runs on the sign-up/OTP paths a real user
95
- * takes, not the service-role admin API), which is why a `@persona.test`
96
- * address works here at all — see `persona.ts`'s header for the same note
97
- * in more detail, since that is the command a contributor is more likely to
98
- * read first.
99
- */
100
- export async function withTemporaryModerator(instance, fn) {
101
- const admin = adminClient(instance);
102
- const email = temporaryModeratorEmail();
103
- const password = randomPassword();
104
- let userId = null;
105
- try {
106
- const { data, error } = await admin.auth.admin.createUser({
107
- email,
108
- password,
109
- email_confirm: true,
110
- user_metadata: { createdBy: "devtools moderation check" },
111
- });
112
- if (error || !data.user) {
113
- throw new Error(`Could not create a temporary moderator: ${error?.message}`);
114
- }
115
- userId = data.user.id;
116
- await grantModerator(admin, userId);
117
- const client = await signedInClient(instance, email, password);
118
- return await fn(client);
119
- }
120
- finally {
121
- // `userRoles.userId` cascades on delete (see the migration that defines
122
- // it), so deleting the auth user is the whole teardown — no separate
123
- // `userRoles` row to clean up, and the Moderator role DEFINITION is left
124
- // in place for the next caller, same as `ensureModeratorRole` intends.
125
- if (userId)
126
- await admin.auth.admin.deleteUser(userId);
127
- }
128
- }
@@ -1 +0,0 @@
1
- export declare function openBrowser(url: string): void;
@@ -1,13 +0,0 @@
1
- /** Opens `url` in the user's default browser, cross-platform. */
2
- import { execFile } from "node:child_process";
3
- export function openBrowser(url) {
4
- if (process.platform === "win32") {
5
- execFile("cmd", ["/c", "start", "", url]);
6
- }
7
- else if (process.platform === "darwin") {
8
- execFile("open", [url]);
9
- }
10
- else {
11
- execFile("xdg-open", [url]);
12
- }
13
- }
@@ -1,24 +0,0 @@
1
- /** Default Supabase project that hosts the "Sign in with DevDogs" OAuth server. */
2
- export declare const DEFAULT_API_URL = "https://api.devdogsuga.org";
3
- /**
4
- * DevDogs website — human-facing links, and (since TASK-351) the origin
5
- * that serves the one-click connect flow's `/tools/oauth/connect` and
6
- * `/tools/oauth/connect/exchange` endpoints. Overridable via `--platform-url`
7
- * / `DEVDOGS_PLATFORM_URL` for local testing against a platform dev server —
8
- * see `commands.ts`'s `oauth` node and `wizard.ts`.
9
- */
10
- export declare const WEBSITE_URL = "https://devdogsuga.org";
11
- /**
12
- * Custom OAuth/OIDC provider identifier. Supabase requires custom provider
13
- * identifiers to be prefixed with "custom:".
14
- */
15
- export declare const PROVIDER_IDENTIFIER = "custom:devdogsuga";
16
- export declare const PROVIDER_NAME = "DevDogs";
17
- export declare const PROVIDER_SCOPES: string[];
18
- export declare const ENV_KEYS: {
19
- readonly baseUrl: "OAUTH_BASE_URL";
20
- readonly providerName: "OAUTH_PROVIDER_NAME";
21
- readonly clientId: "OAUTH_CLIENT_ID";
22
- readonly clientSecret: "OAUTH_CLIENT_SECRET";
23
- readonly platformUrl: "DEVDOGS_PLATFORM_URL";
24
- };
@@ -1,24 +0,0 @@
1
- /** Default Supabase project that hosts the "Sign in with DevDogs" OAuth server. */
2
- export const DEFAULT_API_URL = "https://api.devdogsuga.org";
3
- /**
4
- * DevDogs website — human-facing links, and (since TASK-351) the origin
5
- * that serves the one-click connect flow's `/tools/oauth/connect` and
6
- * `/tools/oauth/connect/exchange` endpoints. Overridable via `--platform-url`
7
- * / `DEVDOGS_PLATFORM_URL` for local testing against a platform dev server —
8
- * see `commands.ts`'s `oauth` node and `wizard.ts`.
9
- */
10
- export const WEBSITE_URL = "https://devdogsuga.org";
11
- /**
12
- * Custom OAuth/OIDC provider identifier. Supabase requires custom provider
13
- * identifiers to be prefixed with "custom:".
14
- */
15
- export const PROVIDER_IDENTIFIER = "custom:devdogsuga";
16
- export const PROVIDER_NAME = "DevDogs";
17
- export const PROVIDER_SCOPES = ["openid", "email", "profile"];
18
- export const ENV_KEYS = {
19
- baseUrl: "OAUTH_BASE_URL",
20
- providerName: "OAUTH_PROVIDER_NAME",
21
- clientId: "OAUTH_CLIENT_ID",
22
- clientSecret: "OAUTH_CLIENT_SECRET",
23
- platformUrl: "DEVDOGS_PLATFORM_URL",
24
- };
@@ -1,17 +0,0 @@
1
- /**
2
- * Builds the platform's `/tools/oauth/connect` URL — the page a browser
3
- * opens to approve a `devtools oauth` connection. See this repo's launch
4
- * instructions for the fixed wire contract: `redirect_uri`, `code_challenge`
5
- * (base64url SHA-256 of the PKCE verifier, no padding — `pkce.ts`),
6
- * `code_challenge_method=S256`, `state`, `label` (1–100 characters), and
7
- * `callback_uri` (the TARGET Supabase project's own `/auth/v1/callback` —
8
- * NOT this loopback listener's URL; that is `redirect_uri`).
9
- */
10
- export declare function buildConnectUrl({ platformUrl, redirectUri, codeChallenge, state, label, callbackUri, }: {
11
- platformUrl: string;
12
- redirectUri: string;
13
- codeChallenge: string;
14
- state: string;
15
- label: string;
16
- callbackUri: string;
17
- }): string;
@@ -1,23 +0,0 @@
1
- /**
2
- * Builds the platform's `/tools/oauth/connect` URL — the page a browser
3
- * opens to approve a `devtools oauth` connection. See this repo's launch
4
- * instructions for the fixed wire contract: `redirect_uri`, `code_challenge`
5
- * (base64url SHA-256 of the PKCE verifier, no padding — `pkce.ts`),
6
- * `code_challenge_method=S256`, `state`, `label` (1–100 characters), and
7
- * `callback_uri` (the TARGET Supabase project's own `/auth/v1/callback` —
8
- * NOT this loopback listener's URL; that is `redirect_uri`).
9
- */
10
- export function buildConnectUrl({ platformUrl, redirectUri, codeChallenge, state, label, callbackUri, }) {
11
- const url = new URL(`${platformUrl.replace(/\/+$/, "")}/tools/oauth/connect`);
12
- url.searchParams.set("redirect_uri", redirectUri);
13
- url.searchParams.set("code_challenge", codeChallenge);
14
- url.searchParams.set("code_challenge_method", "S256");
15
- url.searchParams.set("state", state);
16
- // The wire contract caps this at 100 characters; truncated here rather
17
- // than left for the platform to reject, since the label is a cosmetic
18
- // default (project name / directory name — see `label.ts`), not something
19
- // worth failing the whole connect flow over.
20
- url.searchParams.set("label", label.slice(0, 100));
21
- url.searchParams.set("callback_uri", callbackUri);
22
- return url.toString();
23
- }
@@ -1,37 +0,0 @@
1
- export type LocalSupabaseConfig = {
2
- apiUrl: string;
3
- serviceRoleKey: string;
4
- };
5
- /**
6
- * Runs `supabase status -o env` in `cwd` and extracts the local API URL and
7
- * service role key. Throws a descriptive error if Supabase isn't running or
8
- * the CLI isn't installed.
9
- *
10
- * Two invocations are tried, because this wizard runs in two different worlds:
11
- * a sibling project, where the Supabase CLI is normally installed globally, and
12
- * this monorepo, where it is a workspace devDependency and not on PATH at all.
13
- * A global install wins, since `pnpm exec` in a non-pnpm project would fail for
14
- * reasons that have nothing to do with Supabase.
15
- */
16
- export declare function detectLocalSupabase(cwd: string): LocalSupabaseConfig;
17
- /**
18
- * Looks up a custom auth provider on a local Supabase instance through the
19
- * Admin SDK, and reports whether it exists along with its name and issuer.
20
- *
21
- * A 404 means it does not exist yet. Any other error is real and rethrown.
22
- */
23
- export declare function checkProvider({ apiUrl, serviceRoleKey }: LocalSupabaseConfig, identifier: string): Promise<{
24
- exists: boolean;
25
- name?: string;
26
- issuer?: string;
27
- }>;
28
- export declare function upsertDevDogsProvider({ apiUrl, serviceRoleKey }: LocalSupabaseConfig, { identifier, name, clientId, clientSecret, issuer, }: {
29
- identifier: string;
30
- name: string;
31
- clientId: string;
32
- clientSecret: string;
33
- issuer: string;
34
- }): Promise<{
35
- identifier: string;
36
- issuer: string;
37
- }>;