@devdogsuga/backstage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/bin/backstage.mjs +28 -0
- package/dist/args-Cjr_Iqts.js +66 -0
- package/dist/build-info.json +3 -0
- package/dist/catalog-BOBE4ee7.js +1570 -0
- package/dist/cli-YLCe3Zrx.js +6300 -0
- package/dist/commands-Bk7IbhPD.js +220 -0
- package/dist/commands-D345F-Od.js +800 -0
- package/dist/commands-Dv5TXjvb.js +545 -0
- package/dist/dispatch-D048O65I.js +13 -0
- package/dist/launch.js +329 -0
- package/dist/options-BTjOf5KP.js +183 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/telemetry-Bjoz29Hl.js +609 -0
- package/dist/telemetry.js +2 -0
- package/dist/ui-CdKo8mLw.js +66 -0
- package/env.ts +163 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @devdogsuga/backstage
|
|
2
|
+
|
|
3
|
+
The officer and production CLI: everything that needs production secrets and is
|
|
4
|
+
used only by the two people who hold them (and CI), plus the tools that need
|
|
5
|
+
only the user's own login. Run it anywhere:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm dlx --config.minimum-release-age=0 --config.dlx-cache-max-age=0 @devdogsuga/backstage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It always runs the latest publish, CI included, so local runs and CI runs
|
|
12
|
+
match. The two flags matter outside a DevDogsUGA checkout, where the workspace's
|
|
13
|
+
`minimumReleaseAgeExclude` and `dlxCacheMaxAge: 0` do not apply (inside one,
|
|
14
|
+
`pnpm backstage` is the script).
|
|
15
|
+
|
|
16
|
+
It **starts without a checkout**. Help, `version`, `completions`, the tools
|
|
17
|
+
below that need no secrets (`graphics`, `qr`, `github`, `newsletter`) and
|
|
18
|
+
anything run with `--no-env` never look for one; a command that reads a checkout says
|
|
19
|
+
"run this from inside a DevDogsUGA clone" and exits 1. The DevDogsUGA libraries
|
|
20
|
+
it reads (`@devdogsuga/env`, `@devdogsuga/db`) are optional peers, resolved
|
|
21
|
+
through the checkout by the commands that need them.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm backstage # no arguments: a menu
|
|
25
|
+
pnpm backstage env pull --target production # Bitwarden → .env.production
|
|
26
|
+
pnpm backstage env audit --target production # every store, plus orphaned Worker secrets
|
|
27
|
+
pnpm backstage env audit --target production --prune
|
|
28
|
+
pnpm backstage deploy platform --tier staging # token check, secrets file, wrangler deploy
|
|
29
|
+
pnpm backstage --no-env deploy write-env # the CI steps that supply their own environment
|
|
30
|
+
pnpm backstage --no-env planner status # the preflight credential, from DB_URL
|
|
31
|
+
pnpm backstage graphics 'event/*' --out ~/images # club images, no checkout
|
|
32
|
+
pnpm backstage qr https://devdogsuga.org --format svg,png,webp --logo acm
|
|
33
|
+
pnpm backstage newsletter send 3.0.1 --to a@uga.edu # asks first; --yes with no terminal
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
| ---------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
40
|
+
| `deploy <app> --tier <t>` | Checks `CLOUDFLARE_API_TOKEN`, writes the Worker's secrets file, deploys, removes it. |
|
|
41
|
+
| `deploy write-env` | Composes `.env.<DEPLOY_ENV>` from the GitHub environment. |
|
|
42
|
+
| `deploy preflight` | Classifies the project: paused (skip) or broken (fail). |
|
|
43
|
+
| `deploy plan`, `deploy migrate` | Dry-run the migrations into the job summary; apply them to `DB_URL`. |
|
|
44
|
+
| `deploy smoke --tier <t> [--app]` | Public routes answer 200, the auth redirect works, this deploy's Sentry release shows. |
|
|
45
|
+
| `deploy reconcile --tier <t>` | The platform's config reconcile, after the deploy (`CRON_SECRET`). |
|
|
46
|
+
| `env pull\|push\|audit --target <t>` | One env file per target, synced to Bitwarden and GitHub. `audit` lists orphans. |
|
|
47
|
+
| `planner status\|create\|reset-password\|drop` | The `migration_planner` role the preflight tier holds. |
|
|
48
|
+
| `graphics [graphic…]` | Club images from `@devdogsuga/brand`: `brand/*`, `app/*`, `event/*`. |
|
|
49
|
+
| `qr <text>` | QR codes with every option of `/console/qr`. |
|
|
50
|
+
| `github rulesets\|settings` | Diff (and with `--apply` write) GitHub config, through `gh`. |
|
|
51
|
+
| `newsletter render\|draft\|send <issue…>` | Changelog issues as files, mailbox drafts, or a send. |
|
|
52
|
+
|
|
53
|
+
`smoke` and `reconcile` replace DevDogsUGA's `packages/deploy-checks`. The
|
|
54
|
+
per-app data (hosts, public paths, the protected path and its redirect) stays in
|
|
55
|
+
DevDogsUGA, as a `smoke` field on each app's entry in `workers.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"path": "apps/platform",
|
|
61
|
+
"smoke": {
|
|
62
|
+
"hosts": {
|
|
63
|
+
"staging": "staging.devdogsuga.org",
|
|
64
|
+
"production": "devdogsuga.org"
|
|
65
|
+
},
|
|
66
|
+
"publicPaths": ["/", "/events"],
|
|
67
|
+
"protectedPath": "/console/permissions",
|
|
68
|
+
"protectedRedirectPrefix": "/auth"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"apps/sandbox"
|
|
72
|
+
]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Entries may be bare paths or objects. While the field is absent, the table
|
|
76
|
+
`deploy-checks` carried answers for `platform` and `schedule-builder`.
|
|
77
|
+
|
|
78
|
+
## The tools that need no production secrets
|
|
79
|
+
|
|
80
|
+
None of these reads an env file or needs a checkout, so the launcher skips env
|
|
81
|
+
entry for them (`envFree` in the command tree).
|
|
82
|
+
|
|
83
|
+
- **`graphics`** renders the brand, app and event images with
|
|
84
|
+
`@devdogsuga/brand/render`, reading meetings from the published
|
|
85
|
+
`@devdogsuga/events`. Files go to `--out` or the current directory, flat, as
|
|
86
|
+
`<name>-<format>.png`. There is no `page/*` group (the platform renders page
|
|
87
|
+
cards per request) and no `--default-out`.
|
|
88
|
+
- **`qr`** takes its options from `qrRequestSchema` in `@devdogsuga/brand/qr`,
|
|
89
|
+
the same schema `/console/qr` parses, so a new option reaches both. Every
|
|
90
|
+
schema field has a flag (`qr/options.ts` is typed over the schema, and a test
|
|
91
|
+
checks it): content, `--size`, `--margin`, `--color`, `--background`,
|
|
92
|
+
`--gradient`, `--shape`, `--error-level`, `--qr-version`, `--logo` (a preset,
|
|
93
|
+
`none`, or an image file) with `--logo-size`, `--logo-padding` and
|
|
94
|
+
`--logo-crop x,y,w,h`, and `--format` with any of svg, png, jpg, webp, avif
|
|
95
|
+
and tiff at once. It prints the same scannability warning as the page.
|
|
96
|
+
`/console/qr` stays, deprecated; new options are CLI-only.
|
|
97
|
+
- **`github rulesets|settings`** keep GitHub's rulesets and repository settings
|
|
98
|
+
as code, through `gh` and your own login. They must keep matching what the
|
|
99
|
+
platform's `server/github/rulesets.ts` and `teamSync.ts` assume.
|
|
100
|
+
- **`newsletter render|draft|send`** are described in the
|
|
101
|
+
[newsletter package](../newsletter/README.md). Drafts and sends use the club
|
|
102
|
+
mailbox (`devdogs@uga.edu`) with the officer's own Microsoft sign-in; there
|
|
103
|
+
is no `--mailbox`. `send` needs `--to` and always asks first, naming the issue
|
|
104
|
+
and every recipient; with no terminal, `--yes` answers it. The sign-in
|
|
105
|
+
borrows Thunderbird's public client ID (see `src/newsletter/oauth.ts`); if
|
|
106
|
+
Microsoft or UGA's tenant blocks it, sending needs a club-owned app
|
|
107
|
+
registration.
|
|
108
|
+
|
|
109
|
+
## Tiers, `--no-env`, CI
|
|
110
|
+
|
|
111
|
+
Nothing is ever asked at launch. `--tier <t>` (anywhere in argv) or
|
|
112
|
+
`DEPLOY_ENV` names the session's tier and so the env file loaded; with neither
|
|
113
|
+
the session is plain development (where `BWS_ACCESS_TOKEN` lives). `deploy`
|
|
114
|
+
refuses to run without one of the two.
|
|
115
|
+
|
|
116
|
+
`--no-env` loads no env files and does not look for a checkout: the caller's
|
|
117
|
+
environment is the environment. It is for `deploy write-env`, which creates the
|
|
118
|
+
file tier resolution would otherwise insist on reading, and for CI steps that
|
|
119
|
+
hold one narrow credential in the job's `env:` block.
|
|
120
|
+
|
|
121
|
+
With no terminal, or `CI=true`: no menu, no banner, plain lines, errors on
|
|
122
|
+
stderr, and every confirmation needs `--yes`. Against staging or production a
|
|
123
|
+
command that is not read-only asks once first (`--yes` answers it).
|
|
124
|
+
|
|
125
|
+
Each command checks for the secrets it uses up front: `deploy <app>` for
|
|
126
|
+
`CLOUDFLARE_API_TOKEN`, `deploy reconcile` for `CRON_SECRET`, `env` for the
|
|
127
|
+
Secrets Manager token (flag, environment, then the Bitwarden vault, which `env`
|
|
128
|
+
signs in to and unlocks itself, then asking).
|
|
129
|
+
|
|
130
|
+
## Layout
|
|
131
|
+
|
|
132
|
+
Like devtools: `src/<domain>/catalog.ts` (inert data) and `commands.ts`;
|
|
133
|
+
`src/catalog.ts` composes the tree, `src/cli.ts` maps names to handlers,
|
|
134
|
+
`src/launch-core.ts` settles the tier before any command is imported. The
|
|
135
|
+
shared core is the private `@devdogsuga/cli-core`, which `tsdown` inlines; every
|
|
136
|
+
other import must be declared here, and the build fails when one is not.
|
|
137
|
+
|
|
138
|
+
`env.ts` is the operator manifest (`BWS_ACCESS_TOKEN`, `CLOUDFLARE_API_TOKEN`
|
|
139
|
+
and friends). It is a copy of devtools' own, kept identical by a test, because
|
|
140
|
+
each CLI loads the one beside it.
|
|
141
|
+
|
|
142
|
+
Contract tests (`pnpm test:contract`) pack the real tarball, install it with
|
|
143
|
+
install scripts off outside any repo (and check the Bitwarden native module
|
|
144
|
+
loads), and run it again inside the fixture repo. They gate publishing.
|
|
145
|
+
|
|
146
|
+
Crash reporting is the same as devtools' (Sentry, scrubbed, off with
|
|
147
|
+
`DEVTOOLS_TELEMETRY=0`); the release is named `backstage@<version>`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for the `backstage` bin.
|
|
4
|
+
*
|
|
5
|
+
* Ships built JS (tsdown bundles `src/` and the private
|
|
6
|
+
* `@devdogsuga/cli-core` into a flat `dist/`) and runs it with plain `node`.
|
|
7
|
+
* It starts anywhere, a DevDogsUGA checkout or not: the DevDogsUGA packages it
|
|
8
|
+
* reads (`@devdogsuga/env`, `@devdogsuga/db`) are optional peers, resolved
|
|
9
|
+
* through the checkout only by the commands that need one.
|
|
10
|
+
*/
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const dist = join(dirname(fileURLToPath(import.meta.url)), "..", "dist");
|
|
15
|
+
|
|
16
|
+
const { launch } = await import(join(dist, "launch.js"));
|
|
17
|
+
try {
|
|
18
|
+
await launch(process.argv.slice(2));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
// Whatever escapes before a command is dispatched (repo discovery, env
|
|
21
|
+
// entry); the dispatch reports its own.
|
|
22
|
+
process.stderr.write(
|
|
23
|
+
`backstage: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
24
|
+
);
|
|
25
|
+
const { captureDevtoolsError } = await import(join(dist, "telemetry.js"));
|
|
26
|
+
await captureDevtoolsError(err);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region ../cli-core/src/args.ts
|
|
2
|
+
/**
|
|
3
|
+
* Positional arguments, with flag values excluded.
|
|
4
|
+
*
|
|
5
|
+
* Its own module because of one bug it prevents. The first positional is the
|
|
6
|
+
* subcommand, and `pull`, `push` and `audit` do very different things to live
|
|
7
|
+
* credentials. A naive `filter((a) => !a.startsWith("--"))` reads a flag's
|
|
8
|
+
* VALUE as a positional, so `env --file notes.env audit` fails loudly:
|
|
9
|
+
* `notes.env` is not a subcommand and the command refuses. The quiet version
|
|
10
|
+
* is the one that matters:
|
|
11
|
+
*
|
|
12
|
+
* env --file push audit
|
|
13
|
+
*
|
|
14
|
+
* which runs `push`, writing to Bitwarden and GitHub, when the caller asked
|
|
15
|
+
* for `audit`, which writes nothing at all.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Flags that consume the token after them.
|
|
19
|
+
*/
|
|
20
|
+
const VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
21
|
+
"--access-token",
|
|
22
|
+
"--format",
|
|
23
|
+
"--out",
|
|
24
|
+
"--version",
|
|
25
|
+
"--app",
|
|
26
|
+
"--apps",
|
|
27
|
+
"--base-url",
|
|
28
|
+
"--file",
|
|
29
|
+
"--source",
|
|
30
|
+
"--target",
|
|
31
|
+
"--tier",
|
|
32
|
+
"--cron",
|
|
33
|
+
"--workflow",
|
|
34
|
+
"--params",
|
|
35
|
+
"--port",
|
|
36
|
+
"--preview-url",
|
|
37
|
+
"--db-url",
|
|
38
|
+
"--user",
|
|
39
|
+
"--filter",
|
|
40
|
+
"--shell"
|
|
41
|
+
]);
|
|
42
|
+
function positionals(argv) {
|
|
43
|
+
const found = [];
|
|
44
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
45
|
+
const arg = argv[i];
|
|
46
|
+
if (!arg.startsWith("-")) {
|
|
47
|
+
found.push(arg);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const next = argv[i + 1];
|
|
51
|
+
if (VALUE_FLAGS.has(arg) && next !== void 0 && !next.startsWith("-")) i += 1;
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The value after `flag`, or `undefined` when the flag is absent or is
|
|
57
|
+
* followed by another flag instead of a value.
|
|
58
|
+
*/
|
|
59
|
+
function flagValue(rest, flag) {
|
|
60
|
+
const index = rest.indexOf(flag);
|
|
61
|
+
if (index === -1) return void 0;
|
|
62
|
+
const value = rest[index + 1];
|
|
63
|
+
return value && !value.startsWith("--") ? value : void 0;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { positionals as n, flagValue as t };
|