@devdogsuga/backstage 0.1.11 → 0.1.13
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 +8 -4
- package/dist/{cli-B7W_-WdD.js → cli-CqbqBpaT.js} +1 -1
- package/dist/{cli-B8Lshscc.js → cli-DIXhcRjn.js} +22 -22
- package/dist/{client-Djo8fRdS.js → client-CFhbS3nu.js} +2 -2
- package/dist/client-CJPfmtwW.js +2 -0
- package/dist/{commands-3HvxDq-2.js → commands-BEYZyI4Y.js} +2 -2
- package/dist/{commands-C6w8jm6a.js → commands-CTQjJ45_.js} +30 -9
- package/dist/{commands-DfWNSPra.js → commands-DfQKdPJY.js} +2 -2
- package/dist/{commands-B25Vafxz.js → commands-DsyAE7BO.js} +2 -2
- package/dist/{commands-D-Vvufb0.js → commands-H4DGzVqb.js} +3 -3
- package/dist/launch.js +1 -1
- package/dist/{meetings-BAX4Sils.js → meetings-QgVEoBIV.js} +1 -1
- package/dist/{read-Dd388anq.js → read-DMderLHG.js} +1 -1
- package/dist/{vault-BpYSYcuT.js → vault-DVmaQC_E.js} +54 -30
- package/package.json +8 -8
- package/dist/client-DCRfKvpP.js +0 -2
package/README.md
CHANGED
|
@@ -153,8 +153,10 @@ a Send another officer made creates a new one, with a new link, under yours.
|
|
|
153
153
|
|
|
154
154
|
The roster needs production's `DB_URL`: `--db-url`, else `.env.production` in a
|
|
155
155
|
checkout, else Secrets Manager. The Linear key: `--linear-token`, then
|
|
156
|
-
`LINEAR_API_KEY`, then the
|
|
157
|
-
a prompt
|
|
156
|
+
`LINEAR_API_KEY`, then the item "DevDogs Linear API key (backstage)" in your
|
|
157
|
+
personal vault, then a prompt, which offers to save it there. Use your own key,
|
|
158
|
+
never one shared through the DevDogs organization: Linear records every edit
|
|
159
|
+
under the key's owner, and creds ignores organization items of that name.
|
|
158
160
|
|
|
159
161
|
## Member data: `import` and `export`
|
|
160
162
|
|
|
@@ -254,8 +256,10 @@ command that is not read-only asks once first (`--yes` answers it).
|
|
|
254
256
|
Each command checks for the secrets it uses up front: `deploy <app>` for
|
|
255
257
|
`CLOUDFLARE_API_TOKEN`, `deploy reconcile` for `CRON_SECRET`, `deploy
|
|
256
258
|
prune-monitors` for `SENTRY_MONITORS_TOKEN` (skipped without it), `env` for the
|
|
257
|
-
Secrets Manager token (flag, environment, then
|
|
258
|
-
signs in to and unlocks itself, then asking).
|
|
259
|
+
Secrets Manager token (flag, environment, then your personal Bitwarden vault,
|
|
260
|
+
which `env` signs in to and unlocks itself, then asking). The token is each
|
|
261
|
+
person's own: the vault read skips organization items, and a save sets no
|
|
262
|
+
organization, so one person's token can be revoked without the rest.
|
|
259
263
|
|
|
260
264
|
## Layout
|
|
261
265
|
|
|
@@ -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-CJPfmtwW.js");
|
|
35
35
|
const secrets = await listSecrets(await projectIdFor(PRODUCTION_PROJECT));
|
|
36
36
|
return nonEmpty(secrets.find((s) => s.key === key)?.value);
|
|
37
37
|
}
|
|
@@ -6,7 +6,7 @@ import { a as wouldRunLine, n as isDryRun, t as dryRunKind } from "./dry-run-OzO
|
|
|
6
6
|
import { a as explainError, i as explain, n as bail, o as unwrap, r as errorMessage } from "./ui-RFZyr6k_.js";
|
|
7
7
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
8
8
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
9
|
-
import { a as listSecrets$1, c as updateSecret, i as createSecret, o as projectIdFor, r as byKey, s as setExplicitAccessToken } from "./client-
|
|
9
|
+
import { a as listSecrets$1, c as updateSecret, i as createSecret, o as projectIdFor, r as byKey, s as setExplicitAccessToken } from "./client-CFhbS3nu.js";
|
|
10
10
|
import { register } from "node:module";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
import { appendFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -3681,11 +3681,15 @@ function buildDesiredRulesets(actors) {
|
|
|
3681
3681
|
conditions: main$1,
|
|
3682
3682
|
bypass_actors: [
|
|
3683
3683
|
{
|
|
3684
|
-
actor_id: actors.
|
|
3684
|
+
actor_id: actors.focusLeadsTeamId,
|
|
3685
|
+
actor_type: "Team",
|
|
3686
|
+
bypass_mode: "pull_request"
|
|
3687
|
+
},
|
|
3688
|
+
{
|
|
3689
|
+
actor_id: actors.devopsTeamId,
|
|
3685
3690
|
actor_type: "Team",
|
|
3686
3691
|
bypass_mode: "pull_request"
|
|
3687
3692
|
},
|
|
3688
|
-
always(actors.devopsTeamId),
|
|
3689
3693
|
always(actors.adminsTeamId)
|
|
3690
3694
|
],
|
|
3691
3695
|
rules: [{
|
|
@@ -3698,7 +3702,7 @@ function buildDesiredRulesets(actors) {
|
|
|
3698
3702
|
target: "branch",
|
|
3699
3703
|
enforcement: "active",
|
|
3700
3704
|
conditions: main$1,
|
|
3701
|
-
bypass_actors: [always(actors.
|
|
3705
|
+
bypass_actors: [always(actors.adminsTeamId)],
|
|
3702
3706
|
rules: [{
|
|
3703
3707
|
type: "pull_request",
|
|
3704
3708
|
parameters: {
|
|
@@ -3716,7 +3720,7 @@ function buildDesiredRulesets(actors) {
|
|
|
3716
3720
|
target: "branch",
|
|
3717
3721
|
enforcement: "active",
|
|
3718
3722
|
conditions: main$1,
|
|
3719
|
-
bypass_actors: [always(actors.
|
|
3723
|
+
bypass_actors: [always(actors.adminsTeamId)],
|
|
3720
3724
|
rules: [{
|
|
3721
3725
|
type: "required_status_checks",
|
|
3722
3726
|
parameters: {
|
|
@@ -3752,6 +3756,7 @@ function buildDesiredRulesets(actors) {
|
|
|
3752
3756
|
exclude: ["refs/heads/main", "refs/heads/team/**"]
|
|
3753
3757
|
} },
|
|
3754
3758
|
bypass_actors: [
|
|
3759
|
+
always(actors.focusLeadsTeamId),
|
|
3755
3760
|
always(actors.devopsTeamId),
|
|
3756
3761
|
always(actors.adminsTeamId),
|
|
3757
3762
|
...actors.renovateAppId === void 0 ? [] : [{
|
|
@@ -3778,15 +3783,11 @@ function buildDesiredRulesets(actors) {
|
|
|
3778
3783
|
include: ["refs/heads/team/**"],
|
|
3779
3784
|
exclude: []
|
|
3780
3785
|
} },
|
|
3781
|
-
bypass_actors: [
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
},
|
|
3787
|
-
always(actors.devopsTeamId),
|
|
3788
|
-
always(actors.adminsTeamId)
|
|
3789
|
-
],
|
|
3786
|
+
bypass_actors: [{
|
|
3787
|
+
actor_id: actors.appId,
|
|
3788
|
+
actor_type: "Integration",
|
|
3789
|
+
bypass_mode: "always"
|
|
3790
|
+
}, always(actors.adminsTeamId)],
|
|
3790
3791
|
rules: [
|
|
3791
3792
|
{ type: "creation" },
|
|
3792
3793
|
{ type: "deletion" },
|
|
@@ -4006,7 +4007,6 @@ const DEFAULT_REPO$1 = "DevDogsUGA";
|
|
|
4006
4007
|
const DEFAULT_APP_SLUG = "devdogs-platform";
|
|
4007
4008
|
const DEFAULT_DEVOPS_SLUG = "devops";
|
|
4008
4009
|
const DEFAULT_ADMINS_SLUG = "admins";
|
|
4009
|
-
const DEFAULT_REVIEWERS_SLUG = "reviewers";
|
|
4010
4010
|
/** TASK-299 — installed separately from the platform App; may not exist yet. */
|
|
4011
4011
|
const DEFAULT_RENOVATE_SLUG = "renovate";
|
|
4012
4012
|
function parseOptions$1(argv) {
|
|
@@ -4085,14 +4085,14 @@ async function runGithubRulesets(argv) {
|
|
|
4085
4085
|
repo: opts.repo
|
|
4086
4086
|
};
|
|
4087
4087
|
let devopsTeamId;
|
|
4088
|
+
let focusLeadsTeamId;
|
|
4088
4089
|
let adminsTeamId;
|
|
4089
|
-
let reviewersTeamId;
|
|
4090
4090
|
let appId;
|
|
4091
4091
|
try {
|
|
4092
|
-
[devopsTeamId,
|
|
4092
|
+
[devopsTeamId, focusLeadsTeamId, adminsTeamId, appId] = await Promise.all([
|
|
4093
4093
|
resolveTeamId(opts.org, DEFAULT_DEVOPS_SLUG),
|
|
4094
|
+
resolveTeamId(opts.org, "focus-leads"),
|
|
4094
4095
|
resolveTeamId(opts.org, DEFAULT_ADMINS_SLUG),
|
|
4095
|
-
resolveTeamId(opts.org, DEFAULT_REVIEWERS_SLUG),
|
|
4096
4096
|
resolveAppId(opts.org, opts.appSlug)
|
|
4097
4097
|
]);
|
|
4098
4098
|
} catch (err) {
|
|
@@ -4107,8 +4107,8 @@ async function runGithubRulesets(argv) {
|
|
|
4107
4107
|
}
|
|
4108
4108
|
const actors = {
|
|
4109
4109
|
devopsTeamId,
|
|
4110
|
+
focusLeadsTeamId,
|
|
4110
4111
|
adminsTeamId,
|
|
4111
|
-
reviewersTeamId,
|
|
4112
4112
|
appId,
|
|
4113
4113
|
renovateAppId
|
|
4114
4114
|
};
|
|
@@ -5389,9 +5389,9 @@ const HANDLERS = {
|
|
|
5389
5389
|
graphics: async (rest) => (await import("./commands-BAh2_Nds.js")).handleGraphics(rest),
|
|
5390
5390
|
qr: async (rest) => (await import("./commands-DILvdMc2.js")).handleQr(rest),
|
|
5391
5391
|
newsletter: async (rest) => (await import("./commands-C3_hAcZS.js")).handleNewsletter(rest),
|
|
5392
|
-
creds: async (rest) => (await import("./commands-
|
|
5393
|
-
import: async (rest) => (await import("./commands-
|
|
5394
|
-
export: async (rest) => (await import("./commands-
|
|
5392
|
+
creds: async (rest) => (await import("./commands-CTQjJ45_.js")).handleCreds(rest),
|
|
5393
|
+
import: async (rest) => (await import("./commands-DfQKdPJY.js")).handleImport(rest),
|
|
5394
|
+
export: async (rest) => (await import("./commands-BEYZyI4Y.js")).handleExport(rest)
|
|
5395
5395
|
};
|
|
5396
5396
|
/**
|
|
5397
5397
|
* Names that were commands of the contributor CLI, refused with where they
|
|
@@ -2,7 +2,7 @@ import { w as findRepoRoot } from "./telemetry-RNK5Wu74.js";
|
|
|
2
2
|
import { n as loadEnv } from "./peers-DE07w6nT.js";
|
|
3
3
|
import { o as unwrap } from "./ui-RFZyr6k_.js";
|
|
4
4
|
import { t as EnvDocument } from "./document-DumoZXoO.js";
|
|
5
|
-
import { a as readTokenFromVault,
|
|
5
|
+
import { a as readTokenFromVault, s as saveTokenToVault, t as VAULT_ITEM_NAME } from "./vault-DVmaQC_E.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";
|
|
@@ -66,7 +66,7 @@ function looksLikeAccessToken(value) {
|
|
|
66
66
|
async function promptForToken() {
|
|
67
67
|
if (!process.stdin.isTTY) return void 0;
|
|
68
68
|
const typed = unwrap(await password({
|
|
69
|
-
message: "Paste
|
|
69
|
+
message: "Paste your own Secrets Manager access token (`admin` machine account)",
|
|
70
70
|
mask: "•",
|
|
71
71
|
validate: (v) => (v ?? "").trim() === "" ? "An access token is required." : void 0
|
|
72
72
|
})).trim();
|
|
@@ -2,8 +2,8 @@ import { n as isDryRun } from "./dry-run-OzO0jUhK.js";
|
|
|
2
2
|
import { o as unwrap, r as errorMessage, t as UsageError } from "./ui-RFZyr6k_.js";
|
|
3
3
|
import { i as exportParams, n as EXPORT_KINDS, r as auditFilters, t as EXPORTS } from "./queries-D3mTchag.js";
|
|
4
4
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
5
|
-
import { c as ProductionError, d as requireProductionKey, i as plural, l as connect, o as reportFailure, r as expandHome, s as say, t as canPrompt, u as dbError } from "./cli-
|
|
6
|
-
import { i as parseBound, n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-
|
|
5
|
+
import { c as ProductionError, d as requireProductionKey, i as plural, l as connect, o as reportFailure, r as expandHome, s as say, t as canPrompt, u as dbError } from "./cli-CqbqBpaT.js";
|
|
6
|
+
import { i as parseBound, n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-QgVEoBIV.js";
|
|
7
7
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
8
8
|
import { basename, dirname, join } from "node:path";
|
|
9
9
|
import { autocomplete, confirm, multiselect, text } from "@clack/prompts";
|
|
@@ -3,7 +3,7 @@ import { t as nonEmpty } from "./connection-D3XsOE3S.js";
|
|
|
3
3
|
import { a as explainError, i as explain, o as unwrap, r as errorMessage, t as UsageError } from "./ui-RFZyr6k_.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,
|
|
6
|
+
import { c as bwCommand, i as readPasswordFromVault, n as bwArgs, o as savePasswordToVault, r as openVault } from "./vault-DVmaQC_E.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-CJPfmtwW.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
|
}
|
|
@@ -660,6 +660,12 @@ function rosterQueryFor(url) {
|
|
|
660
660
|
}
|
|
661
661
|
};
|
|
662
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* In the officer's PERSONAL vault, never the DevDogs organization: a Linear
|
|
665
|
+
* key acts as the person who made it, so a shared one would put every edit to
|
|
666
|
+
* the document under one name. It would also be an organization login that
|
|
667
|
+
* `creds send` could hand out.
|
|
668
|
+
*/
|
|
663
669
|
const LINEAR_VAULT_ITEM = "DevDogs Linear API key (backstage)";
|
|
664
670
|
const LINEAR_API = "https://api.linear.app/graphql";
|
|
665
671
|
function cell(text) {
|
|
@@ -695,21 +701,36 @@ function renderReport(items, now, roster) {
|
|
|
695
701
|
return `${lines.join("\n")}\n`;
|
|
696
702
|
}
|
|
697
703
|
/**
|
|
698
|
-
* `--linear-token`, then `LINEAR_API_KEY`, then
|
|
699
|
-
*
|
|
704
|
+
* `--linear-token`, then `LINEAR_API_KEY`, then your personal vault item, then
|
|
705
|
+
* a prompt, which offers to save what was typed to your personal vault.
|
|
700
706
|
*/
|
|
701
707
|
async function resolveLinearToken(sources) {
|
|
702
|
-
const token = nonEmpty(sources.explicit) ?? nonEmpty(sources.env) ?? await (sources.fromVault ?? readLinearTokenFromVault)()
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
708
|
+
const token = nonEmpty(sources.explicit) ?? nonEmpty(sources.env) ?? await (sources.fromVault ?? readLinearTokenFromVault)();
|
|
709
|
+
if (token) {
|
|
710
|
+
registerSecret(token);
|
|
711
|
+
return token;
|
|
712
|
+
}
|
|
713
|
+
const typed = await (sources.prompt ?? promptForLinearToken)();
|
|
714
|
+
if (!typed) throw new CredsError(`No Linear API key. Set LINEAR_API_KEY, or store your own in your personal vault as "${LINEAR_VAULT_ITEM}".`);
|
|
715
|
+
registerSecret(typed);
|
|
716
|
+
await (sources.offerSave ?? offerToSaveLinearToken)(typed);
|
|
717
|
+
return typed;
|
|
706
718
|
}
|
|
707
719
|
function readLinearTokenFromVault() {
|
|
708
720
|
return readPasswordFromVault(LINEAR_VAULT_ITEM, "the Linear API key", "read the Linear API key", "LINEAR_API_KEY");
|
|
709
721
|
}
|
|
722
|
+
async function offerToSaveLinearToken(token) {
|
|
723
|
+
if (!process.stdin.isTTY) return;
|
|
724
|
+
if (!unwrap(await confirm({
|
|
725
|
+
message: `Save it to your personal Bitwarden vault as "DevDogs Linear API key (backstage)"?`,
|
|
726
|
+
initialValue: true
|
|
727
|
+
}))) return;
|
|
728
|
+
if (await savePasswordToVault("DevDogs Linear API key (backstage)", "Your own Linear API key, read by `backstage creds` to update the Shared Accounts document. Keep it in your personal vault, never the DevDogs organization: Linear records every edit under the key's owner.", token, "save the Linear API key")) log.success(`Stored as "${LINEAR_VAULT_ITEM}" in your vault.`);
|
|
729
|
+
else log.warn("Could not save it; continuing with the key you typed.");
|
|
730
|
+
}
|
|
710
731
|
async function promptForLinearToken() {
|
|
711
732
|
if (!process.stdin.isTTY) return void 0;
|
|
712
|
-
const value = unwrap(await password({ message: "Linear API key? (Settings → Security & access → API keys)" }));
|
|
733
|
+
const value = unwrap(await password({ message: "Your Linear API key? (Settings → Security & access → API keys)" }));
|
|
713
734
|
return nonEmpty(value.trim());
|
|
714
735
|
}
|
|
715
736
|
/** Replaces the document's content. Returns its URL. */
|
|
@@ -2,8 +2,8 @@ import { i as explain } from "./ui-RFZyr6k_.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-DsyAE7BO.js")).handleInvolvement(args);
|
|
6
|
+
if (what === "attendance") return (await import("./commands-H4DGzVqb.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;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as isDryRun } from "./dry-run-OzO0jUhK.js";
|
|
2
2
|
import { r as errorMessage, t as UsageError } from "./ui-RFZyr6k_.js";
|
|
3
3
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
4
|
-
import { a as readInputFile, d as requireProductionKey, f as withConnection, i as plural, n as confirmWrite, o as reportFailure, s as say, t as canPrompt } 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 readInputFile, d as requireProductionKey, f as withConnection, i as plural, n as confirmWrite, o as reportFailure, s as say, t as canPrompt } from "./cli-CqbqBpaT.js";
|
|
5
|
+
import { a as createAccounts, i as createAccountFor, n as accountFinder, o as readAccounts, t as parseCsv } from "./read-DMderLHG.js";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
//#region src/involvement/csv.ts
|
|
8
8
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as isDryRun } from "./dry-run-OzO0jUhK.js";
|
|
2
2
|
import { o as unwrap, r as errorMessage, t as UsageError } from "./ui-RFZyr6k_.js";
|
|
3
3
|
import { t as DONE } from "./dispatch-D048O65I.js";
|
|
4
|
-
import { a as readInputFile, c as ProductionError, d as requireProductionKey, f as withConnection, i as plural, l as connect, n as confirmWrite, o as reportFailure, s as say, t as canPrompt } 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 readInputFile, c as ProductionError, d as requireProductionKey, f as withConnection, i as plural, l as connect, n as confirmWrite, o as reportFailure, s as say, t as canPrompt } from "./cli-CqbqBpaT.js";
|
|
5
|
+
import { a as createAccounts, i as createAccountFor, n as accountFinder, r as accountsWith, t as parseCsv } from "./read-DMderLHG.js";
|
|
6
|
+
import { n as findMeetingsWith, r as pickMeeting, t as describeMeeting } from "./meetings-QgVEoBIV.js";
|
|
7
7
|
import { text } from "@clack/prompts";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
//#region src/attendance/plan.ts
|
package/dist/launch.js
CHANGED
|
@@ -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-DIXhcRjn.js");
|
|
326
326
|
await main(args);
|
|
327
327
|
} });
|
|
328
328
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as errorMessage } from "./ui-RFZyr6k_.js";
|
|
2
|
-
import { c as ProductionError, f as withConnection } from "./cli-
|
|
2
|
+
import { c as ProductionError, f as withConnection } from "./cli-CqbqBpaT.js";
|
|
3
3
|
//#region src/production/accounts.ts
|
|
4
4
|
/**
|
|
5
5
|
* Production accounts: reading them, finding one by email, and creating the
|
|
@@ -50,9 +50,14 @@ function bwCommand(args) {
|
|
|
50
50
|
* prerequisite.
|
|
51
51
|
*
|
|
52
52
|
* ⚠️ The token never passes through argv in either direction. Reads use
|
|
53
|
-
* `bw
|
|
53
|
+
* `bw list items --search <name>`, which puts only the item NAME on the command
|
|
54
54
|
* line, and writes pipe base64 JSON through **stdin**, which `bw create item`
|
|
55
55
|
* documents as an accepted input.
|
|
56
|
+
*
|
|
57
|
+
* ⚠️ Only the PERSONAL vault. A token read from, or saved to, an organization
|
|
58
|
+
* item would be one token for everybody who can see that item: every call made
|
|
59
|
+
* with it looks the same, and revoking one person means revoking everyone.
|
|
60
|
+
* Reads skip organization items and say so; writes set no organization.
|
|
56
61
|
*/
|
|
57
62
|
const run = promisify(execFile);
|
|
58
63
|
/**
|
|
@@ -177,20 +182,20 @@ function bwArgs(args, key) {
|
|
|
177
182
|
key
|
|
178
183
|
] : withNoInteraction;
|
|
179
184
|
}
|
|
180
|
-
/**
|
|
181
|
-
* The stored token, or `undefined` for every reason it might not be there.
|
|
182
|
-
*
|
|
183
|
-
* `bw get password` takes a search term and fails when it matches more than one
|
|
184
|
-
* item, which is the behaviour worth having: two items called something like
|
|
185
|
-
* this means somebody should look, not that this should guess.
|
|
186
|
-
*/
|
|
185
|
+
/** The stored token, or `undefined` for every reason it might not be there. */
|
|
187
186
|
async function readTokenFromVault() {
|
|
188
187
|
return readPasswordFromVault(VAULT_ITEM_NAME, "the access token", "look for the access token", "BWS_ACCESS_TOKEN");
|
|
189
188
|
}
|
|
190
189
|
/**
|
|
191
|
-
* The password of the one vault item named `itemName`, or
|
|
192
|
-
* every reason it might not be there. `what` names it in the
|
|
193
|
-
* finishes the sign-in and unlock questions.
|
|
190
|
+
* The password of the one PERSONAL vault item named exactly `itemName`, or
|
|
191
|
+
* `undefined` for every reason it might not be there. `what` names it in the
|
|
192
|
+
* spinner; `purpose` finishes the sign-in and unlock questions.
|
|
193
|
+
*
|
|
194
|
+
* `bw list items --search` rather than `bw get password`: `get` takes the
|
|
195
|
+
* first item it can see, organization items included, and an item of that
|
|
196
|
+
* name in an organization is precisely the shared token this must not use.
|
|
197
|
+
* Two personal items of the same name mean somebody should look, not that
|
|
198
|
+
* this should guess, so that is "not found" too.
|
|
194
199
|
*/
|
|
195
200
|
async function readPasswordFromVault(itemName, what, purpose, envVar) {
|
|
196
201
|
const status = await signInIfNeeded(await vaultStatus(), purpose);
|
|
@@ -202,34 +207,49 @@ async function readPasswordFromVault(itemName, what, purpose, envVar) {
|
|
|
202
207
|
if (status === "locked" && !key) return void 0;
|
|
203
208
|
const s = spinner();
|
|
204
209
|
s.start("Looking in your Bitwarden vault");
|
|
210
|
+
let items;
|
|
205
211
|
try {
|
|
206
212
|
const { stdout } = await run(...bwCommand(bwArgs([
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
], key)), {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
"list",
|
|
214
|
+
"items",
|
|
215
|
+
"--search",
|
|
216
|
+
itemName
|
|
217
|
+
], key)), {
|
|
218
|
+
shell: false,
|
|
219
|
+
maxBuffer: 16777216
|
|
220
|
+
});
|
|
221
|
+
items = JSON.parse(stdout).filter((item) => item.name === itemName);
|
|
222
|
+
} catch {
|
|
223
|
+
s.stop("Could not read your Bitwarden vault");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const personal = items.filter((item) => !item.organizationId);
|
|
227
|
+
if (personal.length > 1) {
|
|
228
|
+
s.stop(`More than one "${itemName}" in your vault; delete the extras`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const token = personal[0]?.login?.password?.trim();
|
|
232
|
+
if (token) {
|
|
217
233
|
s.stop(`Read ${what} from your vault ("${itemName}")`);
|
|
218
234
|
return token;
|
|
219
|
-
}
|
|
220
|
-
|
|
235
|
+
}
|
|
236
|
+
if (items.length > personal.length) {
|
|
237
|
+
s.stop(`"${itemName}" is in an organization, not your personal vault`);
|
|
238
|
+
log.warn(`Not using it: an organization item is one ${what} for everyone who can see it, so nothing it does can be told apart. Use your own, saved in your personal vault (no organization).`);
|
|
221
239
|
return;
|
|
222
240
|
}
|
|
241
|
+
s.stop("Nothing stored in the vault yet");
|
|
223
242
|
}
|
|
224
243
|
/**
|
|
225
|
-
*
|
|
244
|
+
* Saves `token` as a login item named `itemName` in the PERSONAL vault, the
|
|
245
|
+
* token arriving on **stdin** as base64 JSON.
|
|
226
246
|
*
|
|
227
247
|
* `bw create item` documents an encoded-JSON positional AND stdin. Using stdin
|
|
228
248
|
* is the difference between a live credential that is invisible and one that
|
|
229
|
-
* sits in `ps` output for the length of the call.
|
|
249
|
+
* sits in `ps` output for the length of the call. `organizationId: null` is
|
|
250
|
+
* what makes it personal, and it is not a parameter.
|
|
230
251
|
*/
|
|
231
|
-
async function
|
|
232
|
-
const purpose = "save the access token";
|
|
252
|
+
async function savePasswordToVault(itemName, notes, token, purpose) {
|
|
233
253
|
const status = await signInIfNeeded(await vaultStatus(), purpose);
|
|
234
254
|
if (status === "unavailable" || status === "unauthenticated") return false;
|
|
235
255
|
const key = await session(status, purpose);
|
|
@@ -239,8 +259,8 @@ async function saveTokenToVault(token) {
|
|
|
239
259
|
collectionIds: null,
|
|
240
260
|
folderId: null,
|
|
241
261
|
type: 1,
|
|
242
|
-
name:
|
|
243
|
-
notes
|
|
262
|
+
name: itemName,
|
|
263
|
+
notes,
|
|
244
264
|
favorite: false,
|
|
245
265
|
reprompt: 0,
|
|
246
266
|
login: {
|
|
@@ -268,10 +288,14 @@ async function saveTokenToVault(token) {
|
|
|
268
288
|
child.stdin.end(Buffer.from(JSON.stringify(item)).toString("base64"));
|
|
269
289
|
});
|
|
270
290
|
}
|
|
291
|
+
/** Saves the Secrets Manager access token. See {@link savePasswordToVault}. */
|
|
292
|
+
function saveTokenToVault(token) {
|
|
293
|
+
return savePasswordToVault(VAULT_ITEM_NAME, "Your own Bitwarden Secrets Manager access token for the `admin` machine account, read/write on preflight, staging and production.\n\nRead automatically by `backstage env`. Keep it in your personal vault, never an organization: one token per person is what keeps calls attributable and lets one person's be revoked alone. Never put this in a .env that leaves this machine: it unlocks all three projects, and the tool refuses to upload it.", token, "save the access token");
|
|
294
|
+
}
|
|
271
295
|
/** Why the vault could not be used, phrased as something to do about it. */
|
|
272
296
|
function explainVault(status, envVar = "BWS_ACCESS_TOKEN") {
|
|
273
297
|
if (status === "unavailable") return "The `bw` CLI is not installed, so the vault could not be checked. It ships with backstage, so reinstall it.";
|
|
274
298
|
if (status === "unauthenticated") return `You are not signed in to Bitwarden, and with no terminal there is nobody to sign in. Set ${envVar} instead.`;
|
|
275
299
|
}
|
|
276
300
|
//#endregion
|
|
277
|
-
export { readTokenFromVault as a, readPasswordFromVault as i, bwArgs as n,
|
|
301
|
+
export { readTokenFromVault as a, bwCommand as c, readPasswordFromVault as i, bwArgs as n, savePasswordToVault as o, openVault as r, saveTokenToVault as s, VAULT_ITEM_NAME as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devdogsuga/backstage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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).",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"postgres": "^3.4.9",
|
|
37
37
|
"react": "^19.2.8",
|
|
38
38
|
"satori": "^0.33.4",
|
|
39
|
-
"sharp": "0.35.
|
|
39
|
+
"sharp": "0.35.5",
|
|
40
40
|
"tsx": "^4.23.12",
|
|
41
41
|
"typescript": "^6.0.3",
|
|
42
42
|
"zod": "4.4.3",
|
|
43
|
-
"@devdogsuga/brand": "0.1.
|
|
43
|
+
"@devdogsuga/brand": "0.1.8",
|
|
44
44
|
"@devdogsuga/events": "0.1.7",
|
|
45
|
-
"@devdogsuga/
|
|
46
|
-
"@devdogsuga/
|
|
45
|
+
"@devdogsuga/telemetry": "0.1.4",
|
|
46
|
+
"@devdogsuga/newsletter": "0.1.15"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@devdogsuga/db": "*",
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"eslint": "^9.39.5",
|
|
65
65
|
"tsdown": "^0.23.0",
|
|
66
66
|
"vitest": "^4.1.11",
|
|
67
|
-
"@devdogsuga/
|
|
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-DCRfKvpP.js
DELETED