@devdogsuga/backstage 0.1.4 → 0.1.6
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 +90 -23
- package/dist/{catalog-BuNLUu08.js → catalog-Dke7bQVz.js} +131 -8
- package/dist/{cli-BI5Vn9sR.js → cli-BhZ4je2D.js} +9 -7
- package/dist/cli-Do2nWD7W.js +136 -0
- package/dist/{client-B7HhuTaA.js → client-B_MY9kVI.js} +2 -1
- package/dist/client-DwDGjalU.js +2 -0
- package/dist/commands-5Ou8xJw5.js +225 -0
- package/dist/commands-BQ2NIqub.js +436 -0
- package/dist/commands-CLSxKz-l.js +352 -0
- package/dist/commands-DzALVX5g.js +12 -0
- package/dist/{commands-Bk7IbhPD.js → commands-Sx1fDNNn.js} +2 -1
- package/dist/{commands-Dl3UlhBf.js → commands-zPSnOGzH.js} +86 -46
- package/dist/document-DumoZXoO.js +287 -0
- package/dist/dry-run-3IxPmCtW.js +58 -0
- package/dist/launch.js +3 -3
- package/dist/meetings-DfU867eV.js +118 -0
- package/dist/{options-BTjOf5KP.js → options-DCaqm8BI.js} +1 -57
- package/dist/queries-DeEwfe2c.js +170 -0
- package/dist/read-B-aJJewb.js +141 -0
- package/dist/{vault-C8LYJFOQ.js → vault-CsBC-1Mq.js} +1 -287
- package/package.json +7 -7
- package/dist/client-DzXQHd58.js +0 -2
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ match. The two flags matter outside a DevDogsUGA checkout, where the workspace's
|
|
|
14
14
|
`pnpm backstage` is the script).
|
|
15
15
|
|
|
16
16
|
It **starts without a checkout**. Help, `version`, `completions`, the tools
|
|
17
|
-
below that need no secrets (`graphics`, `qr`, `github`, `newsletter`, `creds`)
|
|
17
|
+
below that need no secrets (`graphics`, `qr`, `github`, `newsletter`, `creds`),
|
|
18
|
+
`import`, `export` and
|
|
18
19
|
anything run with `--no-env` never look for one; a command that reads a checkout says
|
|
19
20
|
"run this from inside a DevDogsUGA clone" and exits 1. The DevDogsUGA libraries
|
|
20
21
|
it reads (`@devdogsuga/env`, `@devdogsuga/db`) are optional peers, resolved
|
|
@@ -33,25 +34,31 @@ pnpm backstage qr https://devdogsuga.org --format svg,png,webp --logo acm
|
|
|
33
34
|
pnpm backstage newsletter send 3.0.1 --to a@uga.edu # asks first; --yes with no terminal
|
|
34
35
|
pnpm backstage creds # share a club login with officers, as a Bitwarden Send
|
|
35
36
|
pnpm backstage creds renew # extend every Send 30 days
|
|
37
|
+
pnpm backstage import involvement --file OrganizationRoster.csv # verify members; previews, then asks
|
|
38
|
+
pnpm backstage import attendance --meeting 2026-09-09 --file sign-in.csv
|
|
39
|
+
pnpm backstage export stars --from 2026-08-17 # audited under your gh login
|
|
36
40
|
```
|
|
37
41
|
|
|
38
42
|
## Commands
|
|
39
43
|
|
|
40
|
-
| Command
|
|
41
|
-
|
|
|
42
|
-
| `deploy <app> --tier <t>`
|
|
43
|
-
| `deploy write-env`
|
|
44
|
-
| `deploy preflight`
|
|
45
|
-
| `deploy plan`, `deploy migrate`
|
|
46
|
-
| `deploy smoke --tier <t> [--app]`
|
|
47
|
-
| `deploy reconcile --tier <t>`
|
|
48
|
-
| `env pull\|push\|audit --target <t>`
|
|
49
|
-
| `planner status\|create\|reset-password\|drop`
|
|
50
|
-
| `graphics [graphic…]`
|
|
51
|
-
| `qr <text>`
|
|
52
|
-
| `github rulesets\|settings`
|
|
53
|
-
| `newsletter render\|draft\|send <issue…>`
|
|
54
|
-
| `creds send\|add\|renew\|list\|report`
|
|
44
|
+
| Command | What it does |
|
|
45
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
46
|
+
| `deploy <app> --tier <t>` | Checks `CLOUDFLARE_API_TOKEN`, writes the Worker's secrets file, deploys, removes it. |
|
|
47
|
+
| `deploy write-env` | Composes `.env.<DEPLOY_ENV>` from the GitHub environment. |
|
|
48
|
+
| `deploy preflight` | Classifies the project: paused (skip) or broken (fail). |
|
|
49
|
+
| `deploy plan`, `deploy migrate` | Dry-run the migrations into the job summary; apply them to `DB_URL`. |
|
|
50
|
+
| `deploy smoke --tier <t> [--app]` | Public routes answer 200, the auth redirect works, this deploy's Sentry release shows. |
|
|
51
|
+
| `deploy reconcile --tier <t>` | The platform's config reconcile, after the deploy (`CRON_SECRET`). |
|
|
52
|
+
| `env pull\|push\|audit --target <t>` | One env file per target, synced to Bitwarden and GitHub. `audit` lists orphans. |
|
|
53
|
+
| `planner status\|create\|reset-password\|drop` | The `migration_planner` role the preflight tier holds. |
|
|
54
|
+
| `graphics [graphic…]` | Club images from `@devdogsuga/brand`: `brand/*`, `app/*`, `event/*`. |
|
|
55
|
+
| `qr <text>` | QR codes with every option of `/console/qr`. |
|
|
56
|
+
| `github rulesets\|settings` | Diff (and with `--apply` write) GitHub config, through `gh`. |
|
|
57
|
+
| `newsletter render\|draft\|send <issue…>` | Changelog issues as files, mailbox drafts, or a send. |
|
|
58
|
+
| `creds send\|add\|renew\|list\|report` | Club logins from Bitwarden as email-verified Sends, and the Linear report. |
|
|
59
|
+
| `import involvement --file <csv>` | Verifies the members on the Involvement Network roster, unverifies everyone else. |
|
|
60
|
+
| `import attendance --meeting <day> --file <csv>` | Records a meeting's attendance from a sign-in sheet (method `import`). |
|
|
61
|
+
| `export stars\|attendance\|reflections` | Member data as CSV, each export audited under the officer's gh login. |
|
|
55
62
|
|
|
56
63
|
`smoke` and `reconcile` replace DevDogsUGA's `packages/deploy-checks`. The
|
|
57
64
|
per-app data (hosts, public paths, the protected path and its redirect) stays in
|
|
@@ -112,8 +119,10 @@ entry for them (`envFree` in the command tree).
|
|
|
112
119
|
## Shared logins: `creds`
|
|
113
120
|
|
|
114
121
|
The club's shared logins (Instagram, Canva, ArchPass, Linktree, later project
|
|
115
|
-
API keys) live
|
|
116
|
-
|
|
122
|
+
API keys) live in the DevDogs Bitwarden organization, in whatever collection
|
|
123
|
+
suits them. `creds` hands any login or secure note there to officers as a
|
|
124
|
+
Bitwarden Send. Personal items, other organizations' items and production
|
|
125
|
+
secrets (which are in Secrets Manager, not the vault) are out of its reach.
|
|
117
126
|
|
|
118
127
|
- **`send`** picks items and recipients (officers by name or by role, read live
|
|
119
128
|
from production: everyone holding a role other than `Member`, by UGA MyID
|
|
@@ -122,16 +131,18 @@ Bitwarden organization. `creds` hands them to officers as Bitwarden Sends:
|
|
|
122
131
|
recipients and deleted 30 days out. Removing an address revokes it. Addresses
|
|
123
132
|
off the roster need `--allow-email`. Non-interactive: `--item`, `--to`,
|
|
124
133
|
`--role`, `--yes`.
|
|
125
|
-
- **`add`** saves a new login
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
- **`add`** saves a new login to the organization, then sends it. It goes in
|
|
135
|
+
the one collection you can see, or the one you pick (`--collection` with no
|
|
136
|
+
terminal). The password is typed at a hidden prompt, or read with
|
|
137
|
+
`--password-stdin`; never argv.
|
|
128
138
|
- **`renew`** pushes each Send's deletion 30 days out in place (the link stays)
|
|
129
139
|
and re-syncs its recipients and body from the item. It asks first for any Send
|
|
130
140
|
with more than 7 days left.
|
|
131
141
|
- **`list`** prints items, recipients and expiry (`--json` for scripts).
|
|
132
142
|
- **`report`** regenerates the **Shared Accounts** Linear document (Platform &
|
|
133
|
-
DevOps initiative), which `send`, `add` and `renew` also do. It
|
|
134
|
-
whole; edits there are
|
|
143
|
+
DevOps initiative), which `send`, `add` and `renew` also do. It lists only
|
|
144
|
+
items that have been shared, and is generated whole; edits there are
|
|
145
|
+
overwritten.
|
|
135
146
|
|
|
136
147
|
Each item's custom fields are the only access record: `Recipients`, `Owner`,
|
|
137
148
|
`Send ID`, `Send link`, `Send expires`, `Send account`. Sends belong to the
|
|
@@ -144,6 +155,62 @@ checkout, else Secrets Manager. The Linear key: `--linear-token`, then
|
|
|
144
155
|
`LINEAR_API_KEY`, then the vault item "DevDogs Linear API key (backstage)", then
|
|
145
156
|
a prompt; it is never saved.
|
|
146
157
|
|
|
158
|
+
## Member data: `import` and `export`
|
|
159
|
+
|
|
160
|
+
These replace the platform's `/console/verification` upload and
|
|
161
|
+
`/console/exports` downloads. They read and write production only:
|
|
162
|
+
`DB_URL`, `API_URL` and `SECRET_KEY` come from `.env.production` in a checkout,
|
|
163
|
+
else Secrets Manager. There is no `--db-url`, since a database override would
|
|
164
|
+
leave account creation pointed at production. Days (`--meeting 2026-09-09`,
|
|
165
|
+
`--from`, `--to`) are Eastern, and `--to` includes the whole day.
|
|
166
|
+
|
|
167
|
+
### `import involvement`
|
|
168
|
+
|
|
169
|
+
Replaces the platform's `/console/verification` upload. Export the roster from
|
|
170
|
+
the UGA Involvement Network (Roster → Export → Organization Roster) and pass the
|
|
171
|
+
file as it downloads: the title lines above the header, the `(Hidden)` columns
|
|
172
|
+
and the one-row-per-position layout are all expected. A roster for another
|
|
173
|
+
organization is refused.
|
|
174
|
+
|
|
175
|
+
The roster is the whole truth: everyone on it is verified, and everyone
|
|
176
|
+
verified who is not on it loses that status. A roster email finds its account
|
|
177
|
+
by the profile's UGA email first, then the sign-in address; with no account,
|
|
178
|
+
one is created (confirmed, no invite email) so the member is verified the first
|
|
179
|
+
time they sign in. Every run previews the counts, who is newly verified, who
|
|
180
|
+
loses verification, and whose preferred name differs from the roster, then
|
|
181
|
+
asks; `--dry-run` stops there, `--yes` answers with no terminal. Preferred
|
|
182
|
+
names are never changed. Rerunning the same export changes nothing.
|
|
183
|
+
|
|
184
|
+
### `import attendance`
|
|
185
|
+
|
|
186
|
+
Records a meeting's attendance from a sign-in sheet, for members who could not
|
|
187
|
+
check in on the platform. The sheet is a Google or Microsoft Forms response
|
|
188
|
+
export or a hand-made spreadsheet, saved as CSV; one sheet is one meeting,
|
|
189
|
+
named with `--meeting` (its day, slug, config id or id; a day with two meetings
|
|
190
|
+
asks for the slug). The header and columns are found, not assumed: the email
|
|
191
|
+
column is the one holding UGA addresses, the name is "First/Last Name" or a
|
|
192
|
+
column mentioning "name", and `--email-column`/`--name-column` override
|
|
193
|
+
either. A sheet with no header is read as every address in it. Only UGA
|
|
194
|
+
addresses count; anything else is listed and skipped.
|
|
195
|
+
|
|
196
|
+
Check-ins are written with method `import`, stamped with the meeting's start,
|
|
197
|
+
and count for stars like any other. A member's own check-in is never touched.
|
|
198
|
+
Someone with no account gets one (confirmed, no invite), named from the sheet;
|
|
199
|
+
with no name on the sheet they are listed and skipped. Cancelled and future
|
|
200
|
+
meetings are refused. Rerunning a sheet records nothing new; `--replace`
|
|
201
|
+
makes the sheet the meeting's whole imported set, removing earlier imported
|
|
202
|
+
rows it no longer lists. Every run previews, then asks (`--dry-run`, `--yes`).
|
|
203
|
+
|
|
204
|
+
### `export stars|attendance|reflections`
|
|
205
|
+
|
|
206
|
+
The platform's three CSVs, columns unchanged: `stars` (one row per earned
|
|
207
|
+
star), `attendance` (one row per check-in; `--meeting` for one night) and
|
|
208
|
+
`reflections` (each member's current text). Each export writes a
|
|
209
|
+
`platform.exportAudit` row before reading anything, attributed to the
|
|
210
|
+
platform account linked to the GitHub login `gh` is signed in as, and refuses
|
|
211
|
+
to run when there is none. The file goes to `./<kind>….csv` (owner-readable
|
|
212
|
+
only, never over an existing file without `--yes`) or stdout with `--out -`.
|
|
213
|
+
|
|
147
214
|
No secret value reaches stdout, stderr, the failure log, Sentry, an error
|
|
148
215
|
message or argv: values go to `bw` as base64 JSON on stdin, every error is
|
|
149
216
|
scrubbed of every value read so far (`src/creds/secrets.ts`), and the clipboard
|
|
@@ -1,7 +1,9 @@
|
|
|
1
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-Bjoz29Hl.js";
|
|
2
|
-
import {
|
|
2
|
+
import { n as isDryRun } from "./dry-run-3IxPmCtW.js";
|
|
3
3
|
import { o as unwrap } from "./ui-CdKo8mLw.js";
|
|
4
4
|
import { n as positionals } from "./args-Cjr_Iqts.js";
|
|
5
|
+
import { t as EXPORTS } from "./queries-DeEwfe2c.js";
|
|
6
|
+
import { r as qrCatalogOptions } from "./options-DCaqm8BI.js";
|
|
5
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
8
|
import { join } from "node:path";
|
|
7
9
|
import { confirm, note, select, text } from "@clack/prompts";
|
|
@@ -928,9 +930,9 @@ const completionsCommand = {
|
|
|
928
930
|
const ITEM = {
|
|
929
931
|
flag: "--item",
|
|
930
932
|
value: "<name>",
|
|
931
|
-
summary: "A
|
|
933
|
+
summary: "A DevDogs organization item, by name or id. Repeat for several."
|
|
932
934
|
};
|
|
933
|
-
const TO = {
|
|
935
|
+
const TO$1 = {
|
|
934
936
|
flag: "--to",
|
|
935
937
|
value: "<a@uga.edu,…>",
|
|
936
938
|
summary: "Recipients, comma-separated. Replaces the item's current list."
|
|
@@ -970,7 +972,7 @@ const credsCommand = {
|
|
|
970
972
|
hint: "pick accounts, then officers",
|
|
971
973
|
options: [
|
|
972
974
|
ITEM,
|
|
973
|
-
TO,
|
|
975
|
+
TO$1,
|
|
974
976
|
ROLE,
|
|
975
977
|
ALLOW_EMAIL,
|
|
976
978
|
DB_URL$1,
|
|
@@ -982,7 +984,7 @@ const credsCommand = {
|
|
|
982
984
|
{
|
|
983
985
|
name: "add",
|
|
984
986
|
envFree: true,
|
|
985
|
-
summary: "Save a new
|
|
987
|
+
summary: "Save a new login to the DevDogs organization, then send it.",
|
|
986
988
|
options: [
|
|
987
989
|
{
|
|
988
990
|
flag: "--name",
|
|
@@ -1004,11 +1006,16 @@ const credsCommand = {
|
|
|
1004
1006
|
value: "<name>",
|
|
1005
1007
|
summary: "The officer responsible for the account."
|
|
1006
1008
|
},
|
|
1009
|
+
{
|
|
1010
|
+
flag: "--collection",
|
|
1011
|
+
value: "<name>",
|
|
1012
|
+
summary: "Where to save it, when you can see more than one."
|
|
1013
|
+
},
|
|
1007
1014
|
{
|
|
1008
1015
|
flag: "--password-stdin",
|
|
1009
1016
|
summary: "Read the password from stdin, for scripts."
|
|
1010
1017
|
},
|
|
1011
|
-
TO,
|
|
1018
|
+
TO$1,
|
|
1012
1019
|
ROLE,
|
|
1013
1020
|
ALLOW_EMAIL,
|
|
1014
1021
|
DB_URL$1,
|
|
@@ -1205,6 +1212,61 @@ const envCommand = {
|
|
|
1205
1212
|
]
|
|
1206
1213
|
};
|
|
1207
1214
|
//#endregion
|
|
1215
|
+
//#region src/export/catalog.ts
|
|
1216
|
+
/**
|
|
1217
|
+
* `export`'s place in the command tree: declaration only, nothing here runs.
|
|
1218
|
+
* The handler lives in `commands.ts` beside it.
|
|
1219
|
+
*
|
|
1220
|
+
* `envFree` like `involvement`: production's `DB_URL` comes from the
|
|
1221
|
+
* checkout's `.env.production` or Secrets Manager, and the audit identity from
|
|
1222
|
+
* `gh`, so it runs under `pnpm dlx`.
|
|
1223
|
+
*/
|
|
1224
|
+
const FROM = {
|
|
1225
|
+
flag: "--from",
|
|
1226
|
+
value: "<day>",
|
|
1227
|
+
summary: "From this Eastern day (or ISO timestamp), inclusive."
|
|
1228
|
+
};
|
|
1229
|
+
const TO = {
|
|
1230
|
+
flag: "--to",
|
|
1231
|
+
value: "<day>",
|
|
1232
|
+
summary: "Through this Eastern day (or before this ISO timestamp)."
|
|
1233
|
+
};
|
|
1234
|
+
const OUT = {
|
|
1235
|
+
flag: "--out",
|
|
1236
|
+
value: "<path|->",
|
|
1237
|
+
summary: "Where to write. Defaults to ./<kind>….csv; - for stdout."
|
|
1238
|
+
};
|
|
1239
|
+
function exportOf(kind, extra = []) {
|
|
1240
|
+
return {
|
|
1241
|
+
name: kind,
|
|
1242
|
+
envFree: true,
|
|
1243
|
+
dryRun: "handled",
|
|
1244
|
+
summary: EXPORTS[kind].summary,
|
|
1245
|
+
options: [
|
|
1246
|
+
FROM,
|
|
1247
|
+
TO,
|
|
1248
|
+
...extra,
|
|
1249
|
+
OUT,
|
|
1250
|
+
DRY_RUN,
|
|
1251
|
+
YES
|
|
1252
|
+
]
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
const exportCommand = {
|
|
1256
|
+
name: "export",
|
|
1257
|
+
summary: "Member data as CSV: stars, attendance, reflections. Audited.",
|
|
1258
|
+
hint: "production database; your gh login is recorded",
|
|
1259
|
+
subcommands: [
|
|
1260
|
+
exportOf("stars"),
|
|
1261
|
+
exportOf("attendance", [{
|
|
1262
|
+
flag: "--meeting",
|
|
1263
|
+
value: "<meeting>",
|
|
1264
|
+
summary: "One meeting: its day (2026-09-09), slug, config id or id."
|
|
1265
|
+
}]),
|
|
1266
|
+
exportOf("reflections")
|
|
1267
|
+
]
|
|
1268
|
+
};
|
|
1269
|
+
//#endregion
|
|
1208
1270
|
//#region src/github/catalog.ts
|
|
1209
1271
|
/**
|
|
1210
1272
|
* `github`'s place in the command tree: declaration only, nothing here runs.
|
|
@@ -1298,6 +1360,63 @@ const graphicsCommand = {
|
|
|
1298
1360
|
]
|
|
1299
1361
|
};
|
|
1300
1362
|
//#endregion
|
|
1363
|
+
//#region src/import/catalog.ts
|
|
1364
|
+
const importCommand = {
|
|
1365
|
+
name: "import",
|
|
1366
|
+
summary: "Member data in: the Involvement roster, a meeting's sign-in sheet.",
|
|
1367
|
+
hint: "production database; previews, then asks",
|
|
1368
|
+
subcommands: [{
|
|
1369
|
+
name: "involvement",
|
|
1370
|
+
envFree: true,
|
|
1371
|
+
dryRun: "handled",
|
|
1372
|
+
summary: "Import an Organization Roster export: verify everyone on it, unverify everyone else.",
|
|
1373
|
+
hint: "previews, then asks",
|
|
1374
|
+
options: [
|
|
1375
|
+
{
|
|
1376
|
+
flag: "--file",
|
|
1377
|
+
value: "<csv>",
|
|
1378
|
+
summary: "The roster export (Involvement Network → Roster → Export → Organization Roster)."
|
|
1379
|
+
},
|
|
1380
|
+
DRY_RUN,
|
|
1381
|
+
YES
|
|
1382
|
+
]
|
|
1383
|
+
}, {
|
|
1384
|
+
name: "attendance",
|
|
1385
|
+
envFree: true,
|
|
1386
|
+
dryRun: "handled",
|
|
1387
|
+
summary: "Import a sign-in sheet (form responses or a spreadsheet, as CSV) for one meeting.",
|
|
1388
|
+
hint: "previews, then asks",
|
|
1389
|
+
options: [
|
|
1390
|
+
{
|
|
1391
|
+
flag: "--meeting",
|
|
1392
|
+
value: "<meeting>",
|
|
1393
|
+
summary: "The meeting: its day (2026-09-09), slug, config id or id."
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
flag: "--file",
|
|
1397
|
+
value: "<csv>",
|
|
1398
|
+
summary: "The sign-in sheet, saved as CSV."
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
flag: "--email-column",
|
|
1402
|
+
value: "<header>",
|
|
1403
|
+
summary: "The column of UGA addresses, when there are several."
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
flag: "--name-column",
|
|
1407
|
+
value: "<header>",
|
|
1408
|
+
summary: "The column of names, used for new accounts."
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
flag: "--replace",
|
|
1412
|
+
summary: "Make this sheet the meeting's whole imported set: remove earlier imported rows it lacks."
|
|
1413
|
+
},
|
|
1414
|
+
DRY_RUN,
|
|
1415
|
+
YES
|
|
1416
|
+
]
|
|
1417
|
+
}]
|
|
1418
|
+
};
|
|
1419
|
+
//#endregion
|
|
1301
1420
|
//#region src/newsletter/catalog.ts
|
|
1302
1421
|
/**
|
|
1303
1422
|
* `newsletter`'s place in the command tree: declaration only, nothing here
|
|
@@ -1453,8 +1572,12 @@ const catalog = createCatalog({
|
|
|
1453
1572
|
commands: [envCommand]
|
|
1454
1573
|
},
|
|
1455
1574
|
{
|
|
1456
|
-
title: "
|
|
1457
|
-
commands: [
|
|
1575
|
+
title: "Production database",
|
|
1576
|
+
commands: [
|
|
1577
|
+
plannerCommand,
|
|
1578
|
+
importCommand,
|
|
1579
|
+
exportCommand
|
|
1580
|
+
]
|
|
1458
1581
|
},
|
|
1459
1582
|
{
|
|
1460
1583
|
title: "Graphics & QR codes (no credentials)",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
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-Bjoz29Hl.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-
|
|
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-Dke7bQVz.js";
|
|
3
3
|
import { a as repoPeerUrl, n as loadEnv, r as loadEnvLoad, t as getEnvSync } from "./peers-B0KDkI6a.js";
|
|
4
4
|
import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as wouldRunLine, n as isDryRun, t as dryRunKind } from "./dry-run-3IxPmCtW.js";
|
|
6
6
|
import { a as explainError, i as explain, n as bail, o as unwrap, r as errorMessage } from "./ui-CdKo8mLw.js";
|
|
7
7
|
import { n as positionals, t as flagValue } from "./args-Cjr_Iqts.js";
|
|
8
|
-
import { a as listSecrets$1, c as updateSecret, i as createSecret, o as projectIdFor, r as byKey, s as setExplicitAccessToken } from "./client-B7HhuTaA.js";
|
|
9
|
-
import { c as EnvDocument } from "./vault-C8LYJFOQ.js";
|
|
10
8
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
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-B_MY9kVI.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";
|
|
@@ -17,9 +17,9 @@ import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
|
17
17
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
18
18
|
import { promisify } from "node:util";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
|
+
import postgres from "postgres";
|
|
20
21
|
import { z } from "zod";
|
|
21
22
|
import { randomBytes } from "node:crypto";
|
|
22
|
-
import postgres from "postgres";
|
|
23
23
|
//#region ../cli-core/src/completions.ts
|
|
24
24
|
/**
|
|
25
25
|
* Groups all paths by their parent prefix to build per-parent completion lists.
|
|
@@ -5268,9 +5268,11 @@ const HANDLERS = {
|
|
|
5268
5268
|
completions: handleCompletions,
|
|
5269
5269
|
github: handleGithub,
|
|
5270
5270
|
graphics: async (rest) => (await import("./commands-Dv5TXjvb.js")).handleGraphics(rest),
|
|
5271
|
-
qr: async (rest) => (await import("./commands-
|
|
5271
|
+
qr: async (rest) => (await import("./commands-Sx1fDNNn.js")).handleQr(rest),
|
|
5272
5272
|
newsletter: async (rest) => (await import("./commands-D345F-Od.js")).handleNewsletter(rest),
|
|
5273
|
-
creds: async (rest) => (await import("./commands-
|
|
5273
|
+
creds: async (rest) => (await import("./commands-zPSnOGzH.js")).handleCreds(rest),
|
|
5274
|
+
import: async (rest) => (await import("./commands-DzALVX5g.js")).handleImport(rest),
|
|
5275
|
+
export: async (rest) => (await import("./commands-5Ou8xJw5.js")).handleExport(rest)
|
|
5274
5276
|
};
|
|
5275
5277
|
/**
|
|
5276
5278
|
* Names that were commands of the contributor CLI, refused with where they
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { C as discoverRepoRoot, p as isNonInteractive } from "./telemetry-Bjoz29Hl.js";
|
|
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-CdKo8mLw.js";
|
|
4
|
+
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { confirm, log, text } from "@clack/prompts";
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import postgres from "postgres";
|
|
9
|
+
//#region src/production/access.ts
|
|
10
|
+
/**
|
|
11
|
+
* Production access for the commands that read or write member data
|
|
12
|
+
* (`involvement`, `attendance`, `export`).
|
|
13
|
+
*
|
|
14
|
+
* Every key comes from one place, so a run never splits across two projects:
|
|
15
|
+
* the checkout's `.env.production`, else the production Secrets Manager
|
|
16
|
+
* project. There is deliberately no `--db-url`; a database override would
|
|
17
|
+
* leave account creation (which goes through Auth with `API_URL`) pointed at
|
|
18
|
+
* production.
|
|
19
|
+
*/
|
|
20
|
+
/** A failure already worded for the officer. Safe to print. */
|
|
21
|
+
var ProductionError = class extends Error {
|
|
22
|
+
name = "ProductionError";
|
|
23
|
+
};
|
|
24
|
+
/** The production project in Secrets Manager (see `creds/roster.ts`). */
|
|
25
|
+
const PRODUCTION_PROJECT = "production";
|
|
26
|
+
/** `.env.production` in a checkout, else the production Secrets Manager project. */
|
|
27
|
+
async function readProductionKey(key) {
|
|
28
|
+
const root = discoverRepoRoot();
|
|
29
|
+
if (root) try {
|
|
30
|
+
const text = await readFile(resolve(root, ".env.production"), "utf8");
|
|
31
|
+
const value = nonEmpty(EnvDocument.parse(text).get(key));
|
|
32
|
+
if (value) return value;
|
|
33
|
+
} catch {}
|
|
34
|
+
const { listSecrets, projectIdFor } = await import("./client-DwDGjalU.js");
|
|
35
|
+
const secrets = await listSecrets(await projectIdFor(PRODUCTION_PROJECT));
|
|
36
|
+
return nonEmpty(secrets.find((s) => s.key === key)?.value);
|
|
37
|
+
}
|
|
38
|
+
async function requireProductionKey(key) {
|
|
39
|
+
const value = await readProductionKey(key);
|
|
40
|
+
if (!value) throw new ProductionError(`Could not find production's ${key}. Run \`backstage env pull --target production\` in a checkout, or sign in to Secrets Manager.`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A database failure, described without the connection string: postgres.js
|
|
45
|
+
* can quote the host, and the URL carries the password. A server error
|
|
46
|
+
* (a five-character SQLSTATE) is the server's own message, which never does.
|
|
47
|
+
*/
|
|
48
|
+
function dbError(what, err) {
|
|
49
|
+
const e = err;
|
|
50
|
+
return new ProductionError(`${what}: ${(e.code && /^[0-9A-Z]{5}$/.test(e.code) ? e.message : e.code) ?? "connection failed"}.`);
|
|
51
|
+
}
|
|
52
|
+
/** One short-lived connection; callers `end()` it. */
|
|
53
|
+
function connect(url) {
|
|
54
|
+
return postgres(url, {
|
|
55
|
+
max: 1,
|
|
56
|
+
prepare: false,
|
|
57
|
+
connect_timeout: 15
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Runs `fn` on a fresh connection and closes it, rewording failures. */
|
|
61
|
+
async function withConnection(url, what, fn) {
|
|
62
|
+
const sql = connect(url);
|
|
63
|
+
try {
|
|
64
|
+
return await fn(sql);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err instanceof ProductionError) throw err;
|
|
67
|
+
throw dbError(what, err);
|
|
68
|
+
} finally {
|
|
69
|
+
await sql.end({ timeout: 5 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/production/cli.ts
|
|
74
|
+
/**
|
|
75
|
+
* The terminal half shared by the member-data commands: progress lines, the
|
|
76
|
+
* input file, and the one confirmation before production is written.
|
|
77
|
+
*/
|
|
78
|
+
/** Progress for a person; plain stderr lines when nobody is watching a terminal. */
|
|
79
|
+
function say(message, level = "info") {
|
|
80
|
+
if (isNonInteractive()) process.stderr.write(`${message}\n`);
|
|
81
|
+
else log[level](message);
|
|
82
|
+
}
|
|
83
|
+
function plural(n, one, many = `${one}s`) {
|
|
84
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
85
|
+
}
|
|
86
|
+
/** Whether a person can answer a prompt. */
|
|
87
|
+
function canPrompt() {
|
|
88
|
+
return !isNonInteractive() && process.stdin.isTTY === true;
|
|
89
|
+
}
|
|
90
|
+
function expandHome(path) {
|
|
91
|
+
return path.startsWith("~/") ? `${process.env.HOME ?? "~"}${path.slice(1)}` : path;
|
|
92
|
+
}
|
|
93
|
+
/** `--file`, else asked for at a terminal; then read. */
|
|
94
|
+
async function readInputFile(file, options) {
|
|
95
|
+
let path = file;
|
|
96
|
+
if (!path) {
|
|
97
|
+
if (!options.interactive) throw new UsageError(`Pass the file with --file <${options.placeholder}>.`);
|
|
98
|
+
path = unwrap(await text({
|
|
99
|
+
message: options.message,
|
|
100
|
+
placeholder: options.placeholder,
|
|
101
|
+
validate: (v) => v?.trim() ? void 0 : "A path, please."
|
|
102
|
+
})).trim();
|
|
103
|
+
}
|
|
104
|
+
const full = expandHome(path);
|
|
105
|
+
try {
|
|
106
|
+
return await (options.read ?? ((p) => readFile(p, "utf8")))(full);
|
|
107
|
+
} catch {
|
|
108
|
+
throw new UsageError(`Could not read ${full}.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The one question before production is written. `--yes` answers it; with no
|
|
113
|
+
* terminal and no `--yes`, the command stops rather than guess. Defaults to no.
|
|
114
|
+
*/
|
|
115
|
+
async function confirmWrite(question, options) {
|
|
116
|
+
if (options.yes) return true;
|
|
117
|
+
if (!options.interactive) throw new UsageError(`${question} Pass --yes to answer yes without a terminal.`);
|
|
118
|
+
return unwrap(await confirm({
|
|
119
|
+
message: question,
|
|
120
|
+
initialValue: false
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The shared `catch`: a worded failure is explained and the run exits 1; any
|
|
125
|
+
* other error is unexpected and goes to Sentry with the command's name.
|
|
126
|
+
*/
|
|
127
|
+
function reportFailure(command, err, worded = []) {
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
if (err instanceof UsageError || err instanceof ProductionError || worded.some((kind) => err instanceof kind)) {
|
|
130
|
+
explain(err.message, "");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
explainError(`${command} failed.`, err);
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { reportFailure as a, connect as c, withConnection as d, readInputFile as i, dbError as l, confirmWrite as n, say as o, plural as r, ProductionError as s, canPrompt as t, requireProductionKey as u };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { w as findRepoRoot } from "./telemetry-Bjoz29Hl.js";
|
|
2
2
|
import { n as loadEnv } from "./peers-B0KDkI6a.js";
|
|
3
3
|
import { o as unwrap } from "./ui-CdKo8mLw.js";
|
|
4
|
-
import {
|
|
4
|
+
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
5
|
+
import { a as readTokenFromVault, o as saveTokenToVault, t as VAULT_ITEM_NAME } from "./vault-CsBC-1Mq.js";
|
|
5
6
|
import { dirname, join, resolve } from "node:path";
|
|
6
7
|
import { log, password, select, text } from "@clack/prompts";
|
|
7
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|