@devdogsuga/devtools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/bin/devtools-ci-bare.mjs +48 -0
- package/bin/devtools-ci.mjs +17 -0
- package/bin/devtools.mjs +27 -0
- package/dist/args.d.ts +26 -0
- package/dist/args.js +78 -0
- package/dist/bws/bw.d.ts +9 -0
- package/dist/bws/bw.js +49 -0
- package/dist/bws/client.d.ts +43 -0
- package/dist/bws/client.js +363 -0
- package/dist/bws/environments.d.ts +96 -0
- package/dist/bws/environments.js +111 -0
- package/dist/bws/pace.d.ts +27 -0
- package/dist/bws/pace.js +43 -0
- package/dist/bws/retry.d.ts +12 -0
- package/dist/bws/retry.js +39 -0
- package/dist/bws/store.d.ts +1 -0
- package/dist/bws/store.js +32 -0
- package/dist/bws/token.d.ts +31 -0
- package/dist/bws/token.js +88 -0
- package/dist/bws/vault.d.ts +29 -0
- package/dist/bws/vault.js +200 -0
- package/dist/catalog.d.ts +55 -0
- package/dist/catalog.js +81 -0
- package/dist/cf/commands.d.ts +1 -0
- package/dist/cf/commands.js +182 -0
- package/dist/cf/local-env.d.ts +44 -0
- package/dist/cf/local-env.js +148 -0
- package/dist/ci.d.ts +1 -0
- package/dist/ci.js +319 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +1077 -0
- package/dist/commands.d.ts +255 -0
- package/dist/commands.js +1279 -0
- package/dist/completions.d.ts +4 -0
- package/dist/completions.js +110 -0
- package/dist/cron/commands.d.ts +47 -0
- package/dist/cron/commands.js +372 -0
- package/dist/cron/discovery.d.ts +55 -0
- package/dist/cron/discovery.js +85 -0
- package/dist/cron/schema.d.ts +45 -0
- package/dist/cron/schema.js +35 -0
- package/dist/db/config-push.d.ts +8 -0
- package/dist/db/config-push.js +11 -0
- package/dist/db/connection.d.ts +79 -0
- package/dist/db/connection.js +114 -0
- package/dist/db/exec.d.ts +1 -0
- package/dist/db/exec.js +11 -0
- package/dist/db/generate-migration.d.ts +1 -0
- package/dist/db/generate-migration.js +67 -0
- package/dist/db/generate-types.d.ts +1 -0
- package/dist/db/generate-types.js +4 -0
- package/dist/db/introspect.d.ts +1 -0
- package/dist/db/introspect.js +113 -0
- package/dist/db/local-env.d.ts +65 -0
- package/dist/db/local-env.js +23 -0
- package/dist/db/new-migration.d.ts +1 -0
- package/dist/db/new-migration.js +8 -0
- package/dist/db/run.d.ts +70 -0
- package/dist/db/run.js +158 -0
- package/dist/db/seed-buckets.d.ts +2 -0
- package/dist/db/seed-buckets.js +4 -0
- package/dist/db/seed-production.d.ts +8 -0
- package/dist/db/seed-production.js +73 -0
- package/dist/db/seed-roles.d.ts +7 -0
- package/dist/db/seed-roles.js +31 -0
- package/dist/db/session-refresh.d.ts +43 -0
- package/dist/db/session-refresh.js +40 -0
- package/dist/deploy/migrations.d.ts +20 -0
- package/dist/deploy/migrations.js +68 -0
- package/dist/deploy/orphans.d.ts +20 -0
- package/dist/deploy/orphans.js +177 -0
- package/dist/deploy/preflight.d.ts +53 -0
- package/dist/deploy/preflight.js +189 -0
- package/dist/deploy/report.d.ts +26 -0
- package/dist/deploy/report.js +70 -0
- package/dist/deploy/require-planner.d.ts +2 -0
- package/dist/deploy/require-planner.js +60 -0
- package/dist/deploy/require-token.d.ts +15 -0
- package/dist/deploy/require-token.js +78 -0
- package/dist/deploy/secrets-file.d.ts +17 -0
- package/dist/deploy/secrets-file.js +167 -0
- package/dist/deploy/write-env.d.ts +40 -0
- package/dist/deploy/write-env.js +393 -0
- package/dist/docs/index-pages.d.ts +36 -0
- package/dist/docs/index-pages.js +184 -0
- package/dist/doctor.d.ts +62 -0
- package/dist/doctor.js +225 -0
- package/dist/emails/commands.d.ts +15 -0
- package/dist/emails/commands.js +130 -0
- package/dist/emails/email-types.d.ts +13 -0
- package/dist/emails/email-types.js +1 -0
- package/dist/emails/fixtures.d.ts +20 -0
- package/dist/emails/fixtures.js +20 -0
- package/dist/env/audit.d.ts +206 -0
- package/dist/env/audit.js +436 -0
- package/dist/env/cloudflare.d.ts +21 -0
- package/dist/env/cloudflare.js +61 -0
- package/dist/env/commands.d.ts +99 -0
- package/dist/env/commands.js +612 -0
- package/dist/env/discovery.d.ts +15 -0
- package/dist/env/discovery.js +226 -0
- package/dist/env/document.d.ts +119 -0
- package/dist/env/document.js +323 -0
- package/dist/env/example.d.ts +68 -0
- package/dist/env/example.js +603 -0
- package/dist/env/selection.d.ts +105 -0
- package/dist/env/selection.js +226 -0
- package/dist/environment.d.ts +92 -0
- package/dist/environment.js +206 -0
- package/dist/fingerprint.d.ts +10 -0
- package/dist/fingerprint.js +18 -0
- package/dist/gen/campus-map.d.ts +1 -0
- package/dist/gen/campus-map.js +473 -0
- package/dist/gen/commands.d.ts +1 -0
- package/dist/gen/commands.js +25 -0
- package/dist/gen/hypno.d.ts +1 -0
- package/dist/gen/hypno.js +134 -0
- package/dist/gh/client.d.ts +80 -0
- package/dist/gh/client.js +258 -0
- package/dist/gh/environments.d.ts +83 -0
- package/dist/gh/environments.js +178 -0
- package/dist/gh/rulesets/actors.d.ts +14 -0
- package/dist/gh/rulesets/actors.js +68 -0
- package/dist/gh/rulesets/api.d.ts +15 -0
- package/dist/gh/rulesets/api.js +99 -0
- package/dist/gh/rulesets/commands.d.ts +3 -0
- package/dist/gh/rulesets/commands.js +203 -0
- package/dist/gh/rulesets/desired.d.ts +112 -0
- package/dist/gh/rulesets/desired.js +247 -0
- package/dist/gh/rulesets/diff.d.ts +65 -0
- package/dist/gh/rulesets/diff.js +134 -0
- package/dist/gh/rulesets/types.d.ts +76 -0
- package/dist/gh/rulesets/types.js +13 -0
- package/dist/gh/settings/api.d.ts +40 -0
- package/dist/gh/settings/api.js +172 -0
- package/dist/gh/settings/commands.d.ts +1 -0
- package/dist/gh/settings/commands.js +245 -0
- package/dist/gh/settings/desired.d.ts +53 -0
- package/dist/gh/settings/desired.js +39 -0
- package/dist/gh/settings/diff.d.ts +57 -0
- package/dist/gh/settings/diff.js +156 -0
- package/dist/gh/settings/types.d.ts +92 -0
- package/dist/gh/settings/types.js +11 -0
- package/dist/gh/settings/workflows.d.ts +41 -0
- package/dist/gh/settings/workflows.js +124 -0
- package/dist/grantRoot.d.ts +42 -0
- package/dist/grantRoot.js +89 -0
- package/dist/help.d.ts +18 -0
- package/dist/help.js +169 -0
- package/dist/images/commands.d.ts +35 -0
- package/dist/images/commands.js +233 -0
- package/dist/images/events.d.ts +36 -0
- package/dist/images/events.js +152 -0
- package/dist/images/graphics.d.ts +70 -0
- package/dist/images/graphics.js +233 -0
- package/dist/images/open-graph-types.d.ts +75 -0
- package/dist/images/open-graph-types.js +1 -0
- package/dist/images/prompts.d.ts +34 -0
- package/dist/images/prompts.js +100 -0
- package/dist/images/render.d.ts +21 -0
- package/dist/images/render.js +57 -0
- package/dist/images/select.d.ts +64 -0
- package/dist/images/select.js +96 -0
- package/dist/instance.d.ts +67 -0
- package/dist/instance.js +129 -0
- package/dist/invocation.d.ts +49 -0
- package/dist/invocation.js +78 -0
- package/dist/launch-ci.d.ts +1 -0
- package/dist/launch-ci.js +86 -0
- package/dist/launch.d.ts +29 -0
- package/dist/launch.js +371 -0
- package/dist/menu.d.ts +31 -0
- package/dist/menu.js +277 -0
- package/dist/newsletter/commands.d.ts +16 -0
- package/dist/newsletter/commands.js +311 -0
- package/dist/newsletter/imap.d.ts +17 -0
- package/dist/newsletter/imap.js +127 -0
- package/dist/newsletter/loopback.d.ts +24 -0
- package/dist/newsletter/loopback.js +100 -0
- package/dist/newsletter/oauth.d.ts +26 -0
- package/dist/newsletter/oauth.js +130 -0
- package/dist/newsletter/smtp.d.ts +23 -0
- package/dist/newsletter/smtp.js +133 -0
- package/dist/oauth/browser.d.ts +1 -0
- package/dist/oauth/browser.js +13 -0
- package/dist/oauth/config.d.ts +24 -0
- package/dist/oauth/config.js +24 -0
- package/dist/oauth/connect-url.d.ts +17 -0
- package/dist/oauth/connect-url.js +23 -0
- package/dist/oauth/db.d.ts +37 -0
- package/dist/oauth/db.js +108 -0
- package/dist/oauth/device.d.ts +93 -0
- package/dist/oauth/device.js +190 -0
- package/dist/oauth/discovery.d.ts +28 -0
- package/dist/oauth/discovery.js +54 -0
- package/dist/oauth/env-file.d.ts +6 -0
- package/dist/oauth/env-file.js +35 -0
- package/dist/oauth/exchange.d.ts +43 -0
- package/dist/oauth/exchange.js +87 -0
- package/dist/oauth/label.d.ts +1 -0
- package/dist/oauth/label.js +25 -0
- package/dist/oauth/loopback.d.ts +52 -0
- package/dist/oauth/loopback.js +162 -0
- package/dist/oauth/pkce.d.ts +6 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/target.d.ts +54 -0
- package/dist/oauth/target.js +102 -0
- package/dist/oauth/transport.d.ts +41 -0
- package/dist/oauth/transport.js +32 -0
- package/dist/oauth/wizard.d.ts +33 -0
- package/dist/oauth/wizard.js +571 -0
- package/dist/pick.d.ts +6 -0
- package/dist/pick.js +64 -0
- package/dist/planner/checks.d.ts +26 -0
- package/dist/planner/checks.js +104 -0
- package/dist/planner/commands.d.ts +10 -0
- package/dist/planner/commands.js +367 -0
- package/dist/planner/db.d.ts +15 -0
- package/dist/planner/db.js +33 -0
- package/dist/planner/role.d.ts +33 -0
- package/dist/planner/role.js +68 -0
- package/dist/repo/peers.d.ts +24 -0
- package/dist/repo/peers.js +114 -0
- package/dist/repo/preflight.d.ts +109 -0
- package/dist/repo/preflight.js +266 -0
- package/dist/repo/resolve.d.ts +41 -0
- package/dist/repo/resolve.js +152 -0
- package/dist/repo/root.d.ts +23 -0
- package/dist/repo/root.js +91 -0
- package/dist/repo/source.d.ts +14 -0
- package/dist/repo/source.js +54 -0
- package/dist/repo/tsx-loader.d.ts +6 -0
- package/dist/repo/tsx-loader.js +41 -0
- package/dist/run/pick.d.ts +60 -0
- package/dist/run/pick.js +559 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +152 -0
- package/dist/stack.d.ts +48 -0
- package/dist/stack.js +290 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +93 -0
- package/dist/tier.d.ts +40 -0
- package/dist/tier.js +93 -0
- package/dist/tsconfig.typecheck.tsbuildinfo +1 -0
- package/dist/ui.d.ts +25 -0
- package/dist/ui.js +51 -0
- package/dist/workers.d.ts +5 -0
- package/dist/workers.js +35 -0
- package/dist/workflows/commands.d.ts +44 -0
- package/dist/workflows/commands.js +668 -0
- package/env.ts +158 -0
- package/package.json +80 -0
package/dist/setup.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-command onboarding, the first thing a new contributor runs.
|
|
3
|
+
*
|
|
4
|
+
* Checks prerequisites (warns, never hard-fails on optional tools), seeds the
|
|
5
|
+
* root `.env`, and points at the database and OAuth steps. Deliberately does NOT run remote
|
|
6
|
+
* Supabase commands: linking a remote project needs credentials that are not
|
|
7
|
+
* in `.env` yet.
|
|
8
|
+
*
|
|
9
|
+
* Was `scripts/setup.ts` at the repo root, resolving paths from
|
|
10
|
+
* `process.cwd()`, so it only worked when invoked from the root. Same class of
|
|
11
|
+
* bug that made the Supabase CLI look for `supabase_db_platform`. It resolves
|
|
12
|
+
* from `findRepoRoot()` now and works from anywhere.
|
|
13
|
+
*/
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { log, note } from "@clack/prompts";
|
|
18
|
+
import { loadRegistry } from "./env/discovery.js";
|
|
19
|
+
import { renderInit, resolveSections } from "./env/example.js";
|
|
20
|
+
import { discoverRepoRoot } from "./repo/root.js";
|
|
21
|
+
function has(cmd) {
|
|
22
|
+
try {
|
|
23
|
+
execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function runSetup() {
|
|
31
|
+
const checks = [];
|
|
32
|
+
// ── Prerequisites ──────────────────────────────────────────────────────────
|
|
33
|
+
//
|
|
34
|
+
// Optional tools report as information rather than warnings. A contributor
|
|
35
|
+
// without Flutter is not misconfigured; they are just not working on the
|
|
36
|
+
// Flutter app, and saying otherwise trains people to ignore the output.
|
|
37
|
+
const node = process.versions.node;
|
|
38
|
+
// The floor is 22.12, not the .nvmrc pin (24): pnpm 11 needs node:sqlite,
|
|
39
|
+
// which appeared in 22.5 and went LTS-stable at 22.12. CI's first-ever run
|
|
40
|
+
// failed on exactly this, with Node 20 from the old pin.
|
|
41
|
+
const [major = 0, minor = 0] = node.split(".").map(Number);
|
|
42
|
+
checks.push(major > 22 || (major === 22 && minor >= 12)
|
|
43
|
+
? `OK Node ${node}`
|
|
44
|
+
: `WARN Node ${node} — this repo needs >= 22.12 for pnpm 11 (see .nvmrc)`);
|
|
45
|
+
checks.push(has("corepack --version")
|
|
46
|
+
? "OK corepack available"
|
|
47
|
+
: "WARN corepack not found — run `corepack enable` for the pinned pnpm");
|
|
48
|
+
checks.push(has("docker info")
|
|
49
|
+
? "OK Docker running (local Supabase stack available)"
|
|
50
|
+
: "INFO Docker not running — fine against a hosted project, needed to run Supabase on this machine");
|
|
51
|
+
checks.push(has("flutter --version")
|
|
52
|
+
? "OK Flutter installed (study-group-finder buildable)"
|
|
53
|
+
: "INFO Flutter not installed — only needed for apps/study-group-finder");
|
|
54
|
+
// ── No repo yet? ───────────────────────────────────────────────────────────
|
|
55
|
+
//
|
|
56
|
+
// `discoverRepoRoot()` (the non-throwing lookup — see `repo/root.ts`)
|
|
57
|
+
// rather than `findRepoRoot()`: per the carve-out plan's §5,
|
|
58
|
+
// `pnpm dlx @devdogsuga/devtools setup` is meant to work PRE-CLONE, run
|
|
59
|
+
// from wherever a contributor happens to be before they have a checkout
|
|
60
|
+
// at all. The prerequisite checks above need no repo either way; only the
|
|
61
|
+
// `.env`-seeding half below does, so that's the only part skipped here.
|
|
62
|
+
const repoRoot = discoverRepoRoot();
|
|
63
|
+
if (repoRoot === null) {
|
|
64
|
+
note(checks.join("\n"), "Prerequisites");
|
|
65
|
+
note([
|
|
66
|
+
"No DevDogsUGA checkout found in this directory or its parents.",
|
|
67
|
+
"",
|
|
68
|
+
"1. git clone https://github.com/devdogsuga/DevDogsUGA.git",
|
|
69
|
+
"2. cd DevDogsUGA && pnpm install",
|
|
70
|
+
"3. pnpm devtools setup — run again from inside the clone to seed .env",
|
|
71
|
+
].join("\n"), "Next steps");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// ── Env ────────────────────────────────────────────────────────────────────
|
|
75
|
+
// Seeded from the SAME renderer as `env init` and the generated
|
|
76
|
+
// .env.example, rather than by copying the example file: a copy would carry
|
|
77
|
+
// the example's "GENERATED ... Do not edit this file" header into the one
|
|
78
|
+
// file that is meant to be edited. Like init, an existing file is untouched.
|
|
79
|
+
const env = join(repoRoot, ".env");
|
|
80
|
+
let seededEnv = false;
|
|
81
|
+
// The apps the picker chose, so "Next steps" boots what this contributor is
|
|
82
|
+
// actually here for rather than a hardcoded default. Null when the .env
|
|
83
|
+
// already existed (no picker ran) — the generic `pnpm dev` picker covers it.
|
|
84
|
+
let chosenApps = null;
|
|
85
|
+
if (existsSync(env)) {
|
|
86
|
+
checks.push("OK .env already exists (left untouched)");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
await loadRegistry();
|
|
90
|
+
// The project picker: a contributor's .env carries the sections for the
|
|
91
|
+
// projects they picked (plus the shared Supabase stack), not all six.
|
|
92
|
+
// The operator tooling is offered as a ROLE in the same picker, off by
|
|
93
|
+
// default: no app reads those keys, and every command that needs one
|
|
94
|
+
// refuses by name with the recovery step. Re-running `env init` later
|
|
95
|
+
// APPENDS the sections for a new project without touching filled values.
|
|
96
|
+
const sections = await resolveSections();
|
|
97
|
+
if (sections) {
|
|
98
|
+
// Lead with the two projects most contributors join; platform is shared
|
|
99
|
+
// auth infrastructure you depend on but rarely edit, and sandbox is
|
|
100
|
+
// dormant, trailing last as the one almost nobody touches.
|
|
101
|
+
const order = [
|
|
102
|
+
"schedule-builder",
|
|
103
|
+
"study-group-finder",
|
|
104
|
+
"platform",
|
|
105
|
+
"sandbox",
|
|
106
|
+
];
|
|
107
|
+
chosenApps = order.filter((app) => sections.has(app));
|
|
108
|
+
}
|
|
109
|
+
writeFileSync(env, renderInit("development", new Date().toISOString().slice(0, 10), sections), { flag: "wx" });
|
|
110
|
+
checks.push("OK created .env (same as `pnpm devtools env init`)");
|
|
111
|
+
seededEnv = true;
|
|
112
|
+
}
|
|
113
|
+
note(checks.join("\n"), "Prerequisites");
|
|
114
|
+
if (seededEnv) {
|
|
115
|
+
log.info(".env starts blank. Supabase on this machine fills the connection block " +
|
|
116
|
+
"for you; only a hosted project needs values typed in.");
|
|
117
|
+
}
|
|
118
|
+
// Boot what the contributor picked. study-group-finder still goes through
|
|
119
|
+
// the same `pnpm dev --filter` picker path (pnpm runs its `flutter run`
|
|
120
|
+
// task), so the shape is uniform; the SDK note is the only difference.
|
|
121
|
+
const startSteps = chosenApps && chosenApps.length > 0
|
|
122
|
+
? chosenApps.map((app) => app === "study-group-finder"
|
|
123
|
+
? ` pnpm dev --filter study-group-finder (Flutter — needs the SDK)`
|
|
124
|
+
: ` pnpm dev --filter ${app}`)
|
|
125
|
+
: [" pnpm dev — then pick your app from the list"];
|
|
126
|
+
note([
|
|
127
|
+
"1. Run `pnpm devtools` again and choose:",
|
|
128
|
+
" Database → start — boots the local Docker stack and writes",
|
|
129
|
+
" .env.generated (no credentials needed)",
|
|
130
|
+
"",
|
|
131
|
+
'2. Choose Workspace → oauth to configure "Sign in with DevDogs"',
|
|
132
|
+
" after the local database is running. Every app signs in through",
|
|
133
|
+
" platform's OAuth server, so this step is shared no matter which",
|
|
134
|
+
" project you are building.",
|
|
135
|
+
"",
|
|
136
|
+
"3. Start the project you picked:",
|
|
137
|
+
...startSteps,
|
|
138
|
+
...(chosenApps?.includes("schedule-builder")
|
|
139
|
+
? [
|
|
140
|
+
"",
|
|
141
|
+
" schedule-builder starts with an empty catalog. Populate it by",
|
|
142
|
+
" triggering the registrar scrape workflow (starts Wrangler for you):",
|
|
143
|
+
" pnpm devtools workflows run --app schedule-builder --tier development",
|
|
144
|
+
]
|
|
145
|
+
: []),
|
|
146
|
+
"",
|
|
147
|
+
"Working against a hosted Supabase project instead? Fill in .env",
|
|
148
|
+
"(dashboard → Project Settings), then:",
|
|
149
|
+
" pnpm devtools db connect <project-ref>",
|
|
150
|
+
" pnpm devtools db types",
|
|
151
|
+
].join("\n"), "Next steps");
|
|
152
|
+
}
|
package/dist/stack.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type DbConnection } from "./db/connection.js";
|
|
2
|
+
import { originReachable } from "./cron/commands.js";
|
|
3
|
+
export declare const STACK_COMMANDS: readonly ["start", "stop", "restart", "status", "migrate", "reset"];
|
|
4
|
+
export type StackCommand = (typeof STACK_COMMANDS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* `db connect [<project-ref>]` — run `supabase link` for whoever drives the
|
|
7
|
+
* bare supabase CLI by hand. Nothing in devtools reads what it writes any
|
|
8
|
+
* more (see `db/connection.ts`'s header); it survives as a convenience, not
|
|
9
|
+
* a dependency.
|
|
10
|
+
*/
|
|
11
|
+
export declare function connectRemoteProject(ref?: string): Promise<number>;
|
|
12
|
+
/** Injectable so a test can fake "server up" / "server down" and inspect the
|
|
13
|
+
* request `reconcileConfigAfterReset` sends, without a real dev server or a
|
|
14
|
+
* `vi.stubGlobal` on `fetch`. Defaults to the real network. */
|
|
15
|
+
export interface ReconcileConfigDeps {
|
|
16
|
+
reachable: typeof originReachable;
|
|
17
|
+
fetch: typeof globalThis.fetch;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort trigger for `GET /cron/config-reconcile` against the local
|
|
21
|
+
* platform dev server, so `db reset` leaves meetings and workshops seeded
|
|
22
|
+
* whenever that server happens to already be up -- the common case for a
|
|
23
|
+
* repeat reset during development, rather than a first clone. No
|
|
24
|
+
* `CRON_SECRET` is sent because the route itself skips auth outside a
|
|
25
|
+
* deployed `DEPLOY_ENV` (see that route's header); this is exactly the
|
|
26
|
+
* unauthenticated local request `cron run` would send.
|
|
27
|
+
*
|
|
28
|
+
* When nothing answers yet -- most likely a first-time reset, before anyone
|
|
29
|
+
* has run `pnpm --filter platform dev` -- this reports that instead of
|
|
30
|
+
* silently leaving the tables empty, and names the manual step: start the
|
|
31
|
+
* server, then either re-run this reset or fire the shared fifteen-minute
|
|
32
|
+
* cron slot directly with `pnpm devtools cron run`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function reconcileConfigAfterReset(deps?: ReconcileConfigDeps): Promise<string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Runs a stack command, returning its exit code and anything to report.
|
|
37
|
+
*
|
|
38
|
+
* `connection` is present exactly when the command writes the database
|
|
39
|
+
* (`migrate`, `reset`) — the caller (`cli.ts`'s `runStack`) resolves it,
|
|
40
|
+
* because resolving means possibly refusing with a printed reason, and the
|
|
41
|
+
* lifecycle commands must keep working with no resolvable database at all
|
|
42
|
+
* (that is what `db start` is FOR). `status` receives whatever resolved,
|
|
43
|
+
* or `null`, and degrades to machine facts.
|
|
44
|
+
*/
|
|
45
|
+
export declare function runStackCommand(command: StackCommand, connection: DbConnection | null): Promise<{
|
|
46
|
+
code: number;
|
|
47
|
+
lines: string[];
|
|
48
|
+
}>;
|
package/dist/stack.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database commands, over the session's database.
|
|
3
|
+
*
|
|
4
|
+
* Previously delegated to `@devdogsuga/supabase` package scripts by name,
|
|
5
|
+
* then to a `--target local|remote` flag; now every data command acts on the
|
|
6
|
+
* ONE database the session entered (see `db/connection.ts`), passed to the
|
|
7
|
+
* supabase CLI as an explicit `--db-url`. That uniformity is load-bearing:
|
|
8
|
+
* the CLI's own defaults disagree per subcommand (`db push` defaults to the
|
|
9
|
+
* LINKED project, `db reset` to `--local`), and `--db-url` is the one
|
|
10
|
+
* spelling that can neither fall back to `supabase link`'s ambient state
|
|
11
|
+
* nor quietly pick a different database than the session says.
|
|
12
|
+
*
|
|
13
|
+
* The stack-lifecycle commands (`start`/`stop`/`restart`) are the deliberate
|
|
14
|
+
* exception: they act on this machine's Docker containers, which no DB_URL
|
|
15
|
+
* names, and they run under any session — starting your local stack while
|
|
16
|
+
* the session targets staging is odd but harmless, and refusing it would
|
|
17
|
+
* block the one command that fixes an offline-local session.
|
|
18
|
+
*/
|
|
19
|
+
import { rmSync } from "node:fs";
|
|
20
|
+
import { writeFile } from "node:fs/promises";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { describeEnvironment, probeEnvironment } from "./environment.js";
|
|
23
|
+
import { findRepoRoot } from "./repo/root.js";
|
|
24
|
+
import { dbPush, generateTypes, seedBuckets, supabase, supabaseCapture, } from "./db/run.js";
|
|
25
|
+
import { describeDbTarget, isLocalConnection, } from "./db/connection.js";
|
|
26
|
+
import { refreshSessionEnv } from "./db/session-refresh.js";
|
|
27
|
+
import { originReachable, resolveBaseUrl } from "./cron/commands.js";
|
|
28
|
+
// Scope order, matching `db`'s subcommands in `commands.ts`: the four that
|
|
29
|
+
// act on the Supabase stack (`connect` is handled separately below — it
|
|
30
|
+
// takes a positional ref), then the two that write the Postgres database
|
|
31
|
+
// inside the session's endpoint.
|
|
32
|
+
export const STACK_COMMANDS = [
|
|
33
|
+
"start",
|
|
34
|
+
"stop",
|
|
35
|
+
"restart",
|
|
36
|
+
"status",
|
|
37
|
+
"migrate",
|
|
38
|
+
"reset",
|
|
39
|
+
];
|
|
40
|
+
// ── Implementations ──────────────────────────────────────────────────────────
|
|
41
|
+
async function startLocalStack() {
|
|
42
|
+
const code = await supabase("start");
|
|
43
|
+
if (code !== 0)
|
|
44
|
+
return code;
|
|
45
|
+
// Write the local stack's connection details so with-env can load them.
|
|
46
|
+
let env;
|
|
47
|
+
try {
|
|
48
|
+
env = await supabaseCapture("status", "-o", "env");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
await writeFile(join(findRepoRoot(), ".env.generated"), env);
|
|
54
|
+
return seedBuckets({ kind: "local" });
|
|
55
|
+
}
|
|
56
|
+
async function stopLocalStack() {
|
|
57
|
+
const code = await supabase("stop");
|
|
58
|
+
if (code !== 0)
|
|
59
|
+
return code;
|
|
60
|
+
rmSync(join(findRepoRoot(), ".env.generated"), { force: true });
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `db connect [<project-ref>]` — run `supabase link` for whoever drives the
|
|
65
|
+
* bare supabase CLI by hand. Nothing in devtools reads what it writes any
|
|
66
|
+
* more (see `db/connection.ts`'s header); it survives as a convenience, not
|
|
67
|
+
* a dependency.
|
|
68
|
+
*/
|
|
69
|
+
export async function connectRemoteProject(ref) {
|
|
70
|
+
const projectRef = ref ?? process.env.PROJECT_REF;
|
|
71
|
+
if (!projectRef) {
|
|
72
|
+
process.stderr.write("devtools db connect: no project ref given, and PROJECT_REF is not set. " +
|
|
73
|
+
"Pass one, or add PROJECT_REF to your .env file.\n");
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
return supabase("link", "--project-ref", projectRef);
|
|
77
|
+
}
|
|
78
|
+
/** The `seed buckets` shape this connection implies — `--db-url` does not
|
|
79
|
+
* exist for that subcommand, so it is the one data command still keyed on
|
|
80
|
+
* local-vs-hosted rather than on the URL itself. */
|
|
81
|
+
function bucketsShape(connection) {
|
|
82
|
+
return isLocalConnection(connection)
|
|
83
|
+
? { kind: "local" }
|
|
84
|
+
: { kind: "remote", projectRef: connection.projectRef };
|
|
85
|
+
}
|
|
86
|
+
async function pushMigrations(connection) {
|
|
87
|
+
// The bare push (shared with CI's `deploy migrate`) then the type
|
|
88
|
+
// regeneration the contributor path layers on top — see `dbPush`'s header
|
|
89
|
+
// for why the two are split.
|
|
90
|
+
const code = await dbPush(connection.dbUrl);
|
|
91
|
+
if (code !== 0)
|
|
92
|
+
return code;
|
|
93
|
+
return generateTypes(connection.dbUrl);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* ⚠️ SAFETY-CRITICAL: drops and re-migrates `connection.dbUrl`. The caller
|
|
97
|
+
* (`cli.ts`'s `runStack`) has already confirmed the session target —
|
|
98
|
+
* including the hard production gate — before this ever runs.
|
|
99
|
+
*
|
|
100
|
+
* Meetings and workshops are NOT among the tables `supabase/seed/*.sql`
|
|
101
|
+
* populates -- they come from `@devdogsuga/events` via
|
|
102
|
+
* `reconcileFromConfig`, an authenticated platform route rather than a
|
|
103
|
+
* devtools-side function this CLI can call directly (it needs the app's
|
|
104
|
+
* Drizzle client, relations and Sentry wiring, none of which belong in this
|
|
105
|
+
* package). On a local reset this calls that route itself, over the same
|
|
106
|
+
* origin `cron run` would use, once the reset and rebuild finish -- see
|
|
107
|
+
* `reconcileConfigAfterReset` for what happens when nothing is listening yet.
|
|
108
|
+
*/
|
|
109
|
+
async function reset(connection) {
|
|
110
|
+
const code = await supabase("db", "reset", "--db-url", connection.dbUrl);
|
|
111
|
+
if (code !== 0)
|
|
112
|
+
return { code, lines: [] };
|
|
113
|
+
const types = await generateTypes(connection.dbUrl);
|
|
114
|
+
if (types !== 0)
|
|
115
|
+
return { code: types, lines: [] };
|
|
116
|
+
const bucketsCode = await seedBuckets(bucketsShape(connection));
|
|
117
|
+
if (bucketsCode !== 0)
|
|
118
|
+
return { code: bucketsCode, lines: [] };
|
|
119
|
+
return {
|
|
120
|
+
code: 0,
|
|
121
|
+
lines: isLocalConnection(connection)
|
|
122
|
+
? await reconcileConfigAfterReset()
|
|
123
|
+
: [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Best-effort trigger for `GET /cron/config-reconcile` against the local
|
|
128
|
+
* platform dev server, so `db reset` leaves meetings and workshops seeded
|
|
129
|
+
* whenever that server happens to already be up -- the common case for a
|
|
130
|
+
* repeat reset during development, rather than a first clone. No
|
|
131
|
+
* `CRON_SECRET` is sent because the route itself skips auth outside a
|
|
132
|
+
* deployed `DEPLOY_ENV` (see that route's header); this is exactly the
|
|
133
|
+
* unauthenticated local request `cron run` would send.
|
|
134
|
+
*
|
|
135
|
+
* When nothing answers yet -- most likely a first-time reset, before anyone
|
|
136
|
+
* has run `pnpm --filter platform dev` -- this reports that instead of
|
|
137
|
+
* silently leaving the tables empty, and names the manual step: start the
|
|
138
|
+
* server, then either re-run this reset or fire the shared fifteen-minute
|
|
139
|
+
* cron slot directly with `pnpm devtools cron run`.
|
|
140
|
+
*/
|
|
141
|
+
export async function reconcileConfigAfterReset(deps = {
|
|
142
|
+
reachable: originReachable,
|
|
143
|
+
fetch: globalThis.fetch,
|
|
144
|
+
}) {
|
|
145
|
+
const { reachable, fetch: fetchImpl } = deps;
|
|
146
|
+
const baseUrl = resolveBaseUrl("platform", "development", undefined, {});
|
|
147
|
+
const manualStep = "Start the platform app (`pnpm --filter platform dev`) and either " +
|
|
148
|
+
"re-run `pnpm devtools db reset` or run `pnpm devtools cron run --app " +
|
|
149
|
+
"platform --cron '*/15 * * * *' --yes`.";
|
|
150
|
+
if (!(await reachable(baseUrl))) {
|
|
151
|
+
return [
|
|
152
|
+
`Meetings and workshops are not seeded yet -- nothing is listening at ${baseUrl} ` +
|
|
153
|
+
`to reconcile @devdogsuga/events into them. ${manualStep}`,
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
const url = new URL("/cron/config-reconcile", baseUrl).toString();
|
|
157
|
+
let response;
|
|
158
|
+
try {
|
|
159
|
+
response = await fetchImpl(url);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return [
|
|
163
|
+
`Config reconcile request to ${url} failed: ` +
|
|
164
|
+
`${err instanceof Error ? err.message : String(err)}. ${manualStep}`,
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
return [
|
|
169
|
+
`Config reconcile answered HTTP ${response.status} at ${url}. ${manualStep}`,
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
const body = (await response.json());
|
|
173
|
+
if (!body.success) {
|
|
174
|
+
return [
|
|
175
|
+
`Config reconcile ran but was aborted (${body.reason}) -- meetings and ` +
|
|
176
|
+
"workshops are still empty. Check the platform dev server's console.",
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
return ["Meetings and workshops reconciled from @devdogsuga/events."];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* BUG 2's fix, applied at every point `start`/`stop`/`restart` can change
|
|
183
|
+
* `.env.generated`: on success, refresh this process's entered environment
|
|
184
|
+
* (see `db/session-refresh.ts`) so the rest of the session — including a
|
|
185
|
+
* `db introspect` run right after this one — sees the stack's CURRENT
|
|
186
|
+
* connection, not whatever `process.env` held at launch. A failed stack
|
|
187
|
+
* command changed nothing on disk, so there is nothing to refresh.
|
|
188
|
+
*/
|
|
189
|
+
async function afterLocalStackChange(code) {
|
|
190
|
+
if (code !== 0)
|
|
191
|
+
return [];
|
|
192
|
+
return refreshSessionEnv();
|
|
193
|
+
}
|
|
194
|
+
// ── The local stack's lifecycle ──────────────────────────────────────────────
|
|
195
|
+
/**
|
|
196
|
+
* Stop, then start again.
|
|
197
|
+
*
|
|
198
|
+
* Two steps rather than one, because `supabase restart` does not exist. The
|
|
199
|
+
* CLI's own answer to a changed `config.toml` is a stop/start pair. Doing it
|
|
200
|
+
* here turns that into one menu entry, rather than two commands the contributor
|
|
201
|
+
* has to know to run in that order.
|
|
202
|
+
*
|
|
203
|
+
* A failed stop short-circuits. Starting a stack that never went down would
|
|
204
|
+
* report success and leave the config change unapplied, which is the one
|
|
205
|
+
* outcome worse than a visible failure.
|
|
206
|
+
*/
|
|
207
|
+
async function restartLocal() {
|
|
208
|
+
const stopCode = await stopLocalStack();
|
|
209
|
+
if (stopCode !== 0) {
|
|
210
|
+
return {
|
|
211
|
+
code: stopCode,
|
|
212
|
+
lines: [
|
|
213
|
+
"Stopping failed, so nothing was restarted. " +
|
|
214
|
+
"Scroll up for the output from the Supabase CLI.",
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const code = await startLocalStack();
|
|
219
|
+
return { code, lines: await afterLocalStackChange(code) };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* What `status` says for a local session, now that it can answer for itself.
|
|
223
|
+
*
|
|
224
|
+
* `environment.ts` already reads the two facts that question is really asking
|
|
225
|
+
* about, so this reports them and names the next step.
|
|
226
|
+
*/
|
|
227
|
+
function localStatus() {
|
|
228
|
+
const env = probeEnvironment();
|
|
229
|
+
let next;
|
|
230
|
+
if (env.docker === "no") {
|
|
231
|
+
next = "Start Docker, then `pnpm devtools db start` to bring the stack up.";
|
|
232
|
+
}
|
|
233
|
+
else if (env.stack === "yes") {
|
|
234
|
+
next = "The stack is up. `supabase status` prints its URLs and keys.";
|
|
235
|
+
}
|
|
236
|
+
else if (env.stack === "no") {
|
|
237
|
+
next = "Nothing is running. `pnpm devtools db start` starts it.";
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
next = "Could not read Docker. `supabase status` asks the stack directly.";
|
|
241
|
+
}
|
|
242
|
+
return { code: 0, lines: [describeEnvironment(env), next] };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Runs a stack command, returning its exit code and anything to report.
|
|
246
|
+
*
|
|
247
|
+
* `connection` is present exactly when the command writes the database
|
|
248
|
+
* (`migrate`, `reset`) — the caller (`cli.ts`'s `runStack`) resolves it,
|
|
249
|
+
* because resolving means possibly refusing with a printed reason, and the
|
|
250
|
+
* lifecycle commands must keep working with no resolvable database at all
|
|
251
|
+
* (that is what `db start` is FOR). `status` receives whatever resolved,
|
|
252
|
+
* or `null`, and degrades to machine facts.
|
|
253
|
+
*/
|
|
254
|
+
export async function runStackCommand(command, connection) {
|
|
255
|
+
if (command === "restart")
|
|
256
|
+
return restartLocal();
|
|
257
|
+
if (command === "stop") {
|
|
258
|
+
const code = await stopLocalStack();
|
|
259
|
+
return { code, lines: await afterLocalStackChange(code) };
|
|
260
|
+
}
|
|
261
|
+
if (command === "start") {
|
|
262
|
+
const code = await startLocalStack();
|
|
263
|
+
return { code, lines: await afterLocalStackChange(code) };
|
|
264
|
+
}
|
|
265
|
+
if (command === "status") {
|
|
266
|
+
if (connection === null || isLocalConnection(connection)) {
|
|
267
|
+
return localStatus();
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
code: 0,
|
|
271
|
+
lines: [
|
|
272
|
+
`This session targets ${describeDbTarget(connection)}` +
|
|
273
|
+
(connection.projectRef
|
|
274
|
+
? ` (project ${connection.projectRef}).`
|
|
275
|
+
: ".") +
|
|
276
|
+
" Check the Supabase dashboard for its health.",
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (connection === null) {
|
|
281
|
+
return {
|
|
282
|
+
code: 1,
|
|
283
|
+
lines: [`No database connection was resolved for \`${command}\`.`],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (command === "migrate") {
|
|
287
|
+
return { code: await pushMigrations(connection), lines: [] };
|
|
288
|
+
}
|
|
289
|
+
return reset(connection);
|
|
290
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `'ci'` in every GitHub Actions job (`CI` is set by the runner itself,
|
|
3
|
+
* before any workflow-authored env), `'local'` on a contributor's machine.
|
|
4
|
+
* One of `@devdogsuga/telemetry`'s `ENVIRONMENTS`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function devtoolsEnvironment(): "ci" | "local";
|
|
7
|
+
/**
|
|
8
|
+
* `DEVTOOLS_TELEMETRY=0` (any other value, including unset, leaves reporting
|
|
9
|
+
* on) is the only opt-out. Checked by both `initDevtoolsTelemetry` (skips
|
|
10
|
+
* `Sentry.init` outright) and `captureDevtoolsError` (so setting it mid-run,
|
|
11
|
+
* or between two invocations in the same process, still works).
|
|
12
|
+
*/
|
|
13
|
+
export declare function devtoolsTelemetryEnabled(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Initializes `@sentry/node` for this CLI process. Safe to call more than
|
|
16
|
+
* once (idempotent) and safe to call with no DSN configured (no-ops via
|
|
17
|
+
* `buildSentryOptions`, see its header).
|
|
18
|
+
*
|
|
19
|
+
* `command` becomes a `command` tag on every event this process reports, so
|
|
20
|
+
* an issue in Sentry names the subcommand it came from (`db reset`,
|
|
21
|
+
* `deploy platform`, …) without anyone opening the job log first.
|
|
22
|
+
*/
|
|
23
|
+
export declare function initDevtoolsTelemetry(command: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Reports an uncaught error from the top-level `main().catch` in `cli.ts`
|
|
26
|
+
* and `ci.ts` alike, then flushes before the process exits. Node's event
|
|
27
|
+
* loop dies with `process.exit`, taking any in-flight request to Sentry's
|
|
28
|
+
* ingest endpoint with it, so the flush must be awaited BEFORE that call —
|
|
29
|
+
* never after.
|
|
30
|
+
*
|
|
31
|
+
* A short timeout: a CLI exiting on error should not hang perceptibly longer
|
|
32
|
+
* because Sentry's ingest is slow or unreachable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function captureDevtoolsError(err: unknown): Promise<void>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* devtools' own Sentry wiring — the CLI is a consumer of
|
|
3
|
+
* `@devdogsuga/telemetry` like `apps/platform`, `apps/schedule-builder`, and
|
|
4
|
+
* `apps/sandbox`, but on `@sentry/node` rather than a framework SDK: a CLI
|
|
5
|
+
* process starts, runs one command, and exits, with none of a server's
|
|
6
|
+
* request lifecycle for a framework integration to hook into.
|
|
7
|
+
*
|
|
8
|
+
* ## Reporting is on by default
|
|
9
|
+
*
|
|
10
|
+
* Every other `*_SENTRY_DSN` in this workspace is optional-and-empty until
|
|
11
|
+
* an org exists to receive it (see `@devdogsuga/telemetry`'s no-op-without-
|
|
12
|
+
* DSN contract). `DEVTOOLS_SENTRY_DSN` follows the same contract — empty
|
|
13
|
+
* means `buildSentryOptions` returns `undefined` and `Sentry.init` never
|
|
14
|
+
* runs — but the DEFAULT here is reporting ON once a DSN exists, unlike a
|
|
15
|
+
* feature a contributor opts into. `DEVTOOLS_TELEMETRY=0` is the one escape
|
|
16
|
+
* hatch, checked before `Sentry.init` and before every capture call, so it
|
|
17
|
+
* also works as a kill switch after init already ran (a long-lived `pnpm
|
|
18
|
+
* devtools` menu session, for instance).
|
|
19
|
+
*
|
|
20
|
+
* `PLACEHOLDER_DEVTOOLS_SENTRY_DSN` is committed empty on purpose: the
|
|
21
|
+
* devtools Sentry project does not exist yet. Once it does, either replace
|
|
22
|
+
* this constant with the real ingest DSN or — preferably — set
|
|
23
|
+
* `DEVTOOLS_SENTRY_DSN` in the environment, which always wins over the
|
|
24
|
+
* placeholder. Either way, an empty result here is indistinguishable from
|
|
25
|
+
* "not configured": no init, no network call, no console spam, which is the
|
|
26
|
+
* whole point of shipping this file before the org is onboarded.
|
|
27
|
+
*/
|
|
28
|
+
import * as Sentry from "@sentry/node";
|
|
29
|
+
import { buildSentryOptions } from "@devdogsuga/telemetry";
|
|
30
|
+
// ⚠️ PLACEHOLDER — see this file's header. Leave empty until the devtools
|
|
31
|
+
// Sentry project exists.
|
|
32
|
+
const PLACEHOLDER_DEVTOOLS_SENTRY_DSN = "";
|
|
33
|
+
let initialized = false;
|
|
34
|
+
/**
|
|
35
|
+
* `'ci'` in every GitHub Actions job (`CI` is set by the runner itself,
|
|
36
|
+
* before any workflow-authored env), `'local'` on a contributor's machine.
|
|
37
|
+
* One of `@devdogsuga/telemetry`'s `ENVIRONMENTS`.
|
|
38
|
+
*/
|
|
39
|
+
export function devtoolsEnvironment() {
|
|
40
|
+
return process.env.CI ? "ci" : "local";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `DEVTOOLS_TELEMETRY=0` (any other value, including unset, leaves reporting
|
|
44
|
+
* on) is the only opt-out. Checked by both `initDevtoolsTelemetry` (skips
|
|
45
|
+
* `Sentry.init` outright) and `captureDevtoolsError` (so setting it mid-run,
|
|
46
|
+
* or between two invocations in the same process, still works).
|
|
47
|
+
*/
|
|
48
|
+
export function devtoolsTelemetryEnabled() {
|
|
49
|
+
return process.env.DEVTOOLS_TELEMETRY !== "0";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Initializes `@sentry/node` for this CLI process. Safe to call more than
|
|
53
|
+
* once (idempotent) and safe to call with no DSN configured (no-ops via
|
|
54
|
+
* `buildSentryOptions`, see its header).
|
|
55
|
+
*
|
|
56
|
+
* `command` becomes a `command` tag on every event this process reports, so
|
|
57
|
+
* an issue in Sentry names the subcommand it came from (`db reset`,
|
|
58
|
+
* `deploy platform`, …) without anyone opening the job log first.
|
|
59
|
+
*/
|
|
60
|
+
export function initDevtoolsTelemetry(command) {
|
|
61
|
+
if (initialized)
|
|
62
|
+
return;
|
|
63
|
+
initialized = true;
|
|
64
|
+
if (!devtoolsTelemetryEnabled())
|
|
65
|
+
return;
|
|
66
|
+
const dsn = process.env.DEVTOOLS_SENTRY_DSN || PLACEHOLDER_DEVTOOLS_SENTRY_DSN;
|
|
67
|
+
const options = buildSentryOptions({
|
|
68
|
+
service: "devtools",
|
|
69
|
+
environment: devtoolsEnvironment(),
|
|
70
|
+
dsn,
|
|
71
|
+
release: process.env.SENTRY_RELEASE,
|
|
72
|
+
});
|
|
73
|
+
if (!options)
|
|
74
|
+
return;
|
|
75
|
+
Sentry.init(options);
|
|
76
|
+
Sentry.getCurrentScope().setTag("command", command);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Reports an uncaught error from the top-level `main().catch` in `cli.ts`
|
|
80
|
+
* and `ci.ts` alike, then flushes before the process exits. Node's event
|
|
81
|
+
* loop dies with `process.exit`, taking any in-flight request to Sentry's
|
|
82
|
+
* ingest endpoint with it, so the flush must be awaited BEFORE that call —
|
|
83
|
+
* never after.
|
|
84
|
+
*
|
|
85
|
+
* A short timeout: a CLI exiting on error should not hang perceptibly longer
|
|
86
|
+
* because Sentry's ingest is slow or unreachable.
|
|
87
|
+
*/
|
|
88
|
+
export async function captureDevtoolsError(err) {
|
|
89
|
+
if (!devtoolsTelemetryEnabled())
|
|
90
|
+
return;
|
|
91
|
+
Sentry.captureException(err);
|
|
92
|
+
await Sentry.flush(2000);
|
|
93
|
+
}
|
package/dist/tier.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DeployEnvironment } from "@devdogsuga/env";
|
|
2
|
+
interface ResolveTierOptions {
|
|
3
|
+
/** Stderr prefix, e.g. "devtools cf preview". */
|
|
4
|
+
label?: string;
|
|
5
|
+
/** Injectable for tests; defaults to the repo's `availableTiers()`. */
|
|
6
|
+
available?: DeployEnvironment[];
|
|
7
|
+
/**
|
|
8
|
+
* The tier the process has already ENTERED, honoured when no explicit tier
|
|
9
|
+
* was passed. Injectable for tests; defaults to `process.env.DEPLOY_ENV`.
|
|
10
|
+
*
|
|
11
|
+
* The wizard enters a tier once, up front (see `menu.ts`), by loading its
|
|
12
|
+
* env and setting `DEPLOY_ENV`; a `DEPLOY_ENV=staging pnpm devtools …` on
|
|
13
|
+
* the command line does the same. Either way a command that resolves its own
|
|
14
|
+
* tier should use that answer rather than ask a question already settled for
|
|
15
|
+
* the whole session.
|
|
16
|
+
*/
|
|
17
|
+
deployEnv?: string;
|
|
18
|
+
/** Injectable for tests; defaults to `process.stdin.isTTY`. */
|
|
19
|
+
isTTY?: boolean;
|
|
20
|
+
/** Injectable for tests; defaults to a clack `select` wrapped in `unwrap`. */
|
|
21
|
+
prompt?: (message: string, choices: {
|
|
22
|
+
value: DeployEnvironment;
|
|
23
|
+
label: string;
|
|
24
|
+
hint?: string;
|
|
25
|
+
}[]) => Promise<DeployEnvironment>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The deploy tier a command runs under when `--tier` was not passed.
|
|
29
|
+
*
|
|
30
|
+
* Prompts ONLY when more than one deploy tier's file is present — the
|
|
31
|
+
* handful of contributors working on the deploy workflow — so that everyone
|
|
32
|
+
* else (just development, possibly with `.env.generated` from a running
|
|
33
|
+
* local stack) falls straight through to development with no prompt at all.
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` after reporting an error on stderr — an invalid `--tier`, or
|
|
36
|
+
* an interactive pick of a tier whose file is missing — so the caller can
|
|
37
|
+
* exit nonzero without printing a second message of its own.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveTier(given: string | undefined, message: string, opts?: ResolveTierOptions): Promise<DeployEnvironment | null>;
|
|
40
|
+
export {};
|