@devdogsuga/devtools 0.1.11 → 0.1.13
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.
- package/bin/devtools-ci.mjs +15 -1
- package/bin/devtools.mjs +15 -1
- package/dist/catalog.d.ts +7 -5
- package/dist/catalog.js +1 -25
- package/dist/cf/build.d.ts +13 -0
- package/dist/cf/build.js +65 -0
- package/dist/cf/commands.js +6 -16
- package/dist/cf/local-env.d.ts +14 -0
- package/dist/cf/local-env.js +29 -0
- package/dist/ci.js +8 -1
- package/dist/cli.js +134 -138
- package/dist/commands.d.ts +10 -4
- package/dist/commands.js +40 -30
- package/dist/db/generated-env.d.ts +69 -0
- package/dist/db/generated-env.js +113 -0
- package/dist/db/seed-production.js +12 -6
- package/dist/docs/index-pages.js +2 -2
- package/dist/emails/commands.js +3 -3
- package/dist/env/discovery.js +19 -6
- package/dist/env-entry.d.ts +49 -0
- package/dist/env-entry.js +204 -0
- package/dist/environment-doctor.d.ts +9 -0
- package/dist/environment-doctor.js +18 -5
- package/dist/environment.d.ts +2 -2
- package/dist/environment.js +5 -28
- package/dist/images/commands.d.ts +3 -6
- package/dist/images/commands.js +26 -30
- package/dist/images/events.d.ts +27 -16
- package/dist/images/events.js +32 -68
- package/dist/images/graphics.js +2 -1
- package/dist/images/prompts.js +2 -2
- package/dist/instance.d.ts +29 -27
- package/dist/instance.js +49 -76
- package/dist/launch-ci.js +5 -5
- package/dist/launch.d.ts +2 -1
- package/dist/launch.js +74 -111
- package/dist/menu.d.ts +17 -5
- package/dist/menu.js +34 -11
- package/dist/moderation.d.ts +48 -40
- package/dist/moderation.js +95 -194
- package/dist/oauth/wizard.d.ts +2 -2
- package/dist/oauth/wizard.js +6 -26
- package/dist/persona.d.ts +18 -0
- package/dist/persona.js +221 -0
- package/dist/repo/peer-redirect-hooks.d.ts +16 -0
- package/dist/repo/peer-redirect-hooks.js +10 -0
- package/dist/repo/peer-redirect.d.ts +4 -0
- package/dist/repo/peer-redirect.js +26 -0
- package/dist/repo/peers.d.ts +15 -0
- package/dist/repo/peers.js +26 -1
- package/dist/repo/supabase-project.d.ts +60 -0
- package/dist/repo/supabase-project.js +124 -0
- package/dist/setup.js +2 -2
- package/dist/stack.js +142 -29
- package/dist/telemetry.d.ts +18 -5
- package/dist/telemetry.js +26 -5
- package/dist/ui.d.ts +15 -0
- package/dist/ui.js +20 -0
- package/dist/workflows/commands.d.ts +10 -0
- package/dist/workflows/commands.js +119 -25
- package/package.json +10 -5
package/dist/launch.js
CHANGED
|
@@ -16,9 +16,27 @@
|
|
|
16
16
|
* This module settles the tier ONCE, before any command's imports — let alone
|
|
17
17
|
* its code — run, using the shared policy in `@devdogsuga/env/session`. By
|
|
18
18
|
* the time `cli.ts` is imported, `process.env.DEPLOY_ENV` already names the
|
|
19
|
-
* tier
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* tier, so `cli.ts`, `menu.ts` and every runner underneath them just read
|
|
20
|
+
* `process.env.DEPLOY_ENV`/`DEV_DB` like any other command would under
|
|
21
|
+
* `with-env` — this module IS the replacement for `with-env`'s job.
|
|
22
|
+
*
|
|
23
|
+
* ## Entering the environment: at launch, or deferred to the menu
|
|
24
|
+
*
|
|
25
|
+
* "Settling the tier" and "entering it" (loading `.env.<tier>` and its
|
|
26
|
+
* `DEV_DB` overlay onto `process.env`, via `@devdogsuga/env/session`'s
|
|
27
|
+
* `enterEnvironment`) are two different steps, and a MENU invocation —
|
|
28
|
+
* a bare `pnpm devtools`, or a bare group like `pnpm devtools db` resumed
|
|
29
|
+
* through `bareGroupStartPath` — only gets the first one here. Entering holds
|
|
30
|
+
* the env files hostage to whatever was true at the moment this process
|
|
31
|
+
* started; a contributor who starts the local stack in another terminal
|
|
32
|
+
* while still choosing from the wizard would otherwise have that missed
|
|
33
|
+
* entirely. So for a menu invocation this module resolves the tier, exports
|
|
34
|
+
* `DEPLOY_ENV`/`DEV_DB` directly (skipping `enterEnvironment` itself), and
|
|
35
|
+
* registers `env-entry.ts`'s menu hook instead of entering — `menu.ts`'s
|
|
36
|
+
* `runMenu` calls it right before dispatching the ONE command the reader
|
|
37
|
+
* chose, so the env files loaded are current as of that moment, not this
|
|
38
|
+
* one. A typed command line still enters right here, unchanged: there is no
|
|
39
|
+
* wizard delay to protect it from.
|
|
22
40
|
*
|
|
23
41
|
* ## `--tier`, a global flag
|
|
24
42
|
*
|
|
@@ -30,10 +48,13 @@
|
|
|
30
48
|
* already been made, and `resolveTier` (see `tier.ts`) falls back to reading
|
|
31
49
|
* it off `process.env.DEPLOY_ENV` rather than asking again.
|
|
32
50
|
*/
|
|
33
|
-
import {
|
|
51
|
+
import { select } from "@clack/prompts";
|
|
34
52
|
import { findCommand } from "./commands.js";
|
|
53
|
+
import { enterSessionEnvironment, realEnvEntryDeps, setMenuEnvHook, } from "./env-entry.js";
|
|
54
|
+
import { bareGroupStartPath } from "./menu.js";
|
|
35
55
|
import { discoverRepoRoot, findRepoRoot, RepoNotFoundError } from "./repo/root.js";
|
|
36
56
|
import { loadEnvLoad, loadEnvSession } from "./repo/peers.js";
|
|
57
|
+
import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
|
|
37
58
|
import { errorMessage, unwrap } from "./ui.js";
|
|
38
59
|
/**
|
|
39
60
|
* Pulls a global `--tier <t>` out of `argv`, wherever it sits, leaving every
|
|
@@ -104,11 +125,14 @@ async function dispatch(argv) {
|
|
|
104
125
|
}
|
|
105
126
|
catch (err) {
|
|
106
127
|
process.stderr.write(`devtools: ${errorMessage(err)}\n`);
|
|
128
|
+
// Report BEFORE exiting — see `captureDevtoolsError`'s header.
|
|
129
|
+
await captureDevtoolsError(err);
|
|
107
130
|
process.exit(1);
|
|
108
131
|
}
|
|
109
132
|
}
|
|
110
133
|
/**
|
|
111
|
-
* Resolves the session's deploy tier, enters it
|
|
134
|
+
* Resolves the session's deploy tier, enters it (or defers entry to the
|
|
135
|
+
* menu — see this file's header), and hands off to `cli.ts`.
|
|
112
136
|
*
|
|
113
137
|
* Exits the process directly on a refusal from `resolveSessionTier` — there
|
|
114
138
|
* is no command dispatched yet for a caller to fall back to, so there is
|
|
@@ -116,6 +140,20 @@ async function dispatch(argv) {
|
|
|
116
140
|
*/
|
|
117
141
|
export async function launch(argv) {
|
|
118
142
|
const { explicit, rest } = stripTierFlag(argv);
|
|
143
|
+
// Here rather than only in `cli.ts`'s `main()`, so a failure while
|
|
144
|
+
// resolving or entering the tier below is reported too. `main()`'s own
|
|
145
|
+
// call is then a no-op (`initDevtoolsTelemetry` is idempotent) and this
|
|
146
|
+
// tag, computed from the same argv `main()` receives, stands.
|
|
147
|
+
initDevtoolsTelemetry(rest[0] ?? "menu");
|
|
148
|
+
// Same test `cli.ts`'s own `main()` uses to decide whether a bare command
|
|
149
|
+
// group resumes the wizard instead of dispatching — computed here too so
|
|
150
|
+
// this function can decide, before resolving anything, whether entry gets
|
|
151
|
+
// deferred to `menu.ts`'s hook. A non-TTY caller never resumes the wizard
|
|
152
|
+
// (see `bareGroupStartPath`'s own doc), so `rest.length === 0` is the only
|
|
153
|
+
// menu shape it can hit — mirroring `main()`'s own `process.stdin.isTTY ?
|
|
154
|
+
// bareGroupStartPath(argv) : null`.
|
|
155
|
+
const isMenuInvocation = rest.length === 0 ||
|
|
156
|
+
(process.stdin.isTTY === true && bareGroupStartPath(rest) !== null);
|
|
119
157
|
// `--help`/`-h` bypasses tier resolution entirely, BEFORE it can refuse.
|
|
120
158
|
// `cli.ts`'s own `main()` already answers these with no env in play (see
|
|
121
159
|
// its header comment); resolving a tier first anyway meant `pnpm devtools
|
|
@@ -133,6 +171,24 @@ export async function launch(argv) {
|
|
|
133
171
|
}
|
|
134
172
|
const envLoad = await loadEnvLoad();
|
|
135
173
|
const envSession = await loadEnvSession();
|
|
174
|
+
// Records, once, which keys THIS shell actually exported before devtools
|
|
175
|
+
// touched anything — the fix for the staleness bug `enterEnvironment`'s own
|
|
176
|
+
// `override: false` comment below describes. `Object.keys(process.env)` at
|
|
177
|
+
// this exact point is still a faithful snapshot of the calling shell: the
|
|
178
|
+
// imports above load code, not env; nothing before this line has mutated
|
|
179
|
+
// `process.env`. See `SHELL_KEYS_ENV` (`@devdogsuga/env/load`) for how
|
|
180
|
+
// `loadEnvironment` narrows "a shell var beats the file" to just this list.
|
|
181
|
+
//
|
|
182
|
+
// Guarded on absence so a NESTED devtools launcher (one `pnpm devtools`
|
|
183
|
+
// command spawning another, e.g. the `db start` offer below re-dispatching
|
|
184
|
+
// through `dispatch()`) keeps the ORIGINAL list instead of recomputing one:
|
|
185
|
+
// by the time a nested launcher's `launch()` runs, `process.env` already
|
|
186
|
+
// holds this session's entered values, not the outer shell's, and
|
|
187
|
+
// recomputing from it would misclassify every one of them as a genuine
|
|
188
|
+
// shell export — the exact bug this marker exists to prevent, one layer in.
|
|
189
|
+
if (process.env[envLoad.SHELL_KEYS_ENV] === undefined) {
|
|
190
|
+
process.env[envLoad.SHELL_KEYS_ENV] = Object.keys(process.env).join(",");
|
|
191
|
+
}
|
|
136
192
|
let tier;
|
|
137
193
|
let devDatabase;
|
|
138
194
|
if (rest[0] === "setup" || rest[0] === "completions" || isEnvFreeCommand(rest)) {
|
|
@@ -207,116 +263,23 @@ export async function launch(argv) {
|
|
|
207
263
|
tier = resolution.tier;
|
|
208
264
|
devDatabase = resolution.devDatabase;
|
|
209
265
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const entered = await envSession.enterEnvironment(tier, {
|
|
223
|
-
override: false,
|
|
224
|
-
devDatabase,
|
|
225
|
-
});
|
|
226
|
-
for (const warning of entered.warnings) {
|
|
227
|
-
process.stderr.write(`devtools: ${warning}\n`);
|
|
228
|
-
}
|
|
229
|
-
reportEntered(entered.files, sessionLabel);
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
if (err instanceof envLoad.LocalStackOfflineError) {
|
|
233
|
-
// The session explicitly means the local database and the stack is not
|
|
234
|
-
// reachable.
|
|
235
|
-
process.stderr.write(`devtools: ${err.message}\n`);
|
|
236
|
-
// Rather than making the contributor stop, run `db start`, and re-run
|
|
237
|
-
// whatever they meant, offer to bring the stack up right here and then
|
|
238
|
-
// carry on with their original command. Only on a TTY (there is no one
|
|
239
|
-
// to answer otherwise), and not when the command is ITSELF a stack
|
|
240
|
-
// lifecycle command — `db start`/`stop`/`restart` do this on their own,
|
|
241
|
-
// and `db stop` against an already-down stack must not be interrupted by
|
|
242
|
-
// an offer to start it. The bare top menu is likewise left to its own
|
|
243
|
-
// Database → start entry.
|
|
244
|
-
const lifecycle = rest[0] === "db" &&
|
|
245
|
-
(rest[1] === "start" || rest[1] === "stop" || rest[1] === "restart");
|
|
246
|
-
if (process.stdin.isTTY === true && rest.length !== 0 && !lifecycle) {
|
|
247
|
-
const start = unwrap(await confirm({
|
|
248
|
-
message: "Start the local Supabase stack now?",
|
|
249
|
-
}));
|
|
250
|
-
if (start) {
|
|
251
|
-
// Keep the session's local qualifier set so the env refresh that
|
|
252
|
-
// `runStackCommand("start")` performs on success re-applies the
|
|
253
|
-
// overlay under it, and the command we dispatch next resolves the
|
|
254
|
-
// freshly-started local database.
|
|
255
|
-
if (devDatabase !== undefined)
|
|
256
|
-
process.env.DEV_DB = devDatabase;
|
|
257
|
-
const { runStackCommand } = await import("./stack.js");
|
|
258
|
-
const { code, lines } = await runStackCommand("start", null);
|
|
259
|
-
for (const line of lines) {
|
|
260
|
-
process.stderr.write(`devtools: ${line}\n`);
|
|
261
|
-
}
|
|
262
|
-
if (code === 0) {
|
|
263
|
-
await dispatch(rest);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
process.stderr.write("devtools: the stack did not start — see the Supabase CLI output " +
|
|
267
|
-
"above.\n");
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// Declined, or nobody to ask. `db` (whose `start` is the fix, and whose
|
|
272
|
-
// data commands re-check the connection themselves) and the bare menu
|
|
273
|
-
// (the road to `db start`) may continue in a degraded, unqualified
|
|
274
|
-
// entry; anything else stops here, with the error's own troubleshooting,
|
|
275
|
-
// instead of failing later against whatever `.env` happens to name.
|
|
276
|
-
if (rest.length !== 0 && rest[0] !== "db")
|
|
277
|
-
process.exit(1);
|
|
278
|
-
process.stderr.write("devtools: continuing without the overlay so `db start` can fix this.\n");
|
|
279
|
-
const inherited = process.env.DEV_DB;
|
|
280
|
-
delete process.env.DEV_DB;
|
|
281
|
-
const entered = await envSession.enterEnvironment(tier, { override: false });
|
|
282
|
-
if (devDatabase !== undefined) {
|
|
283
|
-
// Children spawned AFTER `db start` succeeds should still inherit
|
|
284
|
-
// the session's answer; the refresh that follows a successful start
|
|
285
|
-
// re-applies the overlay under this same variable.
|
|
286
|
-
process.env.DEV_DB = devDatabase;
|
|
287
|
-
}
|
|
288
|
-
else if (inherited !== undefined) {
|
|
289
|
-
process.env.DEV_DB = inherited;
|
|
290
|
-
}
|
|
291
|
-
for (const warning of entered.warnings) {
|
|
292
|
-
process.stderr.write(`devtools: ${warning}\n`);
|
|
293
|
-
}
|
|
294
|
-
reportEntered(entered.files, tier);
|
|
295
|
-
await dispatch(rest);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (!(err instanceof envLoad.MissingEnvFileError))
|
|
299
|
-
throw err;
|
|
300
|
-
if (tier === "development") {
|
|
301
|
-
// The clean-clone path: a fresh checkout has no `.env` at all, and
|
|
302
|
-
// `pnpm devtools setup` is how one gets created — `MissingEnvFileError`
|
|
303
|
-
// names it. Reporting and continuing here, rather than refusing, is
|
|
304
|
-
// what lets `setup` itself run through this same entry point. See
|
|
305
|
-
// `load.ts`'s `MissingEnvFileError` for the message this prints.
|
|
306
|
-
process.stderr.write(`devtools: ${err.message}\n`);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
// An EXPLICITLY selected staging/production tier with no env file is
|
|
310
|
-
// not a first run — it is a real problem, and `env pull` is the fix
|
|
311
|
-
// `MissingEnvFileError`'s own message already names. Fatal.
|
|
312
|
-
process.stderr.write(`devtools: ${err.message}\n`);
|
|
313
|
-
process.exit(1);
|
|
314
|
-
}
|
|
266
|
+
if (isMenuInvocation) {
|
|
267
|
+
// Export the decision, not the files: `DEPLOY_ENV`/`DEV_DB` name the
|
|
268
|
+
// session the same way `enterEnvironment` would set them, but loading
|
|
269
|
+
// `.env.<tier>` itself — and everything `enterSessionEnvironment` does
|
|
270
|
+
// around that, including the offline-stack offer — waits for a command to
|
|
271
|
+
// actually be chosen. See this file's header.
|
|
272
|
+
process.env.DEPLOY_ENV = tier;
|
|
273
|
+
if (devDatabase !== undefined)
|
|
274
|
+
process.env.DEV_DB = devDatabase;
|
|
275
|
+
setMenuEnvHook((commandArgv, dispatchCommand) => enterSessionEnvironment(tier, devDatabase, commandArgv, realEnvEntryDeps(envLoad, envSession), dispatchCommand));
|
|
276
|
+
await dispatch(rest);
|
|
277
|
+
return;
|
|
315
278
|
}
|
|
316
279
|
// Caught here rather than left to `bin/devtools.mjs`'s `child.on("error",
|
|
317
280
|
// …)`, which only ever sees a spawn failure, never a rejection thrown
|
|
318
281
|
// inside this process.
|
|
319
|
-
await dispatch(rest);
|
|
282
|
+
await enterSessionEnvironment(tier, devDatabase, rest, realEnvEntryDeps(envLoad, envSession), () => dispatch(rest));
|
|
320
283
|
}
|
|
321
284
|
// `bin/devtools.mjs` runs this file directly through tsx — `tsx
|
|
322
285
|
// --conditions=devdogs-source src/launch.ts <argv…>` — so `process.argv`
|
package/dist/menu.d.ts
CHANGED
|
@@ -20,11 +20,23 @@ export declare function bareGroupStartPath(argv: readonly string[]): string[] |
|
|
|
20
20
|
* `options.startPath`, from `bareGroupStartPath`, skips straight to that
|
|
21
21
|
* node's subcommand screen — see `walk`'s `startPath` branch.
|
|
22
22
|
*
|
|
23
|
-
* The deploy tier is NOT asked here any more. `src/launch.ts` resolves
|
|
24
|
-
*
|
|
25
|
-
* so by the time the wizard opens
|
|
26
|
-
*
|
|
27
|
-
* directly next time" line only needs to
|
|
23
|
+
* The deploy tier is NOT asked here any more. `src/launch.ts` resolves it
|
|
24
|
+
* before `cli.ts` — and therefore this module — is even imported, and
|
|
25
|
+
* exports `DEPLOY_ENV`/`DEV_DB` right away, so by the time the wizard opens
|
|
26
|
+
* `process.env` already names the session exactly as if `--tier` had been
|
|
27
|
+
* typed. Recording it for the "run it directly next time" line only needs to
|
|
28
|
+
* read `process.env.DEPLOY_ENV` back.
|
|
29
|
+
*
|
|
30
|
+
* Entering that tier — loading its `.env.<tier>` file onto `process.env` —
|
|
31
|
+
* is what's deferred: `launch.ts` registers a hook (`env-entry.ts`'s
|
|
32
|
+
* `setMenuEnvHook`) instead of entering up front, so whatever changed while
|
|
33
|
+
* the reader was still walking the menu (the local stack coming up in
|
|
34
|
+
* another terminal, `.env.generated` being rewritten) is picked up rather
|
|
35
|
+
* than missed. `takeMenuEnvHook()` below runs it right before the chosen
|
|
36
|
+
* command dispatches — the ONE place in a menu walk an env value is ever
|
|
37
|
+
* actually read. `undefined` (no hook registered — a typed command entered
|
|
38
|
+
* already, or a test drives `runMenu` directly) just dispatches straight
|
|
39
|
+
* through, unchanged from before this existed.
|
|
28
40
|
*/
|
|
29
41
|
export declare function runMenu(dispatch: (argv: string[]) => Promise<string | null>, env?: Environment, options?: {
|
|
30
42
|
startPath?: string[];
|
package/dist/menu.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { confirm, note, select, text } from "@clack/prompts";
|
|
20
20
|
import { positionals } from "./args.js";
|
|
21
21
|
import { findCommand, GROUPS, SCOPES, } from "./commands.js";
|
|
22
|
+
import { takeMenuEnvHook } from "./env-entry.js";
|
|
22
23
|
import { blockedBecause, describeEnvironment, isOffered, probeEnvironment, } from "./environment.js";
|
|
23
24
|
import { beginInvocation, recordEnteredTier } from "./invocation.js";
|
|
24
25
|
import { unwrap } from "./ui.js";
|
|
@@ -41,10 +42,13 @@ function offered(nodes, env) {
|
|
|
41
42
|
/**
|
|
42
43
|
* The line beside a name, with whatever is standing in this command's way.
|
|
43
44
|
*
|
|
44
|
-
* The reader learns
|
|
45
|
-
* choosing it, instead of after a spinner and a connection error
|
|
46
|
-
*
|
|
47
|
-
* command
|
|
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.
|
|
48
52
|
*/
|
|
49
53
|
function hintFor(node, env) {
|
|
50
54
|
const base = node.hint ?? node.summary;
|
|
@@ -115,8 +119,7 @@ async function pickSubcommand(node, env) {
|
|
|
115
119
|
*
|
|
116
120
|
* An empty array is a real answer, not a failure: a declined confirm adds no
|
|
117
121
|
* flag, and a blank optional text means "let the command decide", which is
|
|
118
|
-
* how `--db-url` falls back to `.env.production
|
|
119
|
-
* through to the OAuth wizard's own prompt.
|
|
122
|
+
* how `--db-url` falls back to `.env.production`.
|
|
120
123
|
*/
|
|
121
124
|
async function askOption(option) {
|
|
122
125
|
const prompt = option.prompt;
|
|
@@ -241,11 +244,23 @@ export function bareGroupStartPath(argv) {
|
|
|
241
244
|
* `options.startPath`, from `bareGroupStartPath`, skips straight to that
|
|
242
245
|
* node's subcommand screen — see `walk`'s `startPath` branch.
|
|
243
246
|
*
|
|
244
|
-
* The deploy tier is NOT asked here any more. `src/launch.ts` resolves
|
|
245
|
-
*
|
|
246
|
-
* so by the time the wizard opens
|
|
247
|
-
*
|
|
248
|
-
* directly next time" line only needs to
|
|
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.
|
|
249
264
|
*/
|
|
250
265
|
export async function runMenu(dispatch, env = probeEnvironment(), options = {}) {
|
|
251
266
|
// Before the first question, not after a failure. Three lines saying what
|
|
@@ -273,5 +288,13 @@ export async function runMenu(dispatch, env = probeEnvironment(), options = {})
|
|
|
273
288
|
recordEnteredTier(enteredTier === "development" && (devDb === "local" || devDb === "remote")
|
|
274
289
|
? `development:${devDb}`
|
|
275
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
|
+
}
|
|
276
299
|
return dispatch(chosen.argv);
|
|
277
300
|
}
|
package/dist/moderation.d.ts
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Does my app's moderation integration actually work?
|
|
3
3
|
*
|
|
4
|
-
* Two checks, and the second is the one that matters.
|
|
5
|
-
*
|
|
6
4
|
* `conformance()` asks the database what it derived from your schema: is the
|
|
7
5
|
* table addressable, can an author be attributed, does quarantine have a column
|
|
8
6
|
* to write to, can a client still write that column. All of it is answerable
|
|
9
7
|
* from the catalog, and `platform.conformance_check()` does the answering.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* nobody reads it. The only proof is to file, resolve, and then look.
|
|
17
|
-
*
|
|
18
|
-
* It runs against `platform.profile`, where quarantine means FREEZE rather than
|
|
19
|
-
* hide: the display name is reset to the member's name of record and they lose
|
|
20
|
-
* the ability to change it back. A profile cannot be hidden: rosters, teams
|
|
21
|
-
* and standings all render the name, and the row is own-row-only over PostgREST
|
|
22
|
-
* anyway, so there is nobody to hide it from. See
|
|
23
|
-
* 20260808000000_platform_profile_moderation.sql.
|
|
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.
|
|
24
14
|
*
|
|
25
|
-
* Both
|
|
26
|
-
*
|
|
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).
|
|
27
24
|
*/
|
|
28
|
-
import { type Instance } from "./instance.js";
|
|
25
|
+
import { type DevtoolsClient, type Instance } from "./instance.js";
|
|
29
26
|
import type { CheckResult } from "./ui.js";
|
|
30
27
|
interface ConformanceType {
|
|
31
28
|
contentType: string;
|
|
@@ -33,30 +30,41 @@ interface ConformanceType {
|
|
|
33
30
|
tableName: string;
|
|
34
31
|
checks: CheckResult[];
|
|
35
32
|
}
|
|
36
|
-
/** Runs `platform.conformance_check()` as the
|
|
37
|
-
export declare function conformance(
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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";
|
|
45
46
|
/**
|
|
46
|
-
*
|
|
47
|
-
* that the freeze and the name reset both landed.
|
|
47
|
+
* Creates (or updates) the Moderator role definition, and returns its id.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* `
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
56
61
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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.
|
|
60
68
|
*/
|
|
61
|
-
export declare function
|
|
69
|
+
export declare function withTemporaryModerator<T>(instance: Instance, fn: (client: DevtoolsClient) => Promise<T>): Promise<T>;
|
|
62
70
|
export {};
|