@devdogsuga/backstage 0.1.3 → 0.1.4
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 +45 -1
- package/dist/{catalog-BOBE4ee7.js → catalog-BuNLUu08.js} +145 -237
- package/dist/{cli-ZF4QgJro.js → cli-BI5Vn9sR.js} +16 -955
- package/dist/client-B7HhuTaA.js +429 -0
- package/dist/client-DzXQHd58.js +2 -0
- package/dist/commands-Dl3UlhBf.js +1118 -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 +3 -3
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { w as findRepoRoot } from "./telemetry-Bjoz29Hl.js";
|
|
2
|
+
import { n as loadEnv } from "./peers-B0KDkI6a.js";
|
|
3
|
+
import { o as unwrap } from "./ui-CdKo8mLw.js";
|
|
4
|
+
import { a as readTokenFromVault, c as EnvDocument, o as saveTokenToVault, t as VAULT_ITEM_NAME } from "./vault-C8LYJFOQ.js";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { log, password, select, text } from "@clack/prompts";
|
|
7
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
//#region src/bws/token.ts
|
|
10
|
+
/**
|
|
11
|
+
* Finding the Secrets Manager access token, in four places, in order.
|
|
12
|
+
*
|
|
13
|
+
* 1. `--access-token` explicit, and explicit beats ambient
|
|
14
|
+
* 2. `BWS_ACCESS_TOKEN` the environment, which includes your `.env`,
|
|
15
|
+
* since `with-env` loads it for every command
|
|
16
|
+
* 3. the Bitwarden Password Manager vault, via the bundled `bw`
|
|
17
|
+
* (`env` signs in and unlocks it for itself)
|
|
18
|
+
* 4. ask, and offer to save it to `.env` (the default) or the vault, so
|
|
19
|
+
* this is the last time
|
|
20
|
+
*
|
|
21
|
+
* The ordering logic is separated from the four implementations because it is
|
|
22
|
+
* the part that can be wrong invisibly. A chain that silently prefers a stale
|
|
23
|
+
* environment variable over a rotated vault entry authenticates as the wrong
|
|
24
|
+
* account and reports nothing, so the order is asserted rather than assumed.
|
|
25
|
+
*/
|
|
26
|
+
var NoAccessTokenError = class extends Error {};
|
|
27
|
+
async function resolveToken(sources) {
|
|
28
|
+
const announce = sources.onSource ?? (() => void 0);
|
|
29
|
+
if (sources.explicit) {
|
|
30
|
+
announce("flag");
|
|
31
|
+
return sources.explicit;
|
|
32
|
+
}
|
|
33
|
+
if (sources.env) {
|
|
34
|
+
announce("environment");
|
|
35
|
+
return sources.env;
|
|
36
|
+
}
|
|
37
|
+
const stored = await sources.fromVault();
|
|
38
|
+
if (stored) {
|
|
39
|
+
announce("vault");
|
|
40
|
+
return stored;
|
|
41
|
+
}
|
|
42
|
+
const typed = await sources.prompt();
|
|
43
|
+
if (!typed) throw new NoAccessTokenError("No access token. Put BWS_ACCESS_TOKEN in your .env (an interactive run offers to do this for you), pass --access-token, or store one in your Bitwarden vault. See docs/toolkit/guides/env/commands.md.");
|
|
44
|
+
announce("prompt");
|
|
45
|
+
if (await sources.offerSave()) try {
|
|
46
|
+
await sources.save(typed);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.warn(`Could not save to your vault: ${err instanceof Error ? err.message : String(err)}. Continuing with the token you typed.`);
|
|
49
|
+
}
|
|
50
|
+
return typed;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A weak shape check, to turn a paste error into a sentence rather than a 401.
|
|
54
|
+
*
|
|
55
|
+
* Machine account tokens are `0.<uuid>.<secret>:<secret>`. This checks the
|
|
56
|
+
* leading version segment only, and **warns rather than refuses**: the format
|
|
57
|
+
* is Bitwarden's to change, and a tool that rejects a valid token because its
|
|
58
|
+
* prefix moved is worse than one that passes a bad token to `bws` and lets the
|
|
59
|
+
* real error surface.
|
|
60
|
+
*/
|
|
61
|
+
function looksLikeAccessToken(value) {
|
|
62
|
+
return /^\d+\.[0-9a-f-]{36}\./i.test(value.trim());
|
|
63
|
+
}
|
|
64
|
+
/** The interactive half. Masked, and never echoed back. */
|
|
65
|
+
async function promptForToken() {
|
|
66
|
+
if (!process.stdin.isTTY) return void 0;
|
|
67
|
+
const typed = unwrap(await password({
|
|
68
|
+
message: "Paste the Secrets Manager access token for the `admin` account",
|
|
69
|
+
mask: "•",
|
|
70
|
+
validate: (v) => (v ?? "").trim() === "" ? "An access token is required." : void 0
|
|
71
|
+
})).trim();
|
|
72
|
+
if (!looksLikeAccessToken(typed)) log.warn("That does not look like a machine account access token (they start `0.<uuid>.`). Continuing anyway — if it is wrong, Bitwarden will say so.");
|
|
73
|
+
return typed;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/bws/retry.ts
|
|
77
|
+
/**
|
|
78
|
+
* Retrying exactly one failure class: HTTP 429, rate limiting.
|
|
79
|
+
*
|
|
80
|
+
* A 429 is the server saying "correct request, wrong minute", the only error
|
|
81
|
+
* where trying the same thing again IS the fix. Everything else (bad token,
|
|
82
|
+
* missing project, malformed input) re-throws untouched on the first attempt,
|
|
83
|
+
* because retrying those just triples the time to the real message.
|
|
84
|
+
*
|
|
85
|
+
* Written for the Secrets Manager calls, where the minute matters: a push is
|
|
86
|
+
* ~45 sequential writes plus a login, several targets get pushed back-to-back,
|
|
87
|
+
* and Bitwarden's identity endpoint rate-limits repeated access-token logins
|
|
88
|
+
* aggressively. The login half is also fixed at the source, since the client
|
|
89
|
+
* now caches its login in a state file, so the retry is the belt on that
|
|
90
|
+
* suspender.
|
|
91
|
+
*/
|
|
92
|
+
function isRateLimited(err) {
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
return /\b429\b|too many requests|rate.?limit/i.test(message);
|
|
95
|
+
}
|
|
96
|
+
const wait$1 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
97
|
+
async function withRateLimitRetry(op, { retries = 3, baseMs = 5e3, sleep = wait$1, doing }) {
|
|
98
|
+
for (let attempt = 0;; attempt++) try {
|
|
99
|
+
return await op();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (!isRateLimited(err) || attempt >= retries) throw err;
|
|
102
|
+
const ms = baseMs * 2 ** attempt;
|
|
103
|
+
log.warn(`Bitwarden rate-limited ${doing} (HTTP 429). Waiting ${ms / 1e3}s and retrying (${attempt + 1}/${retries})…`);
|
|
104
|
+
await sleep(ms);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/bws/pace.ts
|
|
109
|
+
const WRITE_GAP_MS = 1100;
|
|
110
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
111
|
+
function makePacer(now = Date.now, sleep = wait) {
|
|
112
|
+
let notBefore = 0;
|
|
113
|
+
let chain = Promise.resolve();
|
|
114
|
+
return (kind) => {
|
|
115
|
+
const gap = kind === "write" ? WRITE_GAP_MS : 350;
|
|
116
|
+
const turn = chain.then(async () => {
|
|
117
|
+
const pause = notBefore - now();
|
|
118
|
+
if (pause > 0) await sleep(pause);
|
|
119
|
+
notBefore = now() + gap;
|
|
120
|
+
});
|
|
121
|
+
chain = turn.catch(() => void 0);
|
|
122
|
+
return turn;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/bws/store.ts
|
|
127
|
+
/**
|
|
128
|
+
* Saving a prompted value into the developer's own `.env`.
|
|
129
|
+
*
|
|
130
|
+
* The development file only, by construction: the path is fixed to
|
|
131
|
+
* `fileFor("development")`, so no caller can point this at a vault target's
|
|
132
|
+
* file. Those are what `env push` uploads, and the values saved here
|
|
133
|
+
* (`BWS_ACCESS_TOKEN`, `BWS_ORG_ID`) exist so they never ride a push. The
|
|
134
|
+
* token is refused there by name anyway; this keeps the write from ever being
|
|
135
|
+
* the thing that needs refusing.
|
|
136
|
+
*
|
|
137
|
+
* `EnvDocument.set` revives the commented line the rendered file already
|
|
138
|
+
* carries (never-store keys ship as `# KEY=""` with their documentation), so a
|
|
139
|
+
* saved value lands under its own doc comment rather than appended bare.
|
|
140
|
+
*/
|
|
141
|
+
async function saveToDevEnv(key, value) {
|
|
142
|
+
const { fileFor } = await loadEnv();
|
|
143
|
+
const path = resolve(findRepoRoot(), fileFor("development"));
|
|
144
|
+
let doc;
|
|
145
|
+
try {
|
|
146
|
+
doc = EnvDocument.parse(await readFile(path, "utf8"));
|
|
147
|
+
} catch {
|
|
148
|
+
doc = EnvDocument.empty();
|
|
149
|
+
}
|
|
150
|
+
doc.set(key, value);
|
|
151
|
+
await writeFile(path, doc.toString());
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/bws/client.ts
|
|
155
|
+
/**
|
|
156
|
+
* Secrets Manager, through the official SDK. No `bws` binary to install.
|
|
157
|
+
*
|
|
158
|
+
* This wrapped the `bws` CLI until 2026-08-19. The CLI was the right call
|
|
159
|
+
* against the raw REST API: Secrets Manager is end-to-end encrypted, the
|
|
160
|
+
* server stores ciphertext, and the client derives the key that opens it from
|
|
161
|
+
* the access token, so `fetch` returns blobs and reimplementing the crypto is
|
|
162
|
+
* not a trade worth making. `@bitwarden/sdk-napi` is that same client-side
|
|
163
|
+
* crypto (the same Rust core the CLI wraps), loaded in-process, which buys two
|
|
164
|
+
* things the CLI could not:
|
|
165
|
+
*
|
|
166
|
+
* * **Nothing to install.** The SDK is a dependency of this package, so
|
|
167
|
+
* `pnpm install` is the whole setup. No "install bws from the releases
|
|
168
|
+
* page" step, and no version somebody's laptop drifted on.
|
|
169
|
+
* * **No credential in argv.** `bws secret create` took the VALUE as a
|
|
170
|
+
* positional argument, visible to `ps` for the length of the call, a
|
|
171
|
+
* documented property of the tool this wrapper could only apologize for.
|
|
172
|
+
* In-process values never touch a process table.
|
|
173
|
+
*
|
|
174
|
+
* ⚠️ Imported LAZILY, at the first real call. The SDK is a native module, and
|
|
175
|
+
* loading it at import time would tax every `cli:no-env` path, the CI guards
|
|
176
|
+
* included, with a `.node` binary none of them use.
|
|
177
|
+
*
|
|
178
|
+
* The one thing the SDK needs that the CLI did not: the ORGANIZATION ID. The
|
|
179
|
+
* CLI derived it from the access token's login response; the SDK's every list
|
|
180
|
+
* and create takes it as an argument, and nothing in its API discovers it. It
|
|
181
|
+
* is a public identifier (a UUID that confers nothing), read from `BWS_ORG_ID`.
|
|
182
|
+
* See the declaration in `packages/devtools/env.ts`.
|
|
183
|
+
*/
|
|
184
|
+
var BwsError = class extends Error {};
|
|
185
|
+
/**
|
|
186
|
+
* One pacer for the process: every Secrets Manager request funnels through
|
|
187
|
+
* it, spaced under the published per-IP limits so the 429 retry stays a
|
|
188
|
+
* backstop instead of the plan. See `pace.ts` for the numbers and sources.
|
|
189
|
+
*/
|
|
190
|
+
const pace = makePacer();
|
|
191
|
+
let explicitToken;
|
|
192
|
+
let resolved;
|
|
193
|
+
/** Where a just-typed token goes, picked in `offerSave`, used in `save`. */
|
|
194
|
+
let saveDestination = "no";
|
|
195
|
+
/** Records `--access-token`, before any command runs. */
|
|
196
|
+
function setExplicitAccessToken(token) {
|
|
197
|
+
explicitToken = token;
|
|
198
|
+
resolved = void 0;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The access token, found once per process.
|
|
202
|
+
*
|
|
203
|
+
* Memoized as a **promise**, not a value: resolution can prompt, and a single
|
|
204
|
+
* command makes several Secrets Manager calls. Caching the value alone would
|
|
205
|
+
* still let two concurrent calls open two prompts over each other.
|
|
206
|
+
*
|
|
207
|
+
* Whatever the source, the token reaches the SDK as a function argument in
|
|
208
|
+
* this process, never argv, never a child's environment.
|
|
209
|
+
*/
|
|
210
|
+
async function accessToken() {
|
|
211
|
+
resolved ??= resolveToken({
|
|
212
|
+
explicit: explicitToken,
|
|
213
|
+
env: process.env.BWS_ACCESS_TOKEN,
|
|
214
|
+
fromVault: readTokenFromVault,
|
|
215
|
+
prompt: promptForToken,
|
|
216
|
+
offerSave: async () => {
|
|
217
|
+
saveDestination = unwrap(await select({
|
|
218
|
+
message: "Save it, so this is the last time you paste it?",
|
|
219
|
+
options: [
|
|
220
|
+
{
|
|
221
|
+
value: "env",
|
|
222
|
+
label: "yes — into .env",
|
|
223
|
+
hint: "gitignored, this machine only; push refuses it by name"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
value: "vault",
|
|
227
|
+
label: "yes — into my Bitwarden vault",
|
|
228
|
+
hint: "needs `pnpm devtools bw login`; follows you across machines"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
value: "no",
|
|
232
|
+
label: "no — ask me again next time"
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
initialValue: "env"
|
|
236
|
+
}));
|
|
237
|
+
return saveDestination !== "no";
|
|
238
|
+
},
|
|
239
|
+
save: async (token) => {
|
|
240
|
+
if (saveDestination === "env") {
|
|
241
|
+
await saveToDevEnv("BWS_ACCESS_TOKEN", token);
|
|
242
|
+
log.success("Stored in .env — with-env loads it on every run.");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (await saveTokenToVault(token)) log.success(`Stored as "${VAULT_ITEM_NAME}" in your Bitwarden vault.`);
|
|
246
|
+
else log.warn("Could not save it. The command will continue with the token you typed; nothing was written to your vault.");
|
|
247
|
+
},
|
|
248
|
+
onSource: (source) => {
|
|
249
|
+
if (source === "flag") log.warn("--access-token puts a live credential in argv, where `ps` can read it for the length of the call, and in your shell history. Prefer BWS_ACCESS_TOKEN or the vault.");
|
|
250
|
+
}
|
|
251
|
+
}).catch((err) => {
|
|
252
|
+
resolved = void 0;
|
|
253
|
+
throw err instanceof NoAccessTokenError ? new BwsError(err.message) : err;
|
|
254
|
+
});
|
|
255
|
+
return resolved;
|
|
256
|
+
}
|
|
257
|
+
let resolvedOrgId;
|
|
258
|
+
/**
|
|
259
|
+
* The organization the machine account belongs to: the environment, else a
|
|
260
|
+
* prompt with an offer to save.
|
|
261
|
+
*
|
|
262
|
+
* Prompted rather than only refused because it is the one Secrets Manager
|
|
263
|
+
* input with nothing secret about it: a public UUID that identifies without
|
|
264
|
+
* authorizing. Where nobody can answer (a pipe), the named refusal below is
|
|
265
|
+
* the answer, because an SDK error about a malformed UUID would send somebody
|
|
266
|
+
* debugging the token, which is the one thing that is fine.
|
|
267
|
+
*/
|
|
268
|
+
function organizationId() {
|
|
269
|
+
resolvedOrgId ??= (async () => {
|
|
270
|
+
const fromEnv = process.env.BWS_ORG_ID;
|
|
271
|
+
if (fromEnv) return fromEnv;
|
|
272
|
+
if (!process.stdin.isTTY) throw new BwsError("BWS_ORG_ID is not set. The Secrets Manager SDK addresses everything by organization id — a public UUID, shown in the Secrets Manager URL (bitwarden.com/#/sm/<org-id>/...) and on the machine-account page. Put it in your .env; it identifies, it does not authorize.");
|
|
273
|
+
const typed = unwrap(await text({
|
|
274
|
+
message: "What is the Bitwarden organization id? (the UUID in the Secrets Manager URL: bitwarden.com/#/sm/<org-id>/...)",
|
|
275
|
+
validate: (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test((v ?? "").trim()) ? void 0 : "That is not a UUID."
|
|
276
|
+
})).trim();
|
|
277
|
+
try {
|
|
278
|
+
await saveToDevEnv("BWS_ORG_ID", typed);
|
|
279
|
+
log.success("Stored BWS_ORG_ID in .env.");
|
|
280
|
+
} catch (err) {
|
|
281
|
+
log.warn(`Could not save it to .env: ${err instanceof Error ? err.message : String(err)}. Continuing with the id you typed.`);
|
|
282
|
+
}
|
|
283
|
+
return typed;
|
|
284
|
+
})();
|
|
285
|
+
resolvedOrgId.catch(() => {
|
|
286
|
+
resolvedOrgId = void 0;
|
|
287
|
+
});
|
|
288
|
+
return resolvedOrgId;
|
|
289
|
+
}
|
|
290
|
+
/** The lazily-loaded, logged-in SDK client, one per process. */
|
|
291
|
+
let sdk;
|
|
292
|
+
/**
|
|
293
|
+
* Where the SDK caches its login, so a process is not a fresh authentication.
|
|
294
|
+
*
|
|
295
|
+
* ⚠️ This file is what stops the 429s. Bitwarden's identity endpoint
|
|
296
|
+
* rate-limits repeated access-token logins hard, and "push preflight, audit,
|
|
297
|
+
* push staging, audit, push production, audit" is six devtools processes in
|
|
298
|
+
* two minutes: six logins without this, ONE with it. The `bws` binary kept a
|
|
299
|
+
* state directory for exactly this reason, and the SDK migration dropped it.
|
|
300
|
+
* Per machine account (the token's client-id UUID is in the filename, public
|
|
301
|
+
* by format), under 0700 directories, and outside the repository, because it
|
|
302
|
+
* caches an auth token and must live where nothing syncs or commits it.
|
|
303
|
+
*/
|
|
304
|
+
function stateFileFor(accessToken) {
|
|
305
|
+
const clientId = /^\d+\.([0-9a-f-]{36})\./i.exec(accessToken)?.[1] ?? "default";
|
|
306
|
+
return join(homedir(), ".config", "devdogs-devtools", `bws-state-${clientId}.json`);
|
|
307
|
+
}
|
|
308
|
+
async function client() {
|
|
309
|
+
sdk ??= (async () => {
|
|
310
|
+
const token = await accessToken();
|
|
311
|
+
const { BitwardenClient } = await import("@bitwarden/sdk-napi");
|
|
312
|
+
const instance = new BitwardenClient();
|
|
313
|
+
try {
|
|
314
|
+
const stateFile = stateFileFor(token);
|
|
315
|
+
await mkdir(dirname(stateFile), {
|
|
316
|
+
recursive: true,
|
|
317
|
+
mode: 448
|
|
318
|
+
});
|
|
319
|
+
await withRateLimitRetry(async () => {
|
|
320
|
+
await pace("write");
|
|
321
|
+
return instance.auth().loginAccessToken(token, stateFile);
|
|
322
|
+
}, { doing: "the login" });
|
|
323
|
+
} catch (err) {
|
|
324
|
+
throw new BwsError(describeSdkFailure(err, "logging in"));
|
|
325
|
+
}
|
|
326
|
+
return instance;
|
|
327
|
+
})();
|
|
328
|
+
sdk.catch(() => {
|
|
329
|
+
sdk = void 0;
|
|
330
|
+
});
|
|
331
|
+
return sdk;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Turns an SDK failure into something with a next step in it.
|
|
335
|
+
*
|
|
336
|
+
* The two that actually happen are a rejected token and a token that does not
|
|
337
|
+
* cover the project. The latter surfaces as a bare "not found", because a
|
|
338
|
+
* project you cannot see and a project that does not exist are deliberately
|
|
339
|
+
* the same answer.
|
|
340
|
+
*/
|
|
341
|
+
function describeSdkFailure(err, doing) {
|
|
342
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
343
|
+
if (/\b429\b|too many requests/i.test(message)) return `${message}\n\nBitwarden rate-limited this even after the automatic retries. Wait a minute and re-run — the login is cached in a state file now, so re-running does not spend another authentication.`;
|
|
344
|
+
if (/404|not.?found/i.test(message)) return `${message}\n\nA 404 here usually means the access token is valid but its machine account is not assigned to this project — Secrets Manager reports 'invisible' and 'absent' identically.`;
|
|
345
|
+
if (/401|unauthoriz|invalid|expired/i.test(message)) return `${message}\n\nThe access token was rejected while ${doing}. It may have been revoked or belong to another organization.`;
|
|
346
|
+
return message || `Secrets Manager failed with no message while ${doing}.`;
|
|
347
|
+
}
|
|
348
|
+
function iso(value) {
|
|
349
|
+
if (value instanceof Date) return value.toISOString();
|
|
350
|
+
if (typeof value === "string" && value !== "") return value;
|
|
351
|
+
}
|
|
352
|
+
/** Resolves a project name to its id. Names are unique within an org. */
|
|
353
|
+
async function projectIdFor(name) {
|
|
354
|
+
const c = await client();
|
|
355
|
+
const org = await organizationId();
|
|
356
|
+
let projects;
|
|
357
|
+
try {
|
|
358
|
+
projects = (await withRateLimitRetry(async () => {
|
|
359
|
+
await pace("read");
|
|
360
|
+
return c.projects().list(org);
|
|
361
|
+
}, { doing: "listing projects" })).data;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
throw new BwsError(describeSdkFailure(err, "listing projects"));
|
|
364
|
+
}
|
|
365
|
+
const match = projects.find((p) => p.name === name);
|
|
366
|
+
if (!match) {
|
|
367
|
+
const visible = projects.map((p) => p.name).sort();
|
|
368
|
+
throw new BwsError(`No project named "${name}".\n` + (visible.length > 0 ? `This token can see: ${visible.join(", ")}` : "This token can see no projects at all, which usually means the machine account has no project assignments yet."));
|
|
369
|
+
}
|
|
370
|
+
return match.id;
|
|
371
|
+
}
|
|
372
|
+
async function listSecrets(projectId) {
|
|
373
|
+
const c = await client();
|
|
374
|
+
const org = await organizationId();
|
|
375
|
+
try {
|
|
376
|
+
const identifiers = (await withRateLimitRetry(async () => {
|
|
377
|
+
await pace("read");
|
|
378
|
+
return c.secrets().list(org);
|
|
379
|
+
}, { doing: "listing secrets" })).data;
|
|
380
|
+
if (identifiers.length === 0) return [];
|
|
381
|
+
return (await withRateLimitRetry(async () => {
|
|
382
|
+
await pace("read");
|
|
383
|
+
return c.secrets().getByIds(identifiers.map((i) => i.id));
|
|
384
|
+
}, { doing: "reading secrets" })).data.filter((s) => s.projectId === projectId).map((s) => ({
|
|
385
|
+
id: s.id,
|
|
386
|
+
key: s.key,
|
|
387
|
+
value: s.value,
|
|
388
|
+
note: s.note,
|
|
389
|
+
projectId,
|
|
390
|
+
revisionDate: iso(s.revisionDate)
|
|
391
|
+
}));
|
|
392
|
+
} catch (err) {
|
|
393
|
+
throw new BwsError(describeSdkFailure(err, "listing secrets"));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function createSecret(projectId, key, value, note) {
|
|
397
|
+
const c = await client();
|
|
398
|
+
const org = await organizationId();
|
|
399
|
+
try {
|
|
400
|
+
await withRateLimitRetry(async () => {
|
|
401
|
+
await pace("write");
|
|
402
|
+
return c.secrets().create(org, key, value, note, [projectId]);
|
|
403
|
+
}, { doing: `creating ${key}` });
|
|
404
|
+
} catch (err) {
|
|
405
|
+
throw new BwsError(describeSdkFailure(err, `creating ${key}`));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Takes the whole secret rather than an id: the SDK's update is a full
|
|
410
|
+
* replace (organization, key, project list included), and every caller
|
|
411
|
+
* already holds the listed secret it is updating.
|
|
412
|
+
*/
|
|
413
|
+
async function updateSecret(secret, value, note) {
|
|
414
|
+
const c = await client();
|
|
415
|
+
const org = await organizationId();
|
|
416
|
+
try {
|
|
417
|
+
await withRateLimitRetry(async () => {
|
|
418
|
+
await pace("write");
|
|
419
|
+
return c.secrets().update(org, secret.id, secret.key, value, note, [secret.projectId]);
|
|
420
|
+
}, { doing: `updating ${secret.key}` });
|
|
421
|
+
} catch (err) {
|
|
422
|
+
throw new BwsError(describeSdkFailure(err, `updating ${secret.key}`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function byKey(secrets) {
|
|
426
|
+
return new Map(secrets.map((s) => [s.key, s]));
|
|
427
|
+
}
|
|
428
|
+
//#endregion
|
|
429
|
+
export { listSecrets as a, updateSecret as c, createSecret as i, accessToken as n, projectIdFor as o, byKey as r, setExplicitAccessToken as s, BwsError as t };
|