@devdogsuga/devtools 0.1.26 → 0.1.27
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/README.md +72 -11
- package/bin/devtools.mjs +4 -4
- package/dist/cli-CZKkNHK5.js +6619 -0
- package/dist/failure-log-BBPGejQv.js +256 -0
- package/dist/launch.js +267 -281
- package/dist/local-env-D8HC2JK_.js +351 -0
- package/dist/local-env-DrBCXqD8.js +2 -0
- package/dist/menu-CQBsQRgH.js +1684 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/peers-RBplwovA.js +229 -0
- package/dist/root-C2OmZDwD.js +94 -0
- package/dist/stack-Bx-55oi_.js +2 -0
- package/dist/stack-BynCgRVS.js +181 -0
- package/dist/supabase-project-0knM0mqd.js +426 -0
- package/dist/telemetry-C4foD8MD.js +249 -0
- package/dist/telemetry.js +2 -148
- package/package.json +10 -29
- package/bin/devtools-ci-bare.mjs +0 -48
- package/bin/devtools-ci.mjs +0 -31
- package/dist/args.d.ts +0 -26
- package/dist/args.js +0 -78
- package/dist/bws/bw.d.ts +0 -18
- package/dist/bws/bw.js +0 -70
- package/dist/bws/client.d.ts +0 -43
- package/dist/bws/client.js +0 -363
- package/dist/bws/environments.d.ts +0 -96
- package/dist/bws/environments.js +0 -111
- package/dist/bws/pace.d.ts +0 -25
- package/dist/bws/pace.js +0 -43
- package/dist/bws/retry.d.ts +0 -12
- package/dist/bws/retry.js +0 -39
- package/dist/bws/store.d.ts +0 -1
- package/dist/bws/store.js +0 -32
- package/dist/bws/token.d.ts +0 -31
- package/dist/bws/token.js +0 -88
- package/dist/bws/vault.d.ts +0 -29
- package/dist/bws/vault.js +0 -200
- package/dist/catalog.d.ts +0 -57
- package/dist/catalog.js +0 -57
- package/dist/cf/build.d.ts +0 -13
- package/dist/cf/build.js +0 -65
- package/dist/cf/commands.d.ts +0 -1
- package/dist/cf/commands.js +0 -172
- package/dist/cf/local-env.d.ts +0 -58
- package/dist/cf/local-env.js +0 -177
- package/dist/ci.d.ts +0 -1
- package/dist/ci.js +0 -328
- package/dist/cli.d.ts +0 -11
- package/dist/cli.js +0 -1094
- package/dist/commands.d.ts +0 -261
- package/dist/commands.js +0 -1318
- package/dist/completions.d.ts +0 -4
- package/dist/completions.js +0 -110
- package/dist/cron/commands.d.ts +0 -47
- package/dist/cron/commands.js +0 -372
- package/dist/cron/discovery.d.ts +0 -55
- package/dist/cron/discovery.js +0 -85
- package/dist/cron/schema.d.ts +0 -45
- package/dist/cron/schema.js +0 -35
- package/dist/db/config-push.d.ts +0 -8
- package/dist/db/config-push.js +0 -11
- package/dist/db/connection.d.ts +0 -79
- package/dist/db/connection.js +0 -114
- package/dist/db/exec.d.ts +0 -1
- package/dist/db/exec.js +0 -11
- package/dist/db/generate-types.d.ts +0 -1
- package/dist/db/generate-types.js +0 -4
- package/dist/db/generated-env.d.ts +0 -69
- package/dist/db/generated-env.js +0 -113
- package/dist/db/introspect.d.ts +0 -1
- package/dist/db/introspect.js +0 -122
- package/dist/db/local-env.d.ts +0 -65
- package/dist/db/local-env.js +0 -23
- package/dist/db/new-migration.d.ts +0 -8
- package/dist/db/new-migration.js +0 -69
- package/dist/db/pooler.d.ts +0 -23
- package/dist/db/pooler.js +0 -55
- package/dist/db/run.d.ts +0 -68
- package/dist/db/run.js +0 -156
- package/dist/db/seed-buckets.d.ts +0 -2
- package/dist/db/seed-buckets.js +0 -4
- package/dist/db/seed-production.d.ts +0 -8
- package/dist/db/seed-production.js +0 -79
- package/dist/db/seed-roles.d.ts +0 -7
- package/dist/db/seed-roles.js +0 -33
- package/dist/db/session-refresh.d.ts +0 -43
- package/dist/db/session-refresh.js +0 -40
- package/dist/deploy/migrations.d.ts +0 -20
- package/dist/deploy/migrations.js +0 -68
- package/dist/deploy/orphans.d.ts +0 -20
- package/dist/deploy/orphans.js +0 -177
- package/dist/deploy/preflight.d.ts +0 -53
- package/dist/deploy/preflight.js +0 -189
- package/dist/deploy/report.d.ts +0 -26
- package/dist/deploy/report.js +0 -70
- package/dist/deploy/require-planner.d.ts +0 -2
- package/dist/deploy/require-planner.js +0 -60
- package/dist/deploy/require-token.d.ts +0 -15
- package/dist/deploy/require-token.js +0 -78
- package/dist/deploy/secrets-file.d.ts +0 -17
- package/dist/deploy/secrets-file.js +0 -166
- package/dist/deploy/write-env.d.ts +0 -40
- package/dist/deploy/write-env.js +0 -395
- package/dist/docs/index-pages.d.ts +0 -38
- package/dist/docs/index-pages.js +0 -186
- package/dist/emails/commands.d.ts +0 -15
- package/dist/emails/commands.js +0 -130
- package/dist/emails/email-types.d.ts +0 -13
- package/dist/emails/email-types.js +0 -1
- package/dist/emails/fixtures.d.ts +0 -20
- package/dist/emails/fixtures.js +0 -20
- package/dist/env/audit.d.ts +0 -206
- package/dist/env/audit.js +0 -436
- package/dist/env/cloudflare.d.ts +0 -21
- package/dist/env/cloudflare.js +0 -61
- package/dist/env/commands.d.ts +0 -99
- package/dist/env/commands.js +0 -613
- package/dist/env/discovery.d.ts +0 -15
- package/dist/env/discovery.js +0 -239
- package/dist/env/document.d.ts +0 -119
- package/dist/env/document.js +0 -323
- package/dist/env/example.d.ts +0 -68
- package/dist/env/example.js +0 -605
- package/dist/env/selection.d.ts +0 -105
- package/dist/env/selection.js +0 -228
- package/dist/env-entry.d.ts +0 -49
- package/dist/env-entry.js +0 -204
- package/dist/environment-doctor.d.ts +0 -45
- package/dist/environment-doctor.js +0 -429
- package/dist/environment.d.ts +0 -92
- package/dist/environment.js +0 -185
- package/dist/fingerprint.d.ts +0 -10
- package/dist/fingerprint.js +0 -18
- package/dist/gen/campus-map.d.ts +0 -1
- package/dist/gen/campus-map.js +0 -473
- package/dist/gen/commands.d.ts +0 -1
- package/dist/gen/commands.js +0 -25
- package/dist/gen/hypno.d.ts +0 -1
- package/dist/gen/hypno.js +0 -134
- package/dist/gh/client.d.ts +0 -80
- package/dist/gh/client.js +0 -259
- package/dist/gh/environments.d.ts +0 -83
- package/dist/gh/environments.js +0 -178
- package/dist/gh/rulesets/actors.d.ts +0 -14
- package/dist/gh/rulesets/actors.js +0 -69
- package/dist/gh/rulesets/api.d.ts +0 -15
- package/dist/gh/rulesets/api.js +0 -104
- package/dist/gh/rulesets/commands.d.ts +0 -3
- package/dist/gh/rulesets/commands.js +0 -203
- package/dist/gh/rulesets/desired.d.ts +0 -112
- package/dist/gh/rulesets/desired.js +0 -271
- package/dist/gh/rulesets/diff.d.ts +0 -65
- package/dist/gh/rulesets/diff.js +0 -170
- package/dist/gh/rulesets/types.d.ts +0 -76
- package/dist/gh/rulesets/types.js +0 -13
- package/dist/gh/settings/api.d.ts +0 -40
- package/dist/gh/settings/api.js +0 -180
- package/dist/gh/settings/commands.d.ts +0 -1
- package/dist/gh/settings/commands.js +0 -254
- package/dist/gh/settings/desired.d.ts +0 -53
- package/dist/gh/settings/desired.js +0 -43
- package/dist/gh/settings/diff.d.ts +0 -57
- package/dist/gh/settings/diff.js +0 -161
- package/dist/gh/settings/types.d.ts +0 -92
- package/dist/gh/settings/types.js +0 -11
- package/dist/gh/settings/workflows.d.ts +0 -41
- package/dist/gh/settings/workflows.js +0 -128
- package/dist/grantRoot.d.ts +0 -42
- package/dist/grantRoot.js +0 -89
- package/dist/help.d.ts +0 -18
- package/dist/help.js +0 -169
- package/dist/images/commands.d.ts +0 -32
- package/dist/images/commands.js +0 -231
- package/dist/images/events.d.ts +0 -47
- package/dist/images/events.js +0 -116
- package/dist/images/graphics.d.ts +0 -70
- package/dist/images/graphics.js +0 -234
- package/dist/images/open-graph-types.d.ts +0 -75
- package/dist/images/open-graph-types.js +0 -1
- package/dist/images/prompts.d.ts +0 -34
- package/dist/images/prompts.js +0 -100
- package/dist/images/render.d.ts +0 -21
- package/dist/images/render.js +0 -61
- package/dist/images/select.d.ts +0 -64
- package/dist/images/select.js +0 -96
- package/dist/instance.d.ts +0 -69
- package/dist/instance.js +0 -102
- package/dist/invocation.d.ts +0 -49
- package/dist/invocation.js +0 -78
- package/dist/launch-ci.d.ts +0 -1
- package/dist/launch-ci.js +0 -90
- package/dist/launch.d.ts +0 -18
- package/dist/menu.d.ts +0 -43
- package/dist/menu.js +0 -300
- package/dist/moderation.d.ts +0 -70
- package/dist/moderation.js +0 -128
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -13
- package/dist/oauth/config.d.ts +0 -24
- package/dist/oauth/config.js +0 -24
- package/dist/oauth/connect-url.d.ts +0 -17
- package/dist/oauth/connect-url.js +0 -23
- package/dist/oauth/db.d.ts +0 -37
- package/dist/oauth/db.js +0 -108
- package/dist/oauth/device.d.ts +0 -93
- package/dist/oauth/device.js +0 -198
- package/dist/oauth/discovery.d.ts +0 -28
- package/dist/oauth/discovery.js +0 -54
- package/dist/oauth/env-file.d.ts +0 -6
- package/dist/oauth/env-file.js +0 -35
- package/dist/oauth/exchange.d.ts +0 -43
- package/dist/oauth/exchange.js +0 -93
- package/dist/oauth/label.d.ts +0 -1
- package/dist/oauth/label.js +0 -25
- package/dist/oauth/loopback.d.ts +0 -52
- package/dist/oauth/loopback.js +0 -168
- package/dist/oauth/pkce.d.ts +0 -6
- package/dist/oauth/pkce.js +0 -34
- package/dist/oauth/target.d.ts +0 -54
- package/dist/oauth/target.js +0 -102
- package/dist/oauth/transport.d.ts +0 -41
- package/dist/oauth/transport.js +0 -32
- package/dist/oauth/wizard.d.ts +0 -40
- package/dist/oauth/wizard.js +0 -578
- package/dist/persona.d.ts +0 -18
- package/dist/persona.js +0 -221
- package/dist/pick.d.ts +0 -6
- package/dist/pick.js +0 -64
- package/dist/pipes.d.ts +0 -1
- package/dist/pipes.js +0 -25
- package/dist/planner/checks.d.ts +0 -26
- package/dist/planner/checks.js +0 -104
- package/dist/planner/commands.d.ts +0 -10
- package/dist/planner/commands.js +0 -367
- package/dist/planner/db.d.ts +0 -15
- package/dist/planner/db.js +0 -33
- package/dist/planner/role.d.ts +0 -33
- package/dist/planner/role.js +0 -68
- package/dist/repo/peer-redirect-hooks.d.ts +0 -16
- package/dist/repo/peer-redirect-hooks.js +0 -10
- package/dist/repo/peer-redirect.d.ts +0 -4
- package/dist/repo/peer-redirect.js +0 -26
- package/dist/repo/peers.d.ts +0 -39
- package/dist/repo/peers.js +0 -139
- package/dist/repo/resolve.d.ts +0 -41
- package/dist/repo/resolve.js +0 -152
- package/dist/repo/root.d.ts +0 -23
- package/dist/repo/root.js +0 -91
- package/dist/repo/source.d.ts +0 -14
- package/dist/repo/source.js +0 -54
- package/dist/repo/supabase-project.d.ts +0 -60
- package/dist/repo/supabase-project.js +0 -124
- package/dist/repo/tsx-loader.d.ts +0 -6
- package/dist/repo/tsx-loader.js +0 -41
- package/dist/run/pick.d.ts +0 -90
- package/dist/run/pick.js +0 -664
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -280
- package/dist/stack.d.ts +0 -48
- package/dist/stack.js +0 -403
- package/dist/telemetry.d.ts +0 -59
- package/dist/tier.d.ts +0 -40
- package/dist/tier.js +0 -96
- package/dist/ui.d.ts +0 -40
- package/dist/ui.js +0 -71
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -17
- package/dist/workers.d.ts +0 -5
- package/dist/workers.js +0 -33
- package/dist/workflows/commands.d.ts +0 -54
- package/dist/workflows/commands.js +0 -763
package/dist/persona.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `persona <member|moderator>` / `persona --clean` — throwaway accounts for
|
|
3
|
-
* a development database.
|
|
4
|
-
*
|
|
5
|
-
* These replace the seeded `member@devdogs.test` / `author@devdogs.test` /
|
|
6
|
-
* `moderator@devdogs.test` personas that used to come from
|
|
7
|
-
* `supabase/seed/development/02_moderation.sql`. That file — and the rest of
|
|
8
|
-
* `supabase/seed/development/` — is going away in the app repo, so a fresh
|
|
9
|
-
* `db reset` no longer leaves a signed-in account to switch to; this is what
|
|
10
|
-
* creates one on demand instead.
|
|
11
|
-
*
|
|
12
|
-
* Every account this creates lives at `<kind>-<8 hex>@persona.test`: a
|
|
13
|
-
* domain that cannot collide with a real UGA address, and — not
|
|
14
|
-
* coincidentally — the same reason it can exist here at all. `.createUser`
|
|
15
|
-
* goes through GoTrue's ADMIN API, which inserts the row directly rather
|
|
16
|
-
* than running the app's `before_user_created` Auth Hook (the one that
|
|
17
|
-
* requires a uga.edu address on the sign-up/OTP paths a real member takes).
|
|
18
|
-
* The admin API was never wired to that hook, so a `@persona.test` address
|
|
19
|
-
* sails through here uncontested.
|
|
20
|
-
*
|
|
21
|
-
* `--clean` deletes every account this command created — matched on BOTH the
|
|
22
|
-
* `@persona.test` domain AND a `user_metadata.createdBy` marker, never the
|
|
23
|
-
* domain alone. A shared development database can carry `@persona.test`
|
|
24
|
-
* accounts this command did not create — CI's own fixtures, most likely, or
|
|
25
|
-
* a `moderation check` run whose `withTemporaryModerator` teardown never
|
|
26
|
-
* reached its `finally` because the process was killed. The marker is what
|
|
27
|
-
* keeps `--clean` from reaching past accounts it is not responsible for.
|
|
28
|
-
*/
|
|
29
|
-
import { randomUUID } from "node:crypto";
|
|
30
|
-
import { confirm, log, note, select } from "@clack/prompts";
|
|
31
|
-
import { adminClient, resolveInstance } from "./instance.js";
|
|
32
|
-
import { grantModerator } from "./moderation.js";
|
|
33
|
-
import { explain, unwrap } from "./ui.js";
|
|
34
|
-
export const PERSONA_KINDS = ["member", "moderator"];
|
|
35
|
-
export function isPersonaKind(value) {
|
|
36
|
-
return PERSONA_KINDS.includes(value);
|
|
37
|
-
}
|
|
38
|
-
/** Every account this command creates carries this in `user_metadata`, and
|
|
39
|
-
* `--clean` refuses to delete anything without it — see the header. */
|
|
40
|
-
const CREATED_BY = "devtools persona";
|
|
41
|
-
function personaEmail(kind) {
|
|
42
|
-
return `${kind}-${randomUUID().slice(0, 8)}@persona.test`;
|
|
43
|
-
}
|
|
44
|
-
/** Nobody needs to remember this — it is printed once, for whoever asked. */
|
|
45
|
-
function randomPassword() {
|
|
46
|
-
return randomUUID() + randomUUID();
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Refuses outside development — a staging or production database has real
|
|
50
|
-
* members on it, and this command exists to populate one that does not.
|
|
51
|
-
* Exported so `cli.ts`'s `moderation check` (also development-only now, for
|
|
52
|
-
* the same reason) shares the exact refusal wording.
|
|
53
|
-
*/
|
|
54
|
-
export function refuseUnlessDevelopment(connection, label) {
|
|
55
|
-
if (connection.tier === "development")
|
|
56
|
-
return false;
|
|
57
|
-
process.stderr.write(`${label}: only runs against a development database — this session ` +
|
|
58
|
-
`targets ${connection.tier}. Relaunch with --tier development:local ` +
|
|
59
|
-
"or development:remote.\n");
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Creates the account and its profile — `userId` and `preferredName` are the
|
|
64
|
-
* only two columns `platform.profile` requires (see the migration; every
|
|
65
|
-
* other column is nullable or defaulted), which is why this needs nothing
|
|
66
|
-
* from `02_moderation.sql`'s richer INSERT beyond the shape of the two rows
|
|
67
|
-
* it wrote. Grants the Moderator role too, for `kind === "moderator"`.
|
|
68
|
-
*/
|
|
69
|
-
async function createPersona(instance, kind) {
|
|
70
|
-
const admin = adminClient(instance);
|
|
71
|
-
const email = personaEmail(kind);
|
|
72
|
-
const password = randomPassword();
|
|
73
|
-
const preferredName = kind === "moderator" ? "Devtools Moderator" : "Devtools Member";
|
|
74
|
-
const { data, error } = await admin.auth.admin.createUser({
|
|
75
|
-
email,
|
|
76
|
-
password,
|
|
77
|
-
email_confirm: true,
|
|
78
|
-
user_metadata: { createdBy: CREATED_BY },
|
|
79
|
-
});
|
|
80
|
-
if (error || !data.user) {
|
|
81
|
-
throw new Error(`Could not create the account: ${error?.message}`);
|
|
82
|
-
}
|
|
83
|
-
const userId = data.user.id;
|
|
84
|
-
const { error: profileErr } = await admin
|
|
85
|
-
.from("profile")
|
|
86
|
-
.insert({ userId, preferredName });
|
|
87
|
-
if (profileErr) {
|
|
88
|
-
throw new Error(`Could not create a profile: ${profileErr.message}`);
|
|
89
|
-
}
|
|
90
|
-
if (kind === "moderator")
|
|
91
|
-
await grantModerator(admin, userId);
|
|
92
|
-
return { email, password, userId, preferredName };
|
|
93
|
-
}
|
|
94
|
-
/** Any OTHER profile on this database, for a moderator persona to report —
|
|
95
|
-
* `null` when this is the only account around, which the caller reports
|
|
96
|
-
* rather than guessing at a subject that does not exist. */
|
|
97
|
-
async function findOtherProfile(instance, excludeUserId) {
|
|
98
|
-
const { data, error } = await adminClient(instance)
|
|
99
|
-
.from("profile")
|
|
100
|
-
.select("userId, preferredName")
|
|
101
|
-
.neq("userId", excludeUserId)
|
|
102
|
-
.limit(1)
|
|
103
|
-
.maybeSingle();
|
|
104
|
-
if (error)
|
|
105
|
-
throw new Error(`Could not read platform.profile: ${error.message}`);
|
|
106
|
-
return data;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Files one open report, mirroring the "one open report" `02_moderation.sql`
|
|
110
|
-
* used to seed — inserted directly rather than through
|
|
111
|
-
* `platform.file_report()`, the same reasoning that seed's own comment gave:
|
|
112
|
-
* neither a seed nor this command has a session to attribute the RPC call to.
|
|
113
|
-
*/
|
|
114
|
-
async function fileSampleReport(instance, reporterId, subjectId, subjectName) {
|
|
115
|
-
const admin = adminClient(instance);
|
|
116
|
-
const { data: app, error: appErr } = await admin
|
|
117
|
-
.from("apps")
|
|
118
|
-
.select("id")
|
|
119
|
-
.eq("slug", "platform")
|
|
120
|
-
.single();
|
|
121
|
-
if (appErr || !app) {
|
|
122
|
-
throw new Error(`Could not find the platform app row: ${appErr?.message}`);
|
|
123
|
-
}
|
|
124
|
-
const { error } = await admin.from("reports").insert({
|
|
125
|
-
appId: app.id,
|
|
126
|
-
reporterUserId: reporterId,
|
|
127
|
-
reportedUserId: subjectId,
|
|
128
|
-
contentType: "profile",
|
|
129
|
-
contentRef: subjectId,
|
|
130
|
-
contentSnapshot: subjectName,
|
|
131
|
-
description: "Filed by `pnpm devtools persona` so the moderation queue has something in it.",
|
|
132
|
-
reason: "spam",
|
|
133
|
-
});
|
|
134
|
-
if (error) {
|
|
135
|
-
throw new Error(`Could not file the sample report: ${error.message}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async function runClean(instance) {
|
|
139
|
-
const admin = adminClient(instance);
|
|
140
|
-
const { data, error } = await admin.auth.admin.listUsers();
|
|
141
|
-
if (error)
|
|
142
|
-
throw new Error(`Could not list accounts: ${error.message}`);
|
|
143
|
-
// Both conditions — see the header on why the domain alone is not enough.
|
|
144
|
-
const targets = data.users.filter((u) => u.email?.endsWith("@persona.test") &&
|
|
145
|
-
u.user_metadata?.createdBy === CREATED_BY);
|
|
146
|
-
if (targets.length === 0) {
|
|
147
|
-
log.info("No `pnpm devtools persona`-created accounts to clean up.");
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
// Deleting the auth user cascades to its profile, its Moderator role grant
|
|
151
|
-
// (if any) and any reports it filed or was reported in — every FK a
|
|
152
|
-
// persona account touches is `on delete cascade` — so there is nothing
|
|
153
|
-
// else to clean up per account.
|
|
154
|
-
for (const user of targets) {
|
|
155
|
-
await admin.auth.admin.deleteUser(user.id);
|
|
156
|
-
log.success(`Deleted ${user.email}.`);
|
|
157
|
-
}
|
|
158
|
-
log.info(`Deleted ${targets.length} account${targets.length === 1 ? "" : "s"}.`);
|
|
159
|
-
}
|
|
160
|
-
export async function runPersona(argv) {
|
|
161
|
-
const clean = argv.includes("--clean");
|
|
162
|
-
const positional = argv.find((arg) => !arg.startsWith("--"));
|
|
163
|
-
if (clean && positional) {
|
|
164
|
-
explain("Two answers to one question.", "--clean deletes every account this command made; a kind creates a new one.", ["pnpm devtools persona member", "pnpm devtools persona --clean"]);
|
|
165
|
-
process.exitCode = 1;
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const resolved = await resolveInstance({ label: "devtools persona" });
|
|
169
|
-
if (!resolved) {
|
|
170
|
-
process.exitCode = 1;
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const { connection, instance } = resolved;
|
|
174
|
-
if (refuseUnlessDevelopment(connection, "devtools persona")) {
|
|
175
|
-
process.exitCode = 1;
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (clean) {
|
|
179
|
-
await runClean(instance);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
let kind;
|
|
183
|
-
if (positional) {
|
|
184
|
-
if (!isPersonaKind(positional)) {
|
|
185
|
-
explain(`"${positional}" is not a persona kind.`, "", [
|
|
186
|
-
"pnpm devtools persona member",
|
|
187
|
-
"pnpm devtools persona moderator",
|
|
188
|
-
]);
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
kind = positional;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
kind = unwrap(await select({
|
|
196
|
-
message: "Which kind of persona?",
|
|
197
|
-
options: PERSONA_KINDS.map((value) => ({ value, label: value })),
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
const persona = await createPersona(instance, kind);
|
|
201
|
-
note(`Email: ${persona.email}\nPassword: ${persona.password}`, `${kind} persona created`);
|
|
202
|
-
const fileReport = unwrap(await confirm({
|
|
203
|
-
message: "File a sample report against a member profile, so the moderation queue isn't empty?",
|
|
204
|
-
initialValue: kind === "moderator",
|
|
205
|
-
}));
|
|
206
|
-
if (!fileReport)
|
|
207
|
-
return;
|
|
208
|
-
// A member persona reports itself — there is no separate reporter account
|
|
209
|
-
// on offer here, and nothing downstream cares who filed it, only that the
|
|
210
|
-
// queue has one open report to practice resolving. A moderator persona
|
|
211
|
-
// reports whatever OTHER profile already exists.
|
|
212
|
-
const subject = kind === "member"
|
|
213
|
-
? { userId: persona.userId, preferredName: persona.preferredName }
|
|
214
|
-
: await findOtherProfile(instance, persona.userId);
|
|
215
|
-
if (!subject) {
|
|
216
|
-
log.warn("No other account to report yet — run `pnpm devtools persona member` first.");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
await fileSampleReport(instance, persona.userId, subject.userId, subject.preferredName);
|
|
220
|
-
log.success("Filed a sample report. Resolve it from /console/moderation.");
|
|
221
|
-
}
|
package/dist/pick.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { VaultTarget } from "@devdogsuga/env";
|
|
2
|
-
/**
|
|
3
|
-
* `null` means the failure has already been explained; set an exit code and
|
|
4
|
-
* stop. It is not the same as a cancel, which exits the process outright.
|
|
5
|
-
*/
|
|
6
|
-
export declare function resolveVaultTarget(given: string | undefined, message: string): Promise<VaultTarget | null>;
|
package/dist/pick.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Choosing a target when the command line did not name one.
|
|
3
|
-
*
|
|
4
|
-
* `--target` is optional, and when it is absent this asks rather than guessing.
|
|
5
|
-
* There is no safe default to fall back to, because the targets differ
|
|
6
|
-
* precisely in how much damage picking the wrong one does.
|
|
7
|
-
*
|
|
8
|
-
* Three properties the prompt is built around:
|
|
9
|
-
*
|
|
10
|
-
* * Production is never the highlighted option. The list is ordered least-
|
|
11
|
-
* to most-dangerous (that order lives in the target table), so a reflexive
|
|
12
|
-
* Enter selects `preflight`.
|
|
13
|
-
* * It refuses to prompt where nobody can answer. A prompt on a
|
|
14
|
-
* non-interactive stdin hangs until the job times out, which reads as a
|
|
15
|
-
* broken tool rather than a missing argument.
|
|
16
|
-
* * `development` is refused by name, not by silence. It is a real target
|
|
17
|
-
* with a file, and it is the one target with no Bitwarden project, so
|
|
18
|
-
* `pull`/`push`/`audit` cannot do anything with it. Letting it fall into
|
|
19
|
-
* "not a target" would say something false; the message says which fact
|
|
20
|
-
* rules it out.
|
|
21
|
-
*/
|
|
22
|
-
import { select } from "@clack/prompts";
|
|
23
|
-
import { loadEnv } from "./repo/peers.js";
|
|
24
|
-
import { noVaultProjectHints, NoVaultProjectError, } from "./bws/environments.js";
|
|
25
|
-
import { explain, unwrap } from "./ui.js";
|
|
26
|
-
/** Short enough to sit beside the name; the specs' summaries are paragraphs. */
|
|
27
|
-
const VAULT_HINTS = {
|
|
28
|
-
preflight: "read-only credentials for the pre-promotion checks",
|
|
29
|
-
staging: "the everyday one — safe to overwrite",
|
|
30
|
-
production: "⚠️ the live values",
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* `null` means the failure has already been explained; set an exit code and
|
|
34
|
-
* stop. It is not the same as a cancel, which exits the process outright.
|
|
35
|
-
*/
|
|
36
|
-
export async function resolveVaultTarget(given, message) {
|
|
37
|
-
const { ENV_TARGETS, VAULT_TARGETS, isEnvTarget, isVaultTarget } = await loadEnv();
|
|
38
|
-
if (given !== undefined) {
|
|
39
|
-
if (isVaultTarget(given))
|
|
40
|
-
return given;
|
|
41
|
-
if (isEnvTarget(given)) {
|
|
42
|
-
explain(new NoVaultProjectError(given).message, "", noVaultProjectHints());
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
explain(`"${given}" is not a target.`, "", [
|
|
46
|
-
`Try one of: ${ENV_TARGETS.join(", ")}`,
|
|
47
|
-
`Only ${VAULT_TARGETS.join(", ")} have a Bitwarden project.`,
|
|
48
|
-
]);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
if (!process.stdin.isTTY) {
|
|
52
|
-
explain("No target given, and there is nobody here to ask.", "", [
|
|
53
|
-
`Pass --target: ${VAULT_TARGETS.join(" | ")}`,
|
|
54
|
-
]);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
return unwrap(await select({
|
|
58
|
-
message,
|
|
59
|
-
options: VAULT_TARGETS.map((value) => ({
|
|
60
|
-
value,
|
|
61
|
-
hint: VAULT_HINTS[value],
|
|
62
|
-
})),
|
|
63
|
-
}));
|
|
64
|
-
}
|
package/dist/pipes.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function ignoreClosedPipes(): void;
|
package/dist/pipes.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keeps a closed stdout/stderr from crashing devtools.
|
|
3
|
-
*
|
|
4
|
-
* When whatever reads devtools' output goes away first (`devtools … | head`,
|
|
5
|
-
* or a wrapper that stopped listening), the next write fails with EPIPE. The
|
|
6
|
-
* stream emits that as an `'error'` event nobody handles, so Node throws it as
|
|
7
|
-
* an uncaught exception and Sentry files it as a fatal. Nobody is left to read
|
|
8
|
-
* the output, so dropping it is the right outcome. Any other stream error is
|
|
9
|
-
* rethrown and crashes as it did before.
|
|
10
|
-
*
|
|
11
|
-
* Idempotent, because `launch`, `launchCi` and `ci.ts`'s `main` can all run in
|
|
12
|
-
* one process.
|
|
13
|
-
*/
|
|
14
|
-
let installed = false;
|
|
15
|
-
export function ignoreClosedPipes() {
|
|
16
|
-
if (installed)
|
|
17
|
-
return;
|
|
18
|
-
installed = true;
|
|
19
|
-
for (const stream of [process.stdout, process.stderr]) {
|
|
20
|
-
stream.on("error", (err) => {
|
|
21
|
-
if (err.code !== "EPIPE")
|
|
22
|
-
throw err;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
package/dist/planner/checks.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { PlannerDb } from "./db.js";
|
|
2
|
-
/** Constants, so `PlannerDb.run` never sees a composed string. */
|
|
3
|
-
export declare const CHECK_IDENTITY = "select current_user as who";
|
|
4
|
-
/**
|
|
5
|
-
* `platform.profile` because it exists in every tier and holds real member
|
|
6
|
-
* data in production: the exact rows §3.5 refuses to put behind `main`.
|
|
7
|
-
*/
|
|
8
|
-
export declare const CHECK_OVERREACH = "select * from platform.profile limit 1";
|
|
9
|
-
export declare const CHECK_MIGRATIONS = "select count(*)::int as n from supabase_migrations.schema_migrations";
|
|
10
|
-
export interface PlannerVerdict {
|
|
11
|
-
ok: boolean;
|
|
12
|
-
/** One line per check, in the order run; the report either way. */
|
|
13
|
-
lines: string[];
|
|
14
|
-
/** Set when `ok` is false: what to go and fix. */
|
|
15
|
-
problem?: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Runs the three checks over an OPEN connection; never closes it.
|
|
19
|
-
*
|
|
20
|
-
* Any error from check 2 counts as "cannot read": the interesting distinction
|
|
21
|
-
* is readable versus not, and a role that cannot even enumerate the schema
|
|
22
|
-
* (the validated state) throws just as loudly as one denied on the table.
|
|
23
|
-
*/
|
|
24
|
-
export declare function checkPlanner(db: PlannerDb): Promise<PlannerVerdict>;
|
|
25
|
-
/** Postgres 3F000 (invalid_schema_name), with a text fallback. */
|
|
26
|
-
export declare function isMissingSchema(error: unknown): boolean;
|
package/dist/planner/checks.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The three questions that decide whether a connection IS the planner:
|
|
3
|
-
*
|
|
4
|
-
* 1. Who did Postgres authenticate? (`current_user`)
|
|
5
|
-
* 2. Can it read an application schema? (it must not)
|
|
6
|
-
* 3. Can it read the migrations table? (it must, the dry run is next)
|
|
7
|
-
*
|
|
8
|
-
* Shared between `deploy require-planner` (which turns a bad answer into a
|
|
9
|
-
* red CI job) and `planner create` / `planner reset-password` (which run the
|
|
10
|
-
* same checks over a fresh connection before calling the credential done),
|
|
11
|
-
* so "validated working" means the same thing in both places.
|
|
12
|
-
*
|
|
13
|
-
* ⚠️ Check 2 exists because nothing else can catch the mistake it looks for.
|
|
14
|
-
* The preflight `DB_URL` is a value somebody composes by hand, the registry
|
|
15
|
-
* cannot verify it, and a full-access connection string pasted there works
|
|
16
|
-
* identically: the dry run succeeds, `env push` uploads it, `env audit` sees
|
|
17
|
-
* no drift. Reading a `platform.*` table and EXPECTING the permission error
|
|
18
|
-
* is the only observable difference between the right credential and the
|
|
19
|
-
* wrong one.
|
|
20
|
-
*/
|
|
21
|
-
import { PLANNER_ROLE } from "./role.js";
|
|
22
|
-
/** Constants, so `PlannerDb.run` never sees a composed string. */
|
|
23
|
-
export const CHECK_IDENTITY = "select current_user as who";
|
|
24
|
-
/**
|
|
25
|
-
* `platform.profile` because it exists in every tier and holds real member
|
|
26
|
-
* data in production: the exact rows §3.5 refuses to put behind `main`.
|
|
27
|
-
*/
|
|
28
|
-
export const CHECK_OVERREACH = "select * from platform.profile limit 1";
|
|
29
|
-
export const CHECK_MIGRATIONS = "select count(*)::int as n from supabase_migrations.schema_migrations";
|
|
30
|
-
/**
|
|
31
|
-
* Runs the three checks over an OPEN connection; never closes it.
|
|
32
|
-
*
|
|
33
|
-
* Any error from check 2 counts as "cannot read": the interesting distinction
|
|
34
|
-
* is readable versus not, and a role that cannot even enumerate the schema
|
|
35
|
-
* (the validated state) throws just as loudly as one denied on the table.
|
|
36
|
-
*/
|
|
37
|
-
export async function checkPlanner(db) {
|
|
38
|
-
const lines = [];
|
|
39
|
-
const [identity] = await db.run(CHECK_IDENTITY);
|
|
40
|
-
const who = typeof identity?.who === "string" ? identity.who : "";
|
|
41
|
-
if (who !== PLANNER_ROLE) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
lines,
|
|
45
|
-
problem: `the connection authenticated as "${who}", not ${PLANNER_ROLE} — ` +
|
|
46
|
-
"this is a full-privilege connection string where the planner role " +
|
|
47
|
-
"belongs.",
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
lines.push(`authenticated as ${PLANNER_ROLE}`);
|
|
51
|
-
let overreach = false;
|
|
52
|
-
try {
|
|
53
|
-
await db.run(CHECK_OVERREACH);
|
|
54
|
-
overreach = true;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
lines.push("cannot read platform.* (permission denied, as it must be)");
|
|
58
|
-
}
|
|
59
|
-
if (overreach) {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
lines,
|
|
63
|
-
problem: `${PLANNER_ROLE} can read platform.profile. The role has grants ` +
|
|
64
|
-
"beyond the validated pair — drop and re-create it, then re-push " +
|
|
65
|
-
"the preflight target.",
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const [row] = await db.run(CHECK_MIGRATIONS);
|
|
70
|
-
lines.push(`can read supabase_migrations.schema_migrations (${String(row?.n)} rows)`);
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
// Two causes share this symptom, and the fix for one is a GRANT while
|
|
74
|
-
// the fix for the other is initializing the database's migration
|
|
75
|
-
// history, so the message has to say which. 3F000 is
|
|
76
|
-
// invalid_schema_name; the text match covers drivers that drop the code.
|
|
77
|
-
if (isMissingSchema(error)) {
|
|
78
|
-
return {
|
|
79
|
-
ok: false,
|
|
80
|
-
lines,
|
|
81
|
-
problem: "the supabase_migrations schema does not exist on this database. " +
|
|
82
|
-
"The Supabase CLI creates it the first time it records migration " +
|
|
83
|
-
"history there (`supabase db push`, or `supabase migration " +
|
|
84
|
-
"repair` when baselining an existing database) — until then " +
|
|
85
|
-
"there is nothing for the planner to read and nothing for the " +
|
|
86
|
-
"dry run to plan against. This is not a grants problem.",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
lines,
|
|
92
|
-
problem: `${PLANNER_ROLE} cannot read supabase_migrations.schema_migrations, ` +
|
|
93
|
-
"so the migration dry run would fail right after this. Re-apply the " +
|
|
94
|
-
"two grants (see planner/role.ts).",
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
return { ok: true, lines };
|
|
98
|
-
}
|
|
99
|
-
/** Postgres 3F000 (invalid_schema_name), with a text fallback. */
|
|
100
|
-
export function isMissingSchema(error) {
|
|
101
|
-
const e = error;
|
|
102
|
-
return (e.code === "3F000" ||
|
|
103
|
-
/schema "?supabase_migrations"? does not exist/i.test(e.message ?? ""));
|
|
104
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type Connect } from "./db.js";
|
|
2
|
-
export interface PlannerOptions {
|
|
3
|
-
/** Privileged connection override for the bootstrap case. */
|
|
4
|
-
dbUrl?: string;
|
|
5
|
-
connect?: Connect;
|
|
6
|
-
}
|
|
7
|
-
export declare function runPlannerStatus(options?: PlannerOptions): Promise<void>;
|
|
8
|
-
export declare function runPlannerCreate(options?: PlannerOptions): Promise<void>;
|
|
9
|
-
export declare function runPlannerDrop(options?: PlannerOptions): Promise<void>;
|
|
10
|
-
export declare function runPlannerResetPassword(options?: PlannerOptions): Promise<void>;
|