@devdogsuga/backstage 0.1.6 → 0.1.8
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 +4 -3
- package/dist/{catalog-Dke7bQVz.js → catalog-BWtUtxMF.js} +13 -4
- package/dist/{cli-Do2nWD7W.js → cli-D6swhEN-.js} +3 -3
- package/dist/{cli-BhZ4je2D.js → cli-J_avqnlu.js} +146 -33
- package/dist/client-A6pJcxSE.js +2 -0
- package/dist/{client-B_MY9kVI.js → client-NuoKMQmV.js} +4 -4
- package/dist/{commands-D345F-Od.js → commands-35rBJ1R0.js} +2 -2
- package/dist/{commands-zPSnOGzH.js → commands-BF5eptEi.js} +4 -4
- package/dist/{commands-Dv5TXjvb.js → commands-CQ-vbNe9.js} +2 -2
- package/dist/{commands-BQ2NIqub.js → commands-D-sNogNX.js} +5 -5
- package/dist/{commands-5Ou8xJw5.js → commands-D4FcDQuh.js} +4 -4
- package/dist/{commands-Sx1fDNNn.js → commands-DQYH_m6i.js} +3 -3
- package/dist/{commands-CLSxKz-l.js → commands-Dwr4_CSu.js} +4 -4
- package/dist/{commands-DzALVX5g.js → commands-zApyD2Rk.js} +3 -3
- package/dist/{dry-run-3IxPmCtW.js → dry-run-CFCZUpjb.js} +1 -1
- package/dist/launch.js +6 -6
- package/dist/{meetings-DfU867eV.js → meetings-CtBMT_25.js} +2 -2
- package/dist/{peers-B0KDkI6a.js → peers-u1zxe-Ub.js} +1 -1
- package/dist/{read-B-aJJewb.js → read-BLyKoe4l.js} +2 -2
- package/dist/{telemetry-Bjoz29Hl.js → telemetry-Be_lbB5c.js} +2 -2
- package/dist/telemetry.js +1 -1
- package/dist/{ui-CdKo8mLw.js → ui-Bb69ZfVv.js} +1 -1
- package/dist/{vault-CsBC-1Mq.js → vault-DFV0zjbT.js} +1 -1
- package/env.ts +16 -1
- package/package.json +6 -6
- package/dist/client-DwDGjalU.js +0 -2
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ pnpm backstage export stars --from 2026-08-17 # audited under your gh login
|
|
|
49
49
|
| `deploy plan`, `deploy migrate` | Dry-run the migrations into the job summary; apply them to `DB_URL`. |
|
|
50
50
|
| `deploy smoke --tier <t> [--app]` | Public routes answer 200, the auth redirect works, this deploy's Sentry release shows. |
|
|
51
51
|
| `deploy reconcile --tier <t>` | The platform's config reconcile, after the deploy (`CRON_SECRET`). |
|
|
52
|
+
| `deploy prune-monitors --tier <t> [--app]` | Deletes the app's Sentry Crons monitors it no longer declares (production only). |
|
|
52
53
|
| `env pull\|push\|audit --target <t>` | One env file per target, synced to Bitwarden and GitHub. `audit` lists orphans. |
|
|
53
54
|
| `planner status\|create\|reset-password\|drop` | The `migration_planner` role the preflight tier holds. |
|
|
54
55
|
| `graphics [graphic…]` | Club images from `@devdogsuga/brand`: `brand/*`, `app/*`, `event/*`. |
|
|
@@ -77,8 +78,7 @@ DevDogsUGA, as a `smoke` field on each app's entry in `workers.json`:
|
|
|
77
78
|
"protectedPath": "/console/permissions",
|
|
78
79
|
"protectedRedirectPrefix": "/auth"
|
|
79
80
|
}
|
|
80
|
-
}
|
|
81
|
-
"apps/sandbox"
|
|
81
|
+
}
|
|
82
82
|
]
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -234,7 +234,8 @@ stderr, and every confirmation needs `--yes`. Against staging or production a
|
|
|
234
234
|
command that is not read-only asks once first (`--yes` answers it).
|
|
235
235
|
|
|
236
236
|
Each command checks for the secrets it uses up front: `deploy <app>` for
|
|
237
|
-
`CLOUDFLARE_API_TOKEN`, `deploy reconcile` for `CRON_SECRET`, `
|
|
237
|
+
`CLOUDFLARE_API_TOKEN`, `deploy reconcile` for `CRON_SECRET`, `deploy
|
|
238
|
+
prune-monitors` for `SENTRY_MONITORS_TOKEN` (skipped without it), `env` for the
|
|
238
239
|
Secrets Manager token (flag, environment, then the Bitwarden vault, which `env`
|
|
239
240
|
signs in to and unlocks itself, then asking).
|
|
240
241
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as YES, D as DRY_RUN, O as JSON_FLAG, j as createCatalog, k as SCOPES, v as noteRan, w as findRepoRoot } from "./telemetry-
|
|
2
|
-
import { n as isDryRun } from "./dry-run-
|
|
3
|
-
import { o as unwrap } from "./ui-
|
|
1
|
+
import { A as YES, D as DRY_RUN, O as JSON_FLAG, j as createCatalog, k as SCOPES, v as noteRan, w as findRepoRoot } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { n as isDryRun } from "./dry-run-CFCZUpjb.js";
|
|
3
|
+
import { o as unwrap } from "./ui-Bb69ZfVv.js";
|
|
4
4
|
import { n as positionals } from "./args-Cjr_Iqts.js";
|
|
5
5
|
import { t as EXPORTS } from "./queries-DeEwfe2c.js";
|
|
6
6
|
import { r as qrCatalogOptions } from "./options-DCaqm8BI.js";
|
|
@@ -1099,7 +1099,6 @@ const deployCommand = {
|
|
|
1099
1099
|
subcommands: [
|
|
1100
1100
|
app("platform"),
|
|
1101
1101
|
app("schedule-builder"),
|
|
1102
|
-
app("sandbox"),
|
|
1103
1102
|
{
|
|
1104
1103
|
name: "write-env",
|
|
1105
1104
|
summary: "Compose .env.<DEPLOY_ENV> from the GitHub environment.",
|
|
@@ -1143,6 +1142,16 @@ const deployCommand = {
|
|
|
1143
1142
|
summary: "Run the platform's config reconcile after a deploy.",
|
|
1144
1143
|
hint: "needs CRON_SECRET",
|
|
1145
1144
|
options: [DEPLOY_TIER, SMOKE_APP]
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "prune-monitors",
|
|
1148
|
+
summary: "Delete the Sentry Crons monitors an app no longer declares.",
|
|
1149
|
+
hint: "needs SENTRY_MONITORS_TOKEN; production only",
|
|
1150
|
+
options: [
|
|
1151
|
+
DEPLOY_TIER,
|
|
1152
|
+
SMOKE_APP,
|
|
1153
|
+
DRY_RUN
|
|
1154
|
+
]
|
|
1146
1155
|
}
|
|
1147
1156
|
]
|
|
1148
1157
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as discoverRepoRoot, p as isNonInteractive } from "./telemetry-
|
|
1
|
+
import { C as discoverRepoRoot, p as isNonInteractive } from "./telemetry-Be_lbB5c.js";
|
|
2
2
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
3
|
-
import { a as explainError, i as explain, o as unwrap, t as UsageError } from "./ui-
|
|
3
|
+
import { a as explainError, i as explain, o as unwrap, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
4
4
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { confirm, log, text } from "@clack/prompts";
|
|
@@ -31,7 +31,7 @@ async function readProductionKey(key) {
|
|
|
31
31
|
const value = nonEmpty(EnvDocument.parse(text).get(key));
|
|
32
32
|
if (value) return value;
|
|
33
33
|
} catch {}
|
|
34
|
-
const { listSecrets, projectIdFor } = await import("./client-
|
|
34
|
+
const { listSecrets, projectIdFor } = await import("./client-A6pJcxSE.js");
|
|
35
35
|
const secrets = await listSecrets(await projectIdFor(PRODUCTION_PROJECT));
|
|
36
36
|
return nonEmpty(secrets.find((s) => s.key === key)?.value);
|
|
37
37
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { C as discoverRepoRoot, T as cliName, b as ownPackageDir, c as reportDevtoolsError, f as isNoEnv, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, w as findRepoRoot, x as ownVersion } from "./telemetry-
|
|
2
|
-
import { a as recordEnteredTier, f as helpPath, i as beginInvocation, l as dbPush, m as renderHelp, n as bareGroupStartPath, o as recordResolved, p as renderCommandList, r as runMenu, s as reproducibleCommand, t as catalog, u as dbPushDryRun } from "./catalog-
|
|
3
|
-
import { a as repoPeerUrl, n as loadEnv, r as loadEnvLoad, t as getEnvSync } from "./peers-
|
|
1
|
+
import { C as discoverRepoRoot, T as cliName, b as ownPackageDir, c as reportDevtoolsError, f as isNoEnv, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, w as findRepoRoot, x as ownVersion } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { a as recordEnteredTier, f as helpPath, i as beginInvocation, l as dbPush, m as renderHelp, n as bareGroupStartPath, o as recordResolved, p as renderCommandList, r as runMenu, s as reproducibleCommand, t as catalog, u as dbPushDryRun } from "./catalog-BWtUtxMF.js";
|
|
3
|
+
import { a as repoPeerUrl, n as loadEnv, r as loadEnvLoad, t as getEnvSync } from "./peers-u1zxe-Ub.js";
|
|
4
4
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
5
|
-
import { a as wouldRunLine, n as isDryRun, t as dryRunKind } from "./dry-run-
|
|
6
|
-
import { a as explainError, i as explain, n as bail, o as unwrap, r as errorMessage } from "./ui-
|
|
5
|
+
import { a as wouldRunLine, n as isDryRun, t as dryRunKind } from "./dry-run-CFCZUpjb.js";
|
|
6
|
+
import { a as explainError, i as explain, n as bail, o as unwrap, r as errorMessage } from "./ui-Bb69ZfVv.js";
|
|
7
7
|
import { n as positionals, t as flagValue } from "./args-Cjr_Iqts.js";
|
|
8
8
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
9
9
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
10
|
-
import { a as listSecrets$1, c as updateSecret, i as createSecret, o as projectIdFor, r as byKey, s as setExplicitAccessToken } from "./client-
|
|
10
|
+
import { a as listSecrets$1, c as updateSecret, i as createSecret, o as projectIdFor, r as byKey, s as setExplicitAccessToken } from "./client-NuoKMQmV.js";
|
|
11
11
|
import { register } from "node:module";
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
13
|
import { appendFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -249,16 +249,12 @@ async function importRepoTs(absolutePath) {
|
|
|
249
249
|
* Most workspace packages declare nothing, so "no env.ts found" means
|
|
250
250
|
* not-a-manifest rather than an error.
|
|
251
251
|
*
|
|
252
|
-
* ⚠️
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* `SANDBOX_PROXY_TOKEN`, minted at deploy time and therefore in no Bitwarden
|
|
259
|
-
* project by design, was reported as an orphan, i.e. as safe for the §3.6 prune
|
|
260
|
-
* path to delete. `apps/sandbox/env.ts` exists now, and states that reasoning
|
|
261
|
-
* at length.
|
|
252
|
+
* ⚠️ A Worker still needs a manifest, even though its bindings arrive as a
|
|
253
|
+
* function argument rather than `process.env`: a manifest is the only thing
|
|
254
|
+
* that routes a credential to a deployed environment and the only thing that
|
|
255
|
+
* tells `env audit` a Worker secret is supposed to be there. Without one, a
|
|
256
|
+
* minted secret (in no Bitwarden project by design) is reported as an orphan,
|
|
257
|
+
* i.e. as safe for the §3.6 prune path to delete.
|
|
262
258
|
*/
|
|
263
259
|
/**
|
|
264
260
|
* The single in-flight (or settled) load.
|
|
@@ -553,6 +549,116 @@ async function runDeployMigrate(env = process.env, push = (url) => dbPush(url, {
|
|
|
553
549
|
say(["deploy migrate: migrations applied."]);
|
|
554
550
|
}
|
|
555
551
|
//#endregion
|
|
552
|
+
//#region src/deploy/monitors.ts
|
|
553
|
+
/**
|
|
554
|
+
* `deploy prune-monitors`: deletes the Sentry Crons monitors an app no longer
|
|
555
|
+
* declares.
|
|
556
|
+
*
|
|
557
|
+
* Sentry creates a monitor the first time a job checks in, and never removes
|
|
558
|
+
* one. Rename or drop a `monitorSlug` and the old monitor keeps expecting
|
|
559
|
+
* check-ins that will never come, opening a "missed check-in" issue every
|
|
560
|
+
* period until someone deletes it by hand (PLATFORM-2: the Discord role sync
|
|
561
|
+
* folded into the fifteen-minute cron, and its ten-minute monitor alerted
|
|
562
|
+
* every ten minutes after the deploy).
|
|
563
|
+
*
|
|
564
|
+
* The declared set is every `monitorSlug` on the entries of the app's
|
|
565
|
+
* `cloudflare/scheduled.ts` exports `CRON_ROUTES` and `WORKFLOW_CRONS`. Every
|
|
566
|
+
* monitor in the app's Sentry project that is not in it, and whose slug
|
|
567
|
+
* starts with `<app>-`, is deleted. The prefix keeps a monitor someone made
|
|
568
|
+
* by hand in the Sentry UI out of reach.
|
|
569
|
+
*
|
|
570
|
+
* Delete, not disable. Sentry drops every check-in sent to a disabled monitor
|
|
571
|
+
* and never re-enables one from a check-in, so a slug that came back would
|
|
572
|
+
* report nowhere. A deleted one is recreated by the next check-in that
|
|
573
|
+
* carries its config (each `withMonitor`/`captureCheckIn` here does), and the
|
|
574
|
+
* delete renames the old slug first, so the name is free at once.
|
|
575
|
+
*
|
|
576
|
+
* Production only. Monitors belong to a project, not a tier, and staging
|
|
577
|
+
* deploys `main`, which can be ahead of production: a monitor `main` dropped
|
|
578
|
+
* still has a production job checking in to it.
|
|
579
|
+
*
|
|
580
|
+
* `SENTRY_MONITORS_TOKEN`, not `SENTRY_AUTH_TOKEN`: the deploy token is an
|
|
581
|
+
* organization token (`org:ci`), which Sentry lets neither list nor delete
|
|
582
|
+
* monitors. This one needs `alerts:write` (`alerts:read` to list), and is
|
|
583
|
+
* held in this step's `env:` alone. Absent, the step is skipped with a
|
|
584
|
+
* notice, the same as the Sentry release check.
|
|
585
|
+
*/
|
|
586
|
+
const SENTRY_API$1 = "https://sentry.io/api/0";
|
|
587
|
+
/** Only the field read here; the exports' own shapes are devtools' to check. */
|
|
588
|
+
const Declarations = z.record(z.string(), z.object({ monitorSlug: z.string().min(1).optional() }));
|
|
589
|
+
const MonitorList = z.array(z.object({ slug: z.string() }));
|
|
590
|
+
/**
|
|
591
|
+
* Every `monitorSlug` the app's `cloudflare/scheduled.ts` declares, or
|
|
592
|
+
* `undefined` when the app has no such file (no crons, nothing to prune).
|
|
593
|
+
*/
|
|
594
|
+
async function declaredMonitorSlugs(app, root = findRepoRoot()) {
|
|
595
|
+
const path = join(root, "apps", app, "cloudflare", "scheduled.ts");
|
|
596
|
+
if (!existsSync(path)) return void 0;
|
|
597
|
+
const mod = await import(pathToFileURL(path).href);
|
|
598
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
599
|
+
for (const name of ["CRON_ROUTES", "WORKFLOW_CRONS"]) {
|
|
600
|
+
const parsed = Declarations.safeParse(mod[name] ?? {});
|
|
601
|
+
if (!parsed.success) throw new DeployError(`${path}: ${name} is not a map of entries.`);
|
|
602
|
+
for (const entry of Object.values(parsed.data)) if (entry.monitorSlug) slugs.add(entry.monitorSlug);
|
|
603
|
+
}
|
|
604
|
+
return slugs;
|
|
605
|
+
}
|
|
606
|
+
/** The `rel="next"` URL of a Sentry `Link` header, when it has results. */
|
|
607
|
+
function nextPage(link) {
|
|
608
|
+
for (const part of link?.split(",") ?? []) if (part.includes("rel=\"next\"") && part.includes("results=\"true\"")) return /<([^>]+)>/.exec(part)?.[1];
|
|
609
|
+
}
|
|
610
|
+
async function listMonitorSlugs(org, project, headers, fetchImpl) {
|
|
611
|
+
const slugs = [];
|
|
612
|
+
let url = `${SENTRY_API$1}/organizations/${org}/monitors/?project=${encodeURIComponent(project)}`;
|
|
613
|
+
while (url) {
|
|
614
|
+
const response = await fetchImpl(url, { headers });
|
|
615
|
+
if (!response.ok) throw new DeployError(`Listing Sentry monitors: HTTP ${response.status}.`, ["SENTRY_MONITORS_TOKEN needs alerts:read and alerts:write."]);
|
|
616
|
+
slugs.push(...MonitorList.parse(await response.json()).map((m) => m.slug));
|
|
617
|
+
url = nextPage(response.headers.get("link"));
|
|
618
|
+
}
|
|
619
|
+
return slugs;
|
|
620
|
+
}
|
|
621
|
+
/** Returns the slugs it deleted (or would have, on a dry run). */
|
|
622
|
+
async function runPruneMonitors(options) {
|
|
623
|
+
const env = options.env ?? process.env;
|
|
624
|
+
const { app } = options;
|
|
625
|
+
if (options.tier !== "production") {
|
|
626
|
+
say([`prune-monitors: skipped on ${options.tier}; monitors are pruned against production only.`]);
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
const token = env.SENTRY_MONITORS_TOKEN;
|
|
630
|
+
if (!token) {
|
|
631
|
+
say(["::notice::Sentry monitor pruning skipped -- SENTRY_MONITORS_TOKEN is not configured."]);
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
if (!env.SENTRY_ORG) throw new DeployError("SENTRY_ORG is not set.");
|
|
635
|
+
const declared = options.declared ?? await declaredMonitorSlugs(app);
|
|
636
|
+
if (!declared) {
|
|
637
|
+
say([`prune-monitors: ${app} has no cloudflare/scheduled.ts; nothing to prune.`]);
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
641
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
642
|
+
const orphans = (await listMonitorSlugs(env.SENTRY_ORG, app, headers, fetchImpl)).filter((slug) => slug.startsWith(`${app}-`) && !declared.has(slug));
|
|
643
|
+
if (orphans.length === 0) {
|
|
644
|
+
say([`prune-monitors: every ${app} monitor is declared.`]);
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
for (const slug of orphans) {
|
|
648
|
+
if (options.dryRun) {
|
|
649
|
+
say([`Would delete Sentry monitor ${slug}`]);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const response = await fetchImpl(`${SENTRY_API$1}/projects/${env.SENTRY_ORG}/${app}/monitors/${encodeURIComponent(slug)}/`, {
|
|
653
|
+
method: "DELETE",
|
|
654
|
+
headers
|
|
655
|
+
});
|
|
656
|
+
if (!response.ok && response.status !== 404) throw new DeployError(`Deleting Sentry monitor ${slug}: HTTP ${response.status}.`);
|
|
657
|
+
say([`Deleted Sentry monitor ${slug}`]);
|
|
658
|
+
}
|
|
659
|
+
return orphans;
|
|
660
|
+
}
|
|
661
|
+
//#endregion
|
|
556
662
|
//#region src/deploy/preflight.ts
|
|
557
663
|
/**
|
|
558
664
|
* `pnpm devtools deploy preflight`
|
|
@@ -737,8 +843,7 @@ async function runPreflight(deps = {}) {
|
|
|
737
843
|
* ## Minted keys are refused, not filled
|
|
738
844
|
*
|
|
739
845
|
* A `secrecy: "secret"` key `storableKeys()` excludes is "minted": signed at
|
|
740
|
-
* deploy time rather than stored anywhere
|
|
741
|
-
* is declared as. This has no minter of its own — there is nothing in
|
|
846
|
+
* deploy time rather than stored anywhere. This has no minter of its own — there is nothing in
|
|
742
847
|
* this repository that produces one — so a declared minted key is always a
|
|
743
848
|
* hard failure here rather than a silent omission: a Worker that expects a
|
|
744
849
|
* value substituted fails loudly at the file it never got, not at whatever
|
|
@@ -1551,13 +1656,13 @@ function renderWriteEnvReport(result) {
|
|
|
1551
1656
|
* which leaves a Wrangler "config redirect" at `.wrangler/deploy/config.json`
|
|
1552
1657
|
* pointing at the generated `dist/server/wrangler.json`; a bare `wrangler
|
|
1553
1658
|
* deploy` from the app dir resolves through it with no `--config` needed.
|
|
1554
|
-
*
|
|
1555
|
-
* deploy step.
|
|
1659
|
+
* Both apps share one deploy step.
|
|
1556
1660
|
*
|
|
1557
|
-
* Steps (`write-env`, `preflight`, `plan`, `migrate`, `smoke`, `reconcile
|
|
1558
|
-
* individually addressable for jobs that run only one.
|
|
1559
|
-
* single credential in the job's own `env:` block and
|
|
1560
|
-
* (`preflight`, `plan`, `migrate`, `smoke`, `reconcile
|
|
1661
|
+
* Steps (`write-env`, `preflight`, `plan`, `migrate`, `smoke`, `reconcile`,
|
|
1662
|
+
* `prune-monitors`) are individually addressable for jobs that run only one.
|
|
1663
|
+
* The ones that hold a single credential in the job's own `env:` block and
|
|
1664
|
+
* compose no env file (`preflight`, `plan`, `migrate`, `smoke`, `reconcile`,
|
|
1665
|
+
* `prune-monitors`), and `write-env`,
|
|
1561
1666
|
* which CREATES the file tier resolution would otherwise insist on reading,
|
|
1562
1667
|
* run with `--no-env`.
|
|
1563
1668
|
*/
|
|
@@ -1707,6 +1812,14 @@ async function runStep(sub, rest) {
|
|
|
1707
1812
|
});
|
|
1708
1813
|
return true;
|
|
1709
1814
|
}
|
|
1815
|
+
if (sub === "prune-monitors") {
|
|
1816
|
+
await runPruneMonitors({
|
|
1817
|
+
app: flagValue(rest, "--app") ?? "platform",
|
|
1818
|
+
tier: requireTier(rest),
|
|
1819
|
+
dryRun: rest.includes("--dry-run")
|
|
1820
|
+
});
|
|
1821
|
+
return true;
|
|
1822
|
+
}
|
|
1710
1823
|
if (sub === "write-env") {
|
|
1711
1824
|
await loadRegistry();
|
|
1712
1825
|
const index = rest.indexOf("--source");
|
|
@@ -2619,9 +2732,9 @@ async function listWorkerSecrets(target) {
|
|
|
2619
2732
|
*
|
|
2620
2733
|
* The expected set is derived from the app's own manifest: every key it
|
|
2621
2734
|
* declares with `secrecy: "secret"`. That deliberately includes the MINTED
|
|
2622
|
-
* ones.
|
|
2623
|
-
*
|
|
2624
|
-
*
|
|
2735
|
+
* ones. A minted key has no copy in Bitwarden or GitHub by design, and an
|
|
2736
|
+
* audit that reasoned from stored copies alone would report a live credential
|
|
2737
|
+
* as safe to delete. (`env audit` had exactly that bug; see
|
|
2625
2738
|
* `EnvMeta.minted`.)
|
|
2626
2739
|
*
|
|
2627
2740
|
* ## Report always, prune almost never (security plan §3.6)
|
|
@@ -5267,12 +5380,12 @@ const HANDLERS = {
|
|
|
5267
5380
|
},
|
|
5268
5381
|
completions: handleCompletions,
|
|
5269
5382
|
github: handleGithub,
|
|
5270
|
-
graphics: async (rest) => (await import("./commands-
|
|
5271
|
-
qr: async (rest) => (await import("./commands-
|
|
5272
|
-
newsletter: async (rest) => (await import("./commands-
|
|
5273
|
-
creds: async (rest) => (await import("./commands-
|
|
5274
|
-
import: async (rest) => (await import("./commands-
|
|
5275
|
-
export: async (rest) => (await import("./commands-
|
|
5383
|
+
graphics: async (rest) => (await import("./commands-CQ-vbNe9.js")).handleGraphics(rest),
|
|
5384
|
+
qr: async (rest) => (await import("./commands-DQYH_m6i.js")).handleQr(rest),
|
|
5385
|
+
newsletter: async (rest) => (await import("./commands-35rBJ1R0.js")).handleNewsletter(rest),
|
|
5386
|
+
creds: async (rest) => (await import("./commands-BF5eptEi.js")).handleCreds(rest),
|
|
5387
|
+
import: async (rest) => (await import("./commands-zApyD2Rk.js")).handleImport(rest),
|
|
5388
|
+
export: async (rest) => (await import("./commands-D4FcDQuh.js")).handleExport(rest)
|
|
5276
5389
|
};
|
|
5277
5390
|
/**
|
|
5278
5391
|
* Names that were commands of the contributor CLI, refused with where they
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { w as findRepoRoot } from "./telemetry-
|
|
2
|
-
import { n as loadEnv } from "./peers-
|
|
3
|
-
import { o as unwrap } from "./ui-
|
|
1
|
+
import { w as findRepoRoot } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { n as loadEnv } from "./peers-u1zxe-Ub.js";
|
|
3
|
+
import { o as unwrap } from "./ui-Bb69ZfVv.js";
|
|
4
4
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
5
|
-
import { a as readTokenFromVault, o as saveTokenToVault, t as VAULT_ITEM_NAME } from "./vault-
|
|
5
|
+
import { a as readTokenFromVault, o as saveTokenToVault, t as VAULT_ITEM_NAME } from "./vault-DFV0zjbT.js";
|
|
6
6
|
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { log, password, select, text } from "@clack/prompts";
|
|
8
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as isNonInteractive } from "./telemetry-
|
|
2
|
-
import { a as explainError, o as unwrap, r as errorMessage, t as UsageError } from "./ui-
|
|
1
|
+
import { p as isNonInteractive } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { a as explainError, o as unwrap, r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
3
3
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { confirm, log, multiselect, spinner, text } from "@clack/prompts";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as discoverRepoRoot, p as isNonInteractive } from "./telemetry-
|
|
1
|
+
import { C as discoverRepoRoot, p as isNonInteractive } from "./telemetry-Be_lbB5c.js";
|
|
2
2
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
3
|
-
import { a as explainError, i as explain, o as unwrap, r as errorMessage, t as UsageError } from "./ui-
|
|
3
|
+
import { a as explainError, i as explain, o as unwrap, r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
4
4
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
5
5
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
6
|
-
import { i as readPasswordFromVault, n as bwArgs, r as openVault, s as bwCommand } from "./vault-
|
|
6
|
+
import { i as readPasswordFromVault, n as bwArgs, r as openVault, s as bwCommand } from "./vault-DFV0zjbT.js";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { confirm, log, multiselect, note, password, select, text } from "@clack/prompts";
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
@@ -632,7 +632,7 @@ async function readCheckoutDbUrl() {
|
|
|
632
632
|
}
|
|
633
633
|
}
|
|
634
634
|
async function readSecretsManagerDbUrl() {
|
|
635
|
-
const { listSecrets, projectIdFor } = await import("./client-
|
|
635
|
+
const { listSecrets, projectIdFor } = await import("./client-A6pJcxSE.js");
|
|
636
636
|
const secrets = await listSecrets(await projectIdFor(PRODUCTION_PROJECT));
|
|
637
637
|
return nonEmpty(secrets.find((s) => s.key === "DB_URL")?.value);
|
|
638
638
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as isNonInteractive } from "./telemetry-
|
|
2
|
-
import { a as explainError, o as unwrap, r as errorMessage, t as UsageError } from "./ui-
|
|
1
|
+
import { p as isNonInteractive } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { a as explainError, o as unwrap, r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
3
3
|
import { n as positionals } from "./args-Cjr_Iqts.js";
|
|
4
4
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
5
5
|
import { dirname, relative, resolve } from "node:path";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as isDryRun } from "./dry-run-
|
|
2
|
-
import { o as unwrap, r as errorMessage, t as UsageError } from "./ui-
|
|
1
|
+
import { n as isDryRun } from "./dry-run-CFCZUpjb.js";
|
|
2
|
+
import { o as unwrap, r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
3
3
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
4
|
-
import { a as reportFailure, c as connect, d as withConnection, i as readInputFile, n as confirmWrite, o as say, r as plural, s as ProductionError, t as canPrompt, u as requireProductionKey } from "./cli-
|
|
5
|
-
import { a as createAccounts, i as createAccountFor, n as accountFinder, r as accountsWith, t as parseCsv } from "./read-
|
|
6
|
-
import { n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-
|
|
4
|
+
import { a as reportFailure, c as connect, d as withConnection, i as readInputFile, n as confirmWrite, o as say, r as plural, s as ProductionError, t as canPrompt, u as requireProductionKey } from "./cli-D6swhEN-.js";
|
|
5
|
+
import { a as createAccounts, i as createAccountFor, n as accountFinder, r as accountsWith, t as parseCsv } from "./read-BLyKoe4l.js";
|
|
6
|
+
import { n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-CtBMT_25.js";
|
|
7
7
|
import { text } from "@clack/prompts";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
//#region src/attendance/plan.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as isDryRun } from "./dry-run-
|
|
2
|
-
import { r as errorMessage, t as UsageError } from "./ui-
|
|
1
|
+
import { n as isDryRun } from "./dry-run-CFCZUpjb.js";
|
|
2
|
+
import { r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
3
3
|
import { i as exportParams, n as EXPORT_KINDS, r as auditFilters, t as EXPORTS } from "./queries-DeEwfe2c.js";
|
|
4
4
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
5
|
-
import { a as reportFailure, c as connect, l as dbError, o as say, r as plural, s as ProductionError, u as requireProductionKey } from "./cli-
|
|
6
|
-
import { i as parseBound, n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-
|
|
5
|
+
import { a as reportFailure, c as connect, l as dbError, o as say, r as plural, s as ProductionError, u as requireProductionKey } from "./cli-D6swhEN-.js";
|
|
6
|
+
import { i as parseBound, n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-CtBMT_25.js";
|
|
7
7
|
import { open, rm } from "node:fs/promises";
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
9
|
import { parseArgs, promisify } from "node:util";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { p as isNonInteractive } from "./telemetry-
|
|
2
|
-
import { n as isDryRun } from "./dry-run-
|
|
3
|
-
import { a as explainError, o as unwrap, t as UsageError } from "./ui-
|
|
1
|
+
import { p as isNonInteractive } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { n as isDryRun } from "./dry-run-CFCZUpjb.js";
|
|
3
|
+
import { a as explainError, o as unwrap, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
4
4
|
import { i as qrParseOptions, n as QR_FLAGS, t as QR_EXTRA_FLAGS } from "./options-DCaqm8BI.js";
|
|
5
5
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
6
6
|
import { dirname, extname, relative, resolve } from "node:path";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { n as isDryRun } from "./dry-run-
|
|
2
|
-
import { r as errorMessage, t as UsageError } from "./ui-
|
|
1
|
+
import { n as isDryRun } from "./dry-run-CFCZUpjb.js";
|
|
2
|
+
import { r as errorMessage, t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
3
3
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
4
|
-
import { a as reportFailure, d as withConnection, i as readInputFile, n as confirmWrite, o as say, r as plural, t as canPrompt, u as requireProductionKey } from "./cli-
|
|
5
|
-
import { a as createAccounts, i as createAccountFor, n as accountFinder, o as readAccounts, t as parseCsv } from "./read-
|
|
4
|
+
import { a as reportFailure, d as withConnection, i as readInputFile, n as confirmWrite, o as say, r as plural, t as canPrompt, u as requireProductionKey } from "./cli-D6swhEN-.js";
|
|
5
|
+
import { a as createAccounts, i as createAccountFor, n as accountFinder, o as readAccounts, t as parseCsv } from "./read-BLyKoe4l.js";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
//#region src/involvement/csv.ts
|
|
8
8
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { i as explain } from "./ui-
|
|
1
|
+
import { i as explain } from "./ui-Bb69ZfVv.js";
|
|
2
2
|
//#region src/import/commands.ts
|
|
3
3
|
const handleImport = async (rest) => {
|
|
4
4
|
const [what, ...args] = rest;
|
|
5
|
-
if (what === "involvement") return (await import("./commands-
|
|
6
|
-
if (what === "attendance") return (await import("./commands-
|
|
5
|
+
if (what === "involvement") return (await import("./commands-Dwr4_CSu.js")).handleInvolvement(args);
|
|
6
|
+
if (what === "attendance") return (await import("./commands-D-sNogNX.js")).handleAttendance(args);
|
|
7
7
|
explain(what ? `Unknown import "${what}". Try involvement or attendance.` : "Name the import: involvement or attendance.", "");
|
|
8
8
|
process.exitCode = 1;
|
|
9
9
|
return null;
|
package/dist/launch.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as discoverRepoRoot, E as setCliName, S as RepoNotFoundError, T as cliName, _ as installFailureLog, d as hasYes, g as stripTierFlag, h as stripNoEnvFlag, m as setNoEnv, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, s as lastSentryEventId } from "./telemetry-
|
|
2
|
-
import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-
|
|
3
|
-
import { i as loadEnvSession, r as loadEnvLoad } from "./peers-
|
|
1
|
+
import { C as discoverRepoRoot, E as setCliName, S as RepoNotFoundError, T as cliName, _ as installFailureLog, d as hasYes, g as stripTierFlag, h as stripNoEnvFlag, m as setNoEnv, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, s as lastSentryEventId } from "./telemetry-Be_lbB5c.js";
|
|
2
|
+
import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-BWtUtxMF.js";
|
|
3
|
+
import { i as loadEnvSession, r as loadEnvLoad } from "./peers-u1zxe-Ub.js";
|
|
4
4
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
5
|
-
import { i as setDryRun, n as isDryRun, r as resolveDryRun } from "./dry-run-
|
|
6
|
-
import { o as unwrap, r as errorMessage } from "./ui-
|
|
5
|
+
import { i as setDryRun, n as isDryRun, r as resolveDryRun } from "./dry-run-CFCZUpjb.js";
|
|
6
|
+
import { o as unwrap, r as errorMessage } from "./ui-Bb69ZfVv.js";
|
|
7
7
|
import { confirm } from "@clack/prompts";
|
|
8
8
|
//#region ../cli-core/src/pipes.ts
|
|
9
9
|
/**
|
|
@@ -322,7 +322,7 @@ async function launchWith(argv, options) {
|
|
|
322
322
|
*/
|
|
323
323
|
async function launch(argv) {
|
|
324
324
|
await launchWith(argv, { dispatch: async (args) => {
|
|
325
|
-
const { main } = await import("./cli-
|
|
325
|
+
const { main } = await import("./cli-J_avqnlu.js");
|
|
326
326
|
await main(args);
|
|
327
327
|
} });
|
|
328
328
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as UsageError } from "./ui-
|
|
2
|
-
import "./cli-
|
|
1
|
+
import { t as UsageError } from "./ui-Bb69ZfVv.js";
|
|
2
|
+
import "./cli-D6swhEN-.js";
|
|
3
3
|
//#region src/production/time.ts
|
|
4
4
|
/**
|
|
5
5
|
* Dates as officers type them. Meetings happen in Athens, so a bare
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as RepoNotFoundError, w as findRepoRoot } from "./telemetry-
|
|
1
|
+
import { S as RepoNotFoundError, w as findRepoRoot } from "./telemetry-Be_lbB5c.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as errorMessage } from "./ui-
|
|
2
|
-
import { d as withConnection, s as ProductionError } from "./cli-
|
|
1
|
+
import { r as errorMessage } from "./ui-Bb69ZfVv.js";
|
|
2
|
+
import { d as withConnection, s as ProductionError } from "./cli-D6swhEN-.js";
|
|
3
3
|
//#region src/production/accounts.ts
|
|
4
4
|
/**
|
|
5
5
|
* Production accounts: reading them, finding one by email, and creating the
|
|
@@ -438,8 +438,8 @@ function isNoEnv() {
|
|
|
438
438
|
//#region ../cli-core/src/telemetry.ts
|
|
439
439
|
/**
|
|
440
440
|
* devtools' own Sentry wiring — the CLI is a consumer of
|
|
441
|
-
* `@devdogsuga/telemetry` like `apps/platform
|
|
442
|
-
*
|
|
441
|
+
* `@devdogsuga/telemetry` like `apps/platform` and `apps/schedule-builder`,
|
|
442
|
+
* but on `@sentry/node` rather than a framework SDK: a CLI
|
|
443
443
|
* process starts, runs one command, and exits, with none of a server's
|
|
444
444
|
* request lifecycle for a framework integration to hook into.
|
|
445
445
|
*
|
package/dist/telemetry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as hasRealCaller, c as reportDevtoolsError, i as devtoolsTelemetryEnabled, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as lastSentryEventId, t as captureDevtoolsDeprecation, u as resolveDevtoolsDsn } from "./telemetry-
|
|
1
|
+
import { a as hasRealCaller, c as reportDevtoolsError, i as devtoolsTelemetryEnabled, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as lastSentryEventId, t as captureDevtoolsDeprecation, u as resolveDevtoolsDsn } from "./telemetry-Be_lbB5c.js";
|
|
2
2
|
export { captureDevtoolsDeprecation, captureDevtoolsError, devtoolsEnvironment, devtoolsTelemetryEnabled, hasRealCaller, initDevtoolsTelemetry, lastSentryEventId, reportDevtoolsError, reportDevtoolsFailure, resolveDevtoolsDsn };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as reportDevtoolsError, p as isNonInteractive, y as suppressFailureLog } from "./telemetry-
|
|
1
|
+
import { c as reportDevtoolsError, p as isNonInteractive, y as suppressFailureLog } from "./telemetry-Be_lbB5c.js";
|
|
2
2
|
import { cancel, isCancel, log, note } from "@clack/prompts";
|
|
3
3
|
//#region ../cli-core/src/ui.ts
|
|
4
4
|
/**
|
package/env.ts
CHANGED
|
@@ -96,7 +96,22 @@ declare({
|
|
|
96
96
|
doc:
|
|
97
97
|
"A Sentry organization auth token. Deploys use it to create each " +
|
|
98
98
|
"release and upload its source maps, and the smoke test uses it to " +
|
|
99
|
-
"
|
|
99
|
+
"confirm the release landed. One token serves every environment.",
|
|
100
|
+
scope: "environment",
|
|
101
|
+
secrecy: "secret",
|
|
102
|
+
commented: true,
|
|
103
|
+
}),
|
|
104
|
+
// Read only by `deploy prune-monitors`, in its own step's `env:`: an
|
|
105
|
+
// organization token (above) can neither list nor delete Crons monitors,
|
|
106
|
+
// and one that can delete monitors and alert rules org-wide should not
|
|
107
|
+
// sit in every step of the deploy job. Without it, pruning is skipped
|
|
108
|
+
// with a notice.
|
|
109
|
+
SENTRY_MONITORS_TOKEN: define(z.string().min(1).optional(), {
|
|
110
|
+
doc:
|
|
111
|
+
"A Sentry internal integration token with Alerts: Read & Write and " +
|
|
112
|
+
"nothing else. The production deploy uses it to delete the Crons " +
|
|
113
|
+
"monitors an app no longer declares, which would otherwise alert " +
|
|
114
|
+
"on missed check-ins forever.",
|
|
100
115
|
scope: "environment",
|
|
101
116
|
secrecy: "secret",
|
|
102
117
|
commented: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devdogsuga/backstage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Officer and production CLI for DevDogsUGA: deploys, env sync with Bitwarden and GitHub, the migration planner role, club graphics and QR codes, GitHub rulesets and settings, and the newsletter. Ships built JS; run it anywhere with `pnpm dlx @devdogsuga/backstage` (no checkout needed to start; commands that read a checkout say so).",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"tsx": "^4.23.12",
|
|
41
41
|
"typescript": "^6.0.3",
|
|
42
42
|
"zod": "4.4.3",
|
|
43
|
-
"@devdogsuga/
|
|
43
|
+
"@devdogsuga/events": "0.1.5",
|
|
44
44
|
"@devdogsuga/newsletter": "0.1.10",
|
|
45
|
-
"@devdogsuga/
|
|
46
|
-
"@devdogsuga/
|
|
45
|
+
"@devdogsuga/brand": "0.1.5",
|
|
46
|
+
"@devdogsuga/telemetry": "0.1.4"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@devdogsuga/db": "*",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"vitest": "^4.1.11",
|
|
67
67
|
"@devdogsuga/config": "0.1.2",
|
|
68
68
|
"@devdogsuga/db": "0.1.3",
|
|
69
|
-
"@devdogsuga/
|
|
70
|
-
"@devdogsuga/
|
|
69
|
+
"@devdogsuga/cli-core": "0.0.0",
|
|
70
|
+
"@devdogsuga/env": "0.1.5"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsdown && node scripts/write-build-info.mjs && node ../../scripts/check-bundle-imports.mjs",
|
package/dist/client-DwDGjalU.js
DELETED