@devdogsuga/backstage 0.1.3 → 0.1.5
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 +49 -1
- package/dist/{catalog-BOBE4ee7.js → catalog-BUOwCd2Q.js} +150 -237
- package/dist/{cli-ZF4QgJro.js → cli-B2S6gw9o.js} +16 -955
- package/dist/client-B7HhuTaA.js +429 -0
- package/dist/client-DzXQHd58.js +2 -0
- package/dist/commands-BMQ1OZIr.js +1157 -0
- package/dist/connection-D3XsOE3S.js +7 -0
- package/dist/launch.js +4 -2
- package/dist/peers-B0KDkI6a.js +229 -0
- package/dist/vault-C8LYJFOQ.js +563 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ 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`) and
|
|
17
|
+
below that need no secrets (`graphics`, `qr`, `github`, `newsletter`, `creds`) and
|
|
18
18
|
anything run with `--no-env` never look for one; a command that reads a checkout says
|
|
19
19
|
"run this from inside a DevDogsUGA clone" and exits 1. The DevDogsUGA libraries
|
|
20
20
|
it reads (`@devdogsuga/env`, `@devdogsuga/db`) are optional peers, resolved
|
|
@@ -31,6 +31,8 @@ pnpm backstage --no-env planner status # the preflight credential, fro
|
|
|
31
31
|
pnpm backstage graphics 'event/*' --out ~/images # club images, no checkout
|
|
32
32
|
pnpm backstage qr https://devdogsuga.org --format svg,png,webp --logo acm
|
|
33
33
|
pnpm backstage newsletter send 3.0.1 --to a@uga.edu # asks first; --yes with no terminal
|
|
34
|
+
pnpm backstage creds # share a club login with officers, as a Bitwarden Send
|
|
35
|
+
pnpm backstage creds renew # extend every Send 30 days
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
## Commands
|
|
@@ -49,6 +51,7 @@ pnpm backstage newsletter send 3.0.1 --to a@uga.edu # asks first; --yes with n
|
|
|
49
51
|
| `qr <text>` | QR codes with every option of `/console/qr`. |
|
|
50
52
|
| `github rulesets\|settings` | Diff (and with `--apply` write) GitHub config, through `gh`. |
|
|
51
53
|
| `newsletter render\|draft\|send <issue…>` | Changelog issues as files, mailbox drafts, or a send. |
|
|
54
|
+
| `creds send\|add\|renew\|list\|report` | Club logins from Bitwarden as email-verified Sends, and the Linear report. |
|
|
52
55
|
|
|
53
56
|
`smoke` and `reconcile` replace DevDogsUGA's `packages/deploy-checks`. The
|
|
54
57
|
per-app data (hosts, public paths, the protected path and its redirect) stays in
|
|
@@ -106,6 +109,51 @@ entry for them (`envFree` in the command tree).
|
|
|
106
109
|
Microsoft or UGA's tenant blocks it, sending needs a club-owned app
|
|
107
110
|
registration.
|
|
108
111
|
|
|
112
|
+
## Shared logins: `creds`
|
|
113
|
+
|
|
114
|
+
The club's shared logins (Instagram, Canva, ArchPass, Linktree, later project
|
|
115
|
+
API keys) live in the DevDogs Bitwarden organization, in whatever collection
|
|
116
|
+
suits them. `creds` hands any login or secure note there to officers as a
|
|
117
|
+
Bitwarden Send. Personal items, other organizations' items and production
|
|
118
|
+
secrets (which are in Secrets Manager, not the vault) are out of its reach.
|
|
119
|
+
|
|
120
|
+
- **`send`** picks items and recipients (officers by name or by role, read live
|
|
121
|
+
from production: everyone holding a role other than `Member`, by UGA MyID
|
|
122
|
+
email), shows a preview with the password masked as `••••••••`, then creates
|
|
123
|
+
or updates **one Send per item**, restricted by email verification to its
|
|
124
|
+
recipients and deleted 30 days out. Removing an address revokes it. Addresses
|
|
125
|
+
off the roster need `--allow-email`. Non-interactive: `--item`, `--to`,
|
|
126
|
+
`--role`, `--yes`.
|
|
127
|
+
- **`add`** saves a new login to the organization, then sends it. It goes in
|
|
128
|
+
the one collection you can see, or the one you pick (`--collection` with no
|
|
129
|
+
terminal). The password is typed at a hidden prompt, or read with
|
|
130
|
+
`--password-stdin`; never argv.
|
|
131
|
+
- **`renew`** pushes each Send's deletion 30 days out in place (the link stays)
|
|
132
|
+
and re-syncs its recipients and body from the item. It asks first for any Send
|
|
133
|
+
with more than 7 days left.
|
|
134
|
+
- **`list`** prints items, recipients and expiry (`--json` for scripts).
|
|
135
|
+
- **`report`** regenerates the **Shared Accounts** Linear document (Platform &
|
|
136
|
+
DevOps initiative), which `send`, `add` and `renew` also do. It lists only
|
|
137
|
+
items that have been shared, and is generated whole; edits there are
|
|
138
|
+
overwritten.
|
|
139
|
+
|
|
140
|
+
Each item's custom fields are the only access record: `Recipients`, `Owner`,
|
|
141
|
+
`Send ID`, `Send link`, `Send expires`, `Send account`. Sends belong to the
|
|
142
|
+
Bitwarden account that creates them, so `creds` runs on your own `bw` session
|
|
143
|
+
(signing in and unlocking after asking, or `BW_SESSION`), never on CI; renewing
|
|
144
|
+
a Send another officer made creates a new one, with a new link, under yours.
|
|
145
|
+
|
|
146
|
+
The roster needs production's `DB_URL`: `--db-url`, else `.env.production` in a
|
|
147
|
+
checkout, else Secrets Manager. The Linear key: `--linear-token`, then
|
|
148
|
+
`LINEAR_API_KEY`, then the vault item "DevDogs Linear API key (backstage)", then
|
|
149
|
+
a prompt; it is never saved.
|
|
150
|
+
|
|
151
|
+
No secret value reaches stdout, stderr, the failure log, Sentry, an error
|
|
152
|
+
message or argv: values go to `bw` as base64 JSON on stdin, every error is
|
|
153
|
+
scrubbed of every value read so far (`src/creds/secrets.ts`), and the clipboard
|
|
154
|
+
only ever gets the link. `src/creds/commands.test.ts` runs every path against a
|
|
155
|
+
fake `bw` with a sentinel password and looks for it in all of those places.
|
|
156
|
+
|
|
109
157
|
## Tiers, `--no-env`, CI
|
|
110
158
|
|
|
111
159
|
Nothing is ever asked at launch. `--tier <t>` (anywhere in argv) or
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { A as YES, D as DRY_RUN, O as JSON_FLAG,
|
|
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
2
|
import { o as isDryRun, r as qrCatalogOptions } from "./options-BTjOf5KP.js";
|
|
3
3
|
import { o as unwrap } from "./ui-CdKo8mLw.js";
|
|
4
4
|
import { n as positionals } from "./args-Cjr_Iqts.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
9
7
|
import { confirm, note, select, text } from "@clack/prompts";
|
|
10
8
|
import "node:fs/promises";
|
|
11
9
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
@@ -193,235 +191,6 @@ function renderCommandList(catalog, version) {
|
|
|
193
191
|
}, null, 2);
|
|
194
192
|
}
|
|
195
193
|
//#endregion
|
|
196
|
-
//#region ../cli-core/src/repo/resolve.ts
|
|
197
|
-
/**
|
|
198
|
-
* Resolves an `@devdogsuga/*` package FROM the target repo, not from
|
|
199
|
-
* devtools' own (dlx-isolated) `node_modules`.
|
|
200
|
-
*
|
|
201
|
-
* Ported from the `devtools-dlx` prototype
|
|
202
|
-
* (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
|
|
203
|
-
* experiments 2 and 4 — read that file for the two gotchas this module
|
|
204
|
-
* exists to avoid). The short version:
|
|
205
|
-
*
|
|
206
|
-
* - `require.resolve(\`${specifier}/package.json\`)` throws
|
|
207
|
-
* `ERR_PACKAGE_PATH_NOT_EXPORTED` on packages (like `@devdogsuga/env`,
|
|
208
|
-
* `@devdogsuga/open-graph`) whose `exports` map does not list
|
|
209
|
-
* `"./package.json"`, even though the file is on disk.
|
|
210
|
-
* - A resolved path cannot be assumed to contain a literal
|
|
211
|
-
* `node_modules/<specifier>` segment — pnpm workspace-linked packages
|
|
212
|
-
* resolve to their REALPATH (e.g. `packages/open-graph/dist/index.js`),
|
|
213
|
-
* which has no such segment at all.
|
|
214
|
-
* - `require.resolve` ignores whatever `conditions` a caller has set on
|
|
215
|
-
* `tsx`'s `register()` — condition-based resolution (the
|
|
216
|
-
* `devdogs-source` condition) has to be done by hand, reading the
|
|
217
|
-
* target package's own `exports["."]` map.
|
|
218
|
-
*
|
|
219
|
-
* Both problems are solved the same way: walk up from the resolved file's
|
|
220
|
-
* directory to the nearest `package.json` whose own `"name"` field matches
|
|
221
|
-
* the specifier. That works for a real installed dependency AND a
|
|
222
|
-
* workspace symlink, uniformly.
|
|
223
|
-
*/
|
|
224
|
-
/**
|
|
225
|
-
* The installable package name a (possibly subpath) specifier belongs to:
|
|
226
|
-
* `@devdogsuga/env/load` → `@devdogsuga/env`, `foo/bar` → `foo`. A repo's
|
|
227
|
-
* package.json declares the PACKAGE as a dependency, never a subpath, so
|
|
228
|
-
* `findDependent` has to search on this rather than the literal specifier a
|
|
229
|
-
* caller wants to import.
|
|
230
|
-
*/
|
|
231
|
-
function packageNameOf(specifier) {
|
|
232
|
-
const parts = specifier.split("/");
|
|
233
|
-
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Scans `apps/*` and `packages/*` under `repoRoot` for the first
|
|
237
|
-
* `package.json` whose `dependencies`/`devDependencies`/`peerDependencies`
|
|
238
|
-
* names `specifier`'s package (see `packageNameOf`). Returns its path
|
|
239
|
-
* relative to `repoRoot`, or `null` if nothing in the repo depends on it.
|
|
240
|
-
*
|
|
241
|
-
* Re-scans the filesystem on every call — fine for a short-lived CLI
|
|
242
|
-
* invocation calling this a handful of times; see FINDINGS item 5 if this
|
|
243
|
-
* is ever called in a hot loop.
|
|
244
|
-
*/
|
|
245
|
-
function findDependent(repoRoot, specifier) {
|
|
246
|
-
const packageName = packageNameOf(specifier);
|
|
247
|
-
for (const group of ["apps", "packages"]) {
|
|
248
|
-
const groupDir = join(repoRoot, group);
|
|
249
|
-
let entries;
|
|
250
|
-
try {
|
|
251
|
-
entries = readdirSync(groupDir);
|
|
252
|
-
} catch {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
for (const entry of entries) {
|
|
256
|
-
const pkgJsonPath = join(groupDir, entry, "package.json");
|
|
257
|
-
if (!existsSync(pkgJsonPath)) continue;
|
|
258
|
-
let pkg;
|
|
259
|
-
try {
|
|
260
|
-
pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
261
|
-
} catch {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (packageName in {
|
|
265
|
-
...pkg.dependencies,
|
|
266
|
-
...pkg.devDependencies,
|
|
267
|
-
...pkg.peerDependencies
|
|
268
|
-
}) return join(group, entry, "package.json");
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
/** Walks up from `startDir` to the nearest `package.json` whose `"name"` matches `specifier`. */
|
|
274
|
-
function findOwningPackageJson(startDir, specifier) {
|
|
275
|
-
const packageName = packageNameOf(specifier);
|
|
276
|
-
let dir = startDir;
|
|
277
|
-
for (;;) {
|
|
278
|
-
const candidate = join(dir, "package.json");
|
|
279
|
-
if (existsSync(candidate)) try {
|
|
280
|
-
if (JSON.parse(readFileSync(candidate, "utf8")).name === packageName) return candidate;
|
|
281
|
-
} catch {}
|
|
282
|
-
const parent = dirname(dir);
|
|
283
|
-
if (parent === dir) throw new Error(`resolveFromRepo: could not find ${specifier}'s own package.json by walking up from ${startDir}`);
|
|
284
|
-
dir = parent;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Resolves `specifier` as `resolutionBase` (a workspace-relative
|
|
289
|
-
* `package.json` path known to depend on it, from `findDependent()`) would
|
|
290
|
-
* see it.
|
|
291
|
-
*
|
|
292
|
-
* `opts.condition`, when given, is NOT passed through to Node's resolver —
|
|
293
|
-
* `require.resolve` ignores tsx's `register({ conditions })` entirely (see
|
|
294
|
-
* this module's header). Instead the target package's own
|
|
295
|
-
* `exports["."]` map is read directly and `exports["."][condition]` is
|
|
296
|
-
* picked, falling back to `.default`.
|
|
297
|
-
*/
|
|
298
|
-
function resolveFromRepo(repoRoot, resolutionBase, specifier, opts) {
|
|
299
|
-
const baseFile = join(repoRoot, resolutionBase);
|
|
300
|
-
const require = createRequire(baseFile);
|
|
301
|
-
let resolvedPath;
|
|
302
|
-
if (opts?.condition) {
|
|
303
|
-
const defaultEntry = require.resolve(specifier);
|
|
304
|
-
const pkgJsonPath = findOwningPackageJson(dirname(defaultEntry), specifier);
|
|
305
|
-
const rootExport = JSON.parse(readFileSync(pkgJsonPath, "utf8")).exports?.["."];
|
|
306
|
-
const conditioned = rootExport && typeof rootExport === "object" ? rootExport[opts.condition] ?? rootExport.default : void 0;
|
|
307
|
-
if (typeof conditioned !== "string") throw new Error(`resolveFromRepo: ${specifier} has no "${opts.condition}" (or "default") export in its exports["."] map`);
|
|
308
|
-
resolvedPath = join(dirname(pkgJsonPath), conditioned);
|
|
309
|
-
} else resolvedPath = require.resolve(specifier);
|
|
310
|
-
const pkgJsonPath = findOwningPackageJson(dirname(resolvedPath), specifier);
|
|
311
|
-
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
312
|
-
return {
|
|
313
|
-
resolvedPath,
|
|
314
|
-
version: pkg.version ?? "0.0.0",
|
|
315
|
-
pkgJsonPath
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region ../cli-core/src/repo/peers.ts
|
|
320
|
-
/**
|
|
321
|
-
* Loads devtools' optional-peer `@devdogsuga/*` libraries — the ones
|
|
322
|
-
* published to npm and consumed by the TARGET repo, which devtools reads at
|
|
323
|
-
* runtime rather than bundling its own copy of (see this package's
|
|
324
|
-
* `package.json`: `peerDependencies` + `peerDependenciesMeta.optional`, and
|
|
325
|
-
* `FINDINGS.md` experiment 2).
|
|
326
|
-
*
|
|
327
|
-
* Every export here is memoized per module (one dynamic `import()` per
|
|
328
|
-
* process, not per call) — this is what makes the module-identity guarantee
|
|
329
|
-
* in FINDINGS experiment 3 hold: every devtools call site that needs
|
|
330
|
-
* `@devdogsuga/env`'s registry gets the exact same module instance the
|
|
331
|
-
* repo's own manifests populated via `declare()`/`define()`, because both
|
|
332
|
-
* resolve to the identical absolute file path and Node's ESM cache is keyed
|
|
333
|
-
* by resolved URL.
|
|
334
|
-
*/
|
|
335
|
-
const moduleCache = /* @__PURE__ */ new Map();
|
|
336
|
-
/** The file URL each peer resolved to through the target repo, set only on
|
|
337
|
-
* that path (not on either plain bare-specifier fallback). See
|
|
338
|
-
* `repoPeerUrl()`. */
|
|
339
|
-
const resolvedUrls = /* @__PURE__ */ new Map();
|
|
340
|
-
/**
|
|
341
|
-
* Dynamically imports `specifier` as the repo would resolve it, memoized so
|
|
342
|
-
* repeated calls in one process return the SAME module instance (required
|
|
343
|
-
* for the env-registry identity guarantee above).
|
|
344
|
-
*
|
|
345
|
-
* Falls back to a plain bare-specifier `import()` when devtools is not
|
|
346
|
-
* running inside a DevDogsUGA checkout at all (`findRepoRoot()` throws
|
|
347
|
-
* `RepoNotFoundError`). That is not a production affordance — under real
|
|
348
|
-
* `pnpm dlx` use, `specifier` is an optional peer devtools' own package
|
|
349
|
-
* never installs, so the fallback import fails with the ordinary
|
|
350
|
-
* `MODULE_NOT_FOUND` either way. It exists so devtools' OWN test suite
|
|
351
|
-
* (which has no target repo to discover, but does have every one of these
|
|
352
|
-
* peers as a real Backstage workspace package) exercises this code against
|
|
353
|
-
* the genuine package rather than needing every test to mock this module.
|
|
354
|
-
*/
|
|
355
|
-
function loadPeer(specifier) {
|
|
356
|
-
const cached = moduleCache.get(specifier);
|
|
357
|
-
if (cached) return cached;
|
|
358
|
-
const promise = (async () => {
|
|
359
|
-
if (process.env.DEVTOOLS_TEST_REPO_ROOT) return import(specifier);
|
|
360
|
-
let repoRoot;
|
|
361
|
-
try {
|
|
362
|
-
repoRoot = findRepoRoot();
|
|
363
|
-
} catch (err) {
|
|
364
|
-
if (err instanceof RepoNotFoundError) return import(specifier);
|
|
365
|
-
throw err;
|
|
366
|
-
}
|
|
367
|
-
const resolutionBase = findDependent(repoRoot, specifier);
|
|
368
|
-
if (!resolutionBase) throw new Error(`Nothing in this repo depends on ${specifier} — expected an app or package under apps/* or packages/* to declare it (dependencies/devDependencies/peerDependencies).`);
|
|
369
|
-
const { resolvedPath } = resolveFromRepo(repoRoot, resolutionBase, specifier);
|
|
370
|
-
const url = pathToFileURL(resolvedPath).href;
|
|
371
|
-
resolvedUrls.set(specifier, url);
|
|
372
|
-
return import(url);
|
|
373
|
-
})();
|
|
374
|
-
moduleCache.set(specifier, promise);
|
|
375
|
-
return promise;
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* The file URL `specifier` was loaded from through the target repo, or
|
|
379
|
-
* undefined if it has not been loaded yet or came from a plain bare-specifier
|
|
380
|
-
* `import()` (the test-suite and not-in-a-repo fallbacks above).
|
|
381
|
-
*
|
|
382
|
-
* For code devtools imports that itself names a peer by bare specifier --
|
|
383
|
-
* today only devtools' own `env.ts` manifest. From an installed copy that
|
|
384
|
-
* specifier cannot resolve at all (an optional peer is never installed next
|
|
385
|
-
* to devtools under `pnpm dlx`), and even where it can, the only copy that
|
|
386
|
-
* keeps module identity is this one. `repo/peer-redirect.ts` uses it to
|
|
387
|
-
* point the import here.
|
|
388
|
-
*/
|
|
389
|
-
function repoPeerUrl(specifier) {
|
|
390
|
-
return resolvedUrls.get(specifier);
|
|
391
|
-
}
|
|
392
|
-
let resolvedEnvModule;
|
|
393
|
-
function loadEnv() {
|
|
394
|
-
return loadPeer("@devdogsuga/env").then((mod) => {
|
|
395
|
-
resolvedEnvModule = mod;
|
|
396
|
-
return mod;
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* The synchronous companion to `loadEnv()`, for the handful of call sites
|
|
401
|
-
* (`gh/environments.ts`'s `GITHUB_ENVIRONMENT_SPECS` getters) that cannot
|
|
402
|
-
* become async without changing their own callers' contract — they read a
|
|
403
|
-
* plain array off a getter, not a Promise. Safe ONLY after `loadEnv()` has
|
|
404
|
-
* resolved at least once; every caller of these getters already requires
|
|
405
|
-
* `env/discovery.ts`'s `loadRegistry()` (which calls `loadEnv()` itself) to
|
|
406
|
-
* have run first, via `assertRegistryLoaded()`'s own guard.
|
|
407
|
-
*/
|
|
408
|
-
function getEnvSync() {
|
|
409
|
-
if (!resolvedEnvModule) throw new Error("getEnvSync() called before loadEnv() ever resolved — call loadRegistry() (env/discovery.ts) first.");
|
|
410
|
-
return resolvedEnvModule;
|
|
411
|
-
}
|
|
412
|
-
function loadEnvLoad() {
|
|
413
|
-
return loadPeer("@devdogsuga/env/load");
|
|
414
|
-
}
|
|
415
|
-
function loadEnvSession() {
|
|
416
|
-
return loadPeer("@devdogsuga/env/session");
|
|
417
|
-
}
|
|
418
|
-
//#endregion
|
|
419
|
-
//#region ../cli-core/src/db/connection.ts
|
|
420
|
-
/** An env value, with unset and empty both meaning "not given". */
|
|
421
|
-
function nonEmpty(value) {
|
|
422
|
-
return value === void 0 || value === "" ? void 0 : value;
|
|
423
|
-
}
|
|
424
|
-
//#endregion
|
|
425
194
|
//#region ../cli-core/src/process-group.ts
|
|
426
195
|
/**
|
|
427
196
|
* Running a child tool so that stopping it stops everything it started.
|
|
@@ -1146,6 +915,146 @@ const completionsCommand = {
|
|
|
1146
915
|
}]
|
|
1147
916
|
};
|
|
1148
917
|
//#endregion
|
|
918
|
+
//#region src/creds/catalog.ts
|
|
919
|
+
/**
|
|
920
|
+
* `creds`'s place in the command tree: declaration only, nothing here runs.
|
|
921
|
+
* The handler lives in `commands.ts` beside it.
|
|
922
|
+
*
|
|
923
|
+
* Every subcommand is `envFree`: it runs on the officer's own Bitwarden
|
|
924
|
+
* session and reads production only for the roster, through `--db-url` or
|
|
925
|
+
* Secrets Manager, so no env file or tier is entered. Bare `creds` at a
|
|
926
|
+
* terminal opens this group's menu.
|
|
927
|
+
*/
|
|
928
|
+
const ITEM = {
|
|
929
|
+
flag: "--item",
|
|
930
|
+
value: "<name>",
|
|
931
|
+
summary: "A DevDogs organization item, by name or id. Repeat for several."
|
|
932
|
+
};
|
|
933
|
+
const TO = {
|
|
934
|
+
flag: "--to",
|
|
935
|
+
value: "<a@uga.edu,…>",
|
|
936
|
+
summary: "Recipients, comma-separated. Replaces the item's current list."
|
|
937
|
+
};
|
|
938
|
+
const ROLE = {
|
|
939
|
+
flag: "--role",
|
|
940
|
+
value: "<role,…>",
|
|
941
|
+
summary: "Everyone holding these officer roles, added to --to."
|
|
942
|
+
};
|
|
943
|
+
const ALLOW_EMAIL = {
|
|
944
|
+
flag: "--allow-email",
|
|
945
|
+
summary: "Accept addresses that are not on the officer roster."
|
|
946
|
+
};
|
|
947
|
+
const DB_URL$1 = {
|
|
948
|
+
flag: "--db-url",
|
|
949
|
+
value: "<url>",
|
|
950
|
+
summary: "Production connection for the roster. Defaults to .env.production, then Secrets Manager."
|
|
951
|
+
};
|
|
952
|
+
const LINEAR_TOKEN = {
|
|
953
|
+
flag: "--linear-token",
|
|
954
|
+
value: "<key>",
|
|
955
|
+
summary: "Linear API key. Prefer LINEAR_API_KEY or the vault item."
|
|
956
|
+
};
|
|
957
|
+
const NO_REPORT = {
|
|
958
|
+
flag: "--no-report",
|
|
959
|
+
summary: "Skip regenerating the Shared Accounts Linear document."
|
|
960
|
+
};
|
|
961
|
+
const credsCommand = {
|
|
962
|
+
name: "creds",
|
|
963
|
+
summary: "Share club logins from Bitwarden as email-verified Sends.",
|
|
964
|
+
hint: "your own Bitwarden session; officers only",
|
|
965
|
+
subcommands: [
|
|
966
|
+
{
|
|
967
|
+
name: "send",
|
|
968
|
+
envFree: true,
|
|
969
|
+
summary: "Create or update the Sends for existing shared accounts.",
|
|
970
|
+
hint: "pick accounts, then officers",
|
|
971
|
+
options: [
|
|
972
|
+
ITEM,
|
|
973
|
+
TO,
|
|
974
|
+
ROLE,
|
|
975
|
+
ALLOW_EMAIL,
|
|
976
|
+
DB_URL$1,
|
|
977
|
+
LINEAR_TOKEN,
|
|
978
|
+
NO_REPORT,
|
|
979
|
+
YES
|
|
980
|
+
]
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
name: "add",
|
|
984
|
+
envFree: true,
|
|
985
|
+
summary: "Save a new login to the DevDogs organization, then send it.",
|
|
986
|
+
options: [
|
|
987
|
+
{
|
|
988
|
+
flag: "--name",
|
|
989
|
+
value: "<name>",
|
|
990
|
+
summary: "The item's name, e.g. Instagram."
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
flag: "--url",
|
|
994
|
+
value: "<url>",
|
|
995
|
+
summary: "The login page."
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
flag: "--username",
|
|
999
|
+
value: "<username>",
|
|
1000
|
+
summary: "The login."
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
flag: "--owner",
|
|
1004
|
+
value: "<name>",
|
|
1005
|
+
summary: "The officer responsible for the account."
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
flag: "--collection",
|
|
1009
|
+
value: "<name>",
|
|
1010
|
+
summary: "Where to save it, when you can see more than one."
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
flag: "--password-stdin",
|
|
1014
|
+
summary: "Read the password from stdin, for scripts."
|
|
1015
|
+
},
|
|
1016
|
+
TO,
|
|
1017
|
+
ROLE,
|
|
1018
|
+
ALLOW_EMAIL,
|
|
1019
|
+
DB_URL$1,
|
|
1020
|
+
LINEAR_TOKEN,
|
|
1021
|
+
NO_REPORT,
|
|
1022
|
+
YES
|
|
1023
|
+
]
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
name: "renew",
|
|
1027
|
+
envFree: true,
|
|
1028
|
+
summary: "Extend Sends 30 days and re-sync their recipients.",
|
|
1029
|
+
hint: "asks first for any with more than 7 days left",
|
|
1030
|
+
options: [
|
|
1031
|
+
ITEM,
|
|
1032
|
+
LINEAR_TOKEN,
|
|
1033
|
+
NO_REPORT,
|
|
1034
|
+
YES
|
|
1035
|
+
]
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
name: "list",
|
|
1039
|
+
envFree: true,
|
|
1040
|
+
dryRun: "read-only",
|
|
1041
|
+
summary: "Shared accounts, their recipients and when each Send expires.",
|
|
1042
|
+
hint: "reads only",
|
|
1043
|
+
options: [JSON_FLAG]
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: "report",
|
|
1047
|
+
envFree: true,
|
|
1048
|
+
summary: "Regenerate the Shared Accounts Linear document.",
|
|
1049
|
+
options: [LINEAR_TOKEN, {
|
|
1050
|
+
flag: "--document",
|
|
1051
|
+
value: "<id>",
|
|
1052
|
+
summary: "A different Linear document id, for testing."
|
|
1053
|
+
}]
|
|
1054
|
+
}
|
|
1055
|
+
]
|
|
1056
|
+
};
|
|
1057
|
+
//#endregion
|
|
1149
1058
|
//#region src/deploy/catalog.ts
|
|
1150
1059
|
/**
|
|
1151
1060
|
* `deploy`'s place in the command tree: declaration only, nothing here runs.
|
|
@@ -1557,8 +1466,12 @@ const catalog = createCatalog({
|
|
|
1557
1466
|
commands: [graphicsCommand, qrCommand]
|
|
1558
1467
|
},
|
|
1559
1468
|
{
|
|
1560
|
-
title: "Your own sign-in (gh login, club mailbox)",
|
|
1561
|
-
commands: [
|
|
1469
|
+
title: "Your own sign-in (gh login, club mailbox, Bitwarden)",
|
|
1470
|
+
commands: [
|
|
1471
|
+
githubCommand,
|
|
1472
|
+
newsletterCommand,
|
|
1473
|
+
credsCommand
|
|
1474
|
+
]
|
|
1562
1475
|
},
|
|
1563
1476
|
{
|
|
1564
1477
|
title: "CLI utilities",
|
|
@@ -1567,4 +1480,4 @@ const catalog = createCatalog({
|
|
|
1567
1480
|
]
|
|
1568
1481
|
});
|
|
1569
1482
|
//#endregion
|
|
1570
|
-
export {
|
|
1483
|
+
export { recordEnteredTier as a, setMenuEnvHook as c, formatCommand as d, helpPath as f, beginInvocation as i, dbPush as l, renderHelp as m, bareGroupStartPath as n, recordResolved as o, renderCommandList as p, runMenu as r, reproducibleCommand as s, catalog as t, dbPushDryRun as u };
|