@devdogsuga/devtools 0.1.25 → 0.1.27

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.
Files changed (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
package/dist/bws/retry.js DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Retrying exactly one failure class: HTTP 429, rate limiting.
3
- *
4
- * A 429 is the server saying "correct request, wrong minute", the only error
5
- * where trying the same thing again IS the fix. Everything else (bad token,
6
- * missing project, malformed input) re-throws untouched on the first attempt,
7
- * because retrying those just triples the time to the real message.
8
- *
9
- * Written for the Secrets Manager calls, where the minute matters: a push is
10
- * ~45 sequential writes plus a login, several targets get pushed back-to-back,
11
- * and Bitwarden's identity endpoint rate-limits repeated access-token logins
12
- * aggressively. The login half is also fixed at the source, since the client
13
- * now caches its login in a state file, so the retry is the belt on that
14
- * suspender.
15
- */
16
- import { log } from "@clack/prompts";
17
- export function isRateLimited(err) {
18
- const message = err instanceof Error ? err.message : String(err);
19
- return /\b429\b|too many requests|rate.?limit/i.test(message);
20
- }
21
- const wait = (ms) => new Promise((r) => setTimeout(r, ms));
22
- export async function withRateLimitRetry(op, { retries = 3, baseMs = 5_000, sleep = wait, doing }) {
23
- for (let attempt = 0;; attempt++) {
24
- try {
25
- return await op();
26
- }
27
- catch (err) {
28
- if (!isRateLimited(err) || attempt >= retries)
29
- throw err;
30
- const ms = baseMs * 2 ** attempt;
31
- // A silent 20-second stall reads as a hang, and a command that appears
32
- // hung gets Ctrl-C'd, turning a wait the server asked for into a
33
- // half-finished push.
34
- log.warn(`Bitwarden rate-limited ${doing} (HTTP 429). Waiting ${ms / 1000}s ` +
35
- `and retrying (${attempt + 1}/${retries})…`);
36
- await sleep(ms);
37
- }
38
- }
39
- }
@@ -1 +0,0 @@
1
- export declare function saveToDevEnv(key: string, value: string): Promise<void>;
package/dist/bws/store.js DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * Saving a prompted value into the developer's own `.env`.
3
- *
4
- * The development file only, by construction: the path is fixed to
5
- * `fileFor("development")`, so no caller can point this at a vault target's
6
- * file. Those are what `env push` uploads, and the values saved here
7
- * (`BWS_ACCESS_TOKEN`, `BWS_ORG_ID`) exist so they never ride a push. The
8
- * token is refused there by name anyway; this keeps the write from ever being
9
- * the thing that needs refusing.
10
- *
11
- * `EnvDocument.set` revives the commented line the rendered file already
12
- * carries (never-store keys ship as `# KEY=""` with their documentation), so a
13
- * saved value lands under its own doc comment rather than appended bare.
14
- */
15
- import { readFile, writeFile } from "node:fs/promises";
16
- import { resolve } from "node:path";
17
- import { EnvDocument } from "../env/document.js";
18
- import { findRepoRoot } from "../repo/root.js";
19
- import { loadEnv } from "../repo/peers.js";
20
- export async function saveToDevEnv(key, value) {
21
- const { fileFor } = await loadEnv();
22
- const path = resolve(findRepoRoot(), fileFor("development"));
23
- let doc;
24
- try {
25
- doc = EnvDocument.parse(await readFile(path, "utf8"));
26
- }
27
- catch {
28
- doc = EnvDocument.empty();
29
- }
30
- doc.set(key, value);
31
- await writeFile(path, doc.toString());
32
- }
@@ -1,31 +0,0 @@
1
- export declare class NoAccessTokenError extends Error {
2
- }
3
- export interface TokenSources {
4
- /** From `--access-token`. */
5
- explicit?: string;
6
- /** From `process.env.BWS_ACCESS_TOKEN`. */
7
- env?: string;
8
- /** `undefined` for every reason the vault might not have it. */
9
- fromVault: () => Promise<string | undefined>;
10
- /** `undefined` when there is nobody to ask. */
11
- prompt: () => Promise<string | undefined>;
12
- /** Asked only after a successful prompt. */
13
- offerSave: () => Promise<boolean>;
14
- save: (token: string) => Promise<void>;
15
- /** Where the token came from, for the caller to report. */
16
- onSource?: (source: TokenSource) => void;
17
- }
18
- export type TokenSource = "flag" | "environment" | "vault" | "prompt";
19
- export declare function resolveToken(sources: TokenSources): Promise<string>;
20
- /**
21
- * A weak shape check, to turn a paste error into a sentence rather than a 401.
22
- *
23
- * Machine account tokens are `0.<uuid>.<secret>:<secret>`. This checks the
24
- * leading version segment only, and **warns rather than refuses**: the format
25
- * is Bitwarden's to change, and a tool that rejects a valid token because its
26
- * prefix moved is worse than one that passes a bad token to `bws` and lets the
27
- * real error surface.
28
- */
29
- export declare function looksLikeAccessToken(value: string): boolean;
30
- /** The interactive half. Masked, and never echoed back. */
31
- export declare function promptForToken(): Promise<string | undefined>;
package/dist/bws/token.js DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * Finding the Secrets Manager access token, in four places, in order.
3
- *
4
- * 1. `--access-token` explicit, and explicit beats ambient
5
- * 2. `BWS_ACCESS_TOKEN` the environment, which includes your `.env`,
6
- * since `with-env` loads it for every command
7
- * 3. the Bitwarden Password Manager vault, via the bundled `bw`
8
- * 4. ask, and offer to save it to `.env` (the default) or the vault, so
9
- * this is the last time
10
- *
11
- * The ordering logic is separated from the four implementations because it is
12
- * the part that can be wrong invisibly. A chain that silently prefers a stale
13
- * environment variable over a rotated vault entry authenticates as the wrong
14
- * account and reports nothing, so the order is asserted rather than assumed.
15
- */
16
- import { log, password } from "@clack/prompts";
17
- import { unwrap } from "../ui.js";
18
- export class NoAccessTokenError extends Error {
19
- }
20
- export async function resolveToken(sources) {
21
- const announce = sources.onSource ?? (() => undefined);
22
- // Explicit beats ambient. Someone who passes the flag while an environment
23
- // variable is set is overriding on purpose, and silently ignoring them would
24
- // point the command at the account they were trying to avoid.
25
- if (sources.explicit) {
26
- announce("flag");
27
- return sources.explicit;
28
- }
29
- if (sources.env) {
30
- announce("environment");
31
- return sources.env;
32
- }
33
- const stored = await sources.fromVault();
34
- if (stored) {
35
- announce("vault");
36
- return stored;
37
- }
38
- const typed = await sources.prompt();
39
- if (!typed) {
40
- throw new NoAccessTokenError("No access token. Put BWS_ACCESS_TOKEN in your .env (an interactive " +
41
- "run offers to do this for you), pass --access-token, or store one " +
42
- "in your Bitwarden vault. See docs/toolkit/guides/env/commands.md.");
43
- }
44
- announce("prompt");
45
- // Offered only for a token that was just typed. Saving one that came from a
46
- // flag or the environment would copy a credential into a vault nobody asked
47
- // to put it in.
48
- if (await sources.offerSave()) {
49
- // A failed vault write must not lose the token. Somebody just pasted a live
50
- // credential; making them do it again because a write failed is the worst
51
- // moment to be strict, and the command itself is unaffected.
52
- try {
53
- await sources.save(typed);
54
- }
55
- catch (err) {
56
- log.warn(`Could not save to your vault: ${err instanceof Error ? err.message : String(err)}. ` +
57
- "Continuing with the token you typed.");
58
- }
59
- }
60
- return typed;
61
- }
62
- /**
63
- * A weak shape check, to turn a paste error into a sentence rather than a 401.
64
- *
65
- * Machine account tokens are `0.<uuid>.<secret>:<secret>`. This checks the
66
- * leading version segment only, and **warns rather than refuses**: the format
67
- * is Bitwarden's to change, and a tool that rejects a valid token because its
68
- * prefix moved is worse than one that passes a bad token to `bws` and lets the
69
- * real error surface.
70
- */
71
- export function looksLikeAccessToken(value) {
72
- return /^\d+\.[0-9a-f-]{36}\./i.test(value.trim());
73
- }
74
- /** The interactive half. Masked, and never echoed back. */
75
- export async function promptForToken() {
76
- if (!process.stdin.isTTY)
77
- return undefined;
78
- const typed = unwrap(await password({
79
- message: "Paste the Secrets Manager access token for the `admin` account",
80
- mask: "•",
81
- validate: (v) => (v ?? "").trim() === "" ? "An access token is required." : undefined,
82
- })).trim();
83
- if (!looksLikeAccessToken(typed)) {
84
- log.warn("That does not look like a machine account access token (they start " +
85
- "`0.<uuid>.`). Continuing anyway — if it is wrong, Bitwarden will say so.");
86
- }
87
- return typed;
88
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * The item this looks for, and creates.
3
- *
4
- * Named for what it unlocks rather than for this tool, because whoever finds it
5
- * in the vault six months from now needs to know what it is, not which script
6
- * wrote it.
7
- */
8
- export declare const VAULT_ITEM_NAME = "DevDogs Secrets Manager access token (admin)";
9
- export type VaultStatus = "unavailable" | "unauthenticated" | "locked" | "unlocked";
10
- /** `bw status`, or `unavailable` when the CLI is not installed. */
11
- export declare function vaultStatus(): Promise<VaultStatus>;
12
- /**
13
- * The stored token, or `undefined` for every reason it might not be there.
14
- *
15
- * `bw get password` takes a search term and fails when it matches more than one
16
- * item, which is the behaviour worth having: two items called something like
17
- * this means somebody should look, not that this should guess.
18
- */
19
- export declare function readTokenFromVault(): Promise<string | undefined>;
20
- /**
21
- * Creates the item, with the token arriving on **stdin** as base64 JSON.
22
- *
23
- * `bw create item` documents an encoded-JSON positional AND stdin. Using stdin
24
- * is the difference between a live credential that is invisible and one that
25
- * sits in `ps` output for the length of the call.
26
- */
27
- export declare function saveTokenToVault(token: string): Promise<boolean>;
28
- /** Why the vault could not be used, phrased as something to do about it. */
29
- export declare function explainVault(status: VaultStatus): string | undefined;
package/dist/bws/vault.js DELETED
@@ -1,200 +0,0 @@
1
- /**
2
- * The Bitwarden **Password Manager** vault, via the `bw` CLI.
3
- *
4
- * A different product from Secrets Manager and a different CLI, which is the
5
- * point: `bws` has no user authentication at all, so the access token it needs
6
- * has to be held somewhere a person can log in to. That somewhere is the
7
- * Password Manager vault, and this module reads it back rather than making
8
- * somebody paste a token every session.
9
- *
10
- * Everything here degrades to `undefined`. A missing `bw`, a locked vault, a
11
- * declined unlock, a missing item: none of them are errors, because each has a
12
- * good fallback, which is to ask. Throwing would turn a convenience into a
13
- * prerequisite.
14
- *
15
- * ⚠️ The token never passes through argv in either direction. Reads use
16
- * `bw get password <search>`, which puts only the item NAME on the command
17
- * line, and writes pipe base64 JSON through **stdin**, which `bw create item`
18
- * documents as an accepted input.
19
- */
20
- import { execFile, spawn } from "node:child_process";
21
- import { promisify } from "node:util";
22
- import { confirm, log, spinner } from "@clack/prompts";
23
- import { unwrap } from "../ui.js";
24
- import { bwCommand } from "./bw.js";
25
- const run = promisify(execFile);
26
- /**
27
- * The item this looks for, and creates.
28
- *
29
- * Named for what it unlocks rather than for this tool, because whoever finds it
30
- * in the vault six months from now needs to know what it is, not which script
31
- * wrote it.
32
- */
33
- export const VAULT_ITEM_NAME = "DevDogs Secrets Manager access token (admin)";
34
- /** `bw status`, or `unavailable` when the CLI is not installed. */
35
- export async function vaultStatus() {
36
- try {
37
- const { stdout } = await run(...bwCommand(bwArgs(["status", "--response"])), {
38
- shell: false,
39
- });
40
- // `--response` wraps the payload; the bare form is also accepted, so read
41
- // whichever shape came back rather than depending on one.
42
- const parsed = JSON.parse(stdout);
43
- const status = ("status" in parsed ? parsed.status : undefined) ??
44
- ("data" in parsed ? parsed.data?.template?.status : undefined);
45
- if (status === "unlocked")
46
- return "unlocked";
47
- if (status === "locked")
48
- return "locked";
49
- return "unauthenticated";
50
- }
51
- catch (err) {
52
- if (err.code === "ENOENT")
53
- return "unavailable";
54
- return "unauthenticated";
55
- }
56
- }
57
- /**
58
- * A usable session key, unlocking if the person agrees.
59
- *
60
- * `BW_SESSION` is preferred and silent. Otherwise this ASKS before unlocking:
61
- * a tool that pops a master-password prompt unannounced is shaped exactly like
62
- * the thing people are told never to type their master password into.
63
- *
64
- * The password is typed straight into `bw`. stdin is inherited, so it never
65
- * passes through this process.
66
- */
67
- async function session(status) {
68
- if (process.env.BW_SESSION)
69
- return process.env.BW_SESSION;
70
- if (status !== "locked")
71
- return undefined;
72
- if (!process.stdin.isTTY)
73
- return undefined;
74
- const ok = unwrap(await confirm({
75
- message: "Your Bitwarden vault is locked. Unlock it to look for the access token?",
76
- initialValue: true,
77
- }));
78
- if (!ok)
79
- return undefined;
80
- return new Promise((resolve) => {
81
- // stdin inherited so the master password goes to `bw` and not through here;
82
- // stdout piped so the session key can be captured rather than printed.
83
- const child = spawn(...bwCommand(["unlock", "--raw"]), {
84
- stdio: ["inherit", "pipe", "inherit"],
85
- shell: false,
86
- });
87
- let out = "";
88
- child.stdout.on("data", (c) => (out += c.toString()));
89
- child.on("error", () => resolve(undefined));
90
- child.on("close", (code) => resolve(code === 0 && out.trim() !== "" ? out.trim() : undefined));
91
- });
92
- }
93
- /**
94
- * Adds the session key, and `--nointeraction` always.
95
- *
96
- * The second one is not belt-and-braces. `bw` prompts for a master password on
97
- * stdin whenever the vault is locked, as `bw get template item` does against a
98
- * locked vault, and this module runs `bw` with piped stdio, where that prompt
99
- * has nobody to answer it and hangs until something gives up. `--nointeraction`
100
- * turns that into an immediate non-zero exit, which every caller here already
101
- * treats as "not available, ask instead".
102
- */
103
- function bwArgs(args, key) {
104
- const withNoInteraction = [...args, "--nointeraction"];
105
- return key ? [...withNoInteraction, "--session", key] : withNoInteraction;
106
- }
107
- /**
108
- * The stored token, or `undefined` for every reason it might not be there.
109
- *
110
- * `bw get password` takes a search term and fails when it matches more than one
111
- * item, which is the behaviour worth having: two items called something like
112
- * this means somebody should look, not that this should guess.
113
- */
114
- export async function readTokenFromVault() {
115
- const status = await vaultStatus();
116
- if (status === "unavailable" || status === "unauthenticated") {
117
- // Said out loud, and only here. By the time this runs the chain has already
118
- // found nothing in the flag or the environment, so the person is about to
119
- // be asked to paste a token. That is when knowing the vault could have
120
- // answered instead is worth something.
121
- log.info(explainVault(status));
122
- return undefined;
123
- }
124
- const key = await session(status);
125
- if (status === "locked" && !key)
126
- return undefined;
127
- const s = spinner();
128
- s.start("Looking in your Bitwarden vault");
129
- try {
130
- const { stdout } = await run(...bwCommand(bwArgs(["get", "password", VAULT_ITEM_NAME, "--raw"], key)), { shell: false });
131
- const token = stdout.trim();
132
- if (token === "") {
133
- s.stop("Nothing stored in the vault yet");
134
- return undefined;
135
- }
136
- s.stop(`Read the access token from your vault ("${VAULT_ITEM_NAME}")`);
137
- return token;
138
- }
139
- catch {
140
- // "not found" and "more than one match" both land here, and both mean the
141
- // same thing to the caller: ask instead.
142
- s.stop("No single matching item in your vault");
143
- return undefined;
144
- }
145
- }
146
- /**
147
- * Creates the item, with the token arriving on **stdin** as base64 JSON.
148
- *
149
- * `bw create item` documents an encoded-JSON positional AND stdin. Using stdin
150
- * is the difference between a live credential that is invisible and one that
151
- * sits in `ps` output for the length of the call.
152
- */
153
- export async function saveTokenToVault(token) {
154
- const status = await vaultStatus();
155
- if (status === "unavailable" || status === "unauthenticated")
156
- return false;
157
- const key = await session(status);
158
- if (status === "locked" && !key)
159
- return false;
160
- const item = {
161
- organizationId: null,
162
- collectionIds: null,
163
- folderId: null,
164
- type: 1, // login, so `bw get password` can retrieve it in one call
165
- name: VAULT_ITEM_NAME,
166
- notes: "Bitwarden Secrets Manager access token for the `admin` machine account, " +
167
- "read/write on preflight, staging and production.\n\n" +
168
- "Read automatically by `pnpm devtools env`. Never put this in a .env " +
169
- "file: it unlocks all three projects, and the tool refuses to upload it.",
170
- favorite: false,
171
- reprompt: 0,
172
- login: { username: null, password: token, totp: null },
173
- };
174
- return new Promise((resolve) => {
175
- const child = spawn(...bwCommand(bwArgs(["create", "item"], key)), {
176
- stdio: ["pipe", "ignore", "pipe"],
177
- shell: false,
178
- });
179
- let stderr = "";
180
- child.stderr.on("data", (c) => (stderr += c.toString()));
181
- child.on("error", () => resolve(false));
182
- child.on("close", (code) => {
183
- if (code !== 0 && stderr.trim() !== "")
184
- log.warn(stderr.trim());
185
- resolve(code === 0);
186
- });
187
- child.stdin.end(Buffer.from(JSON.stringify(item)).toString("base64"));
188
- });
189
- }
190
- /** Why the vault could not be used, phrased as something to do about it. */
191
- export function explainVault(status) {
192
- if (status === "unavailable") {
193
- return ("The `bw` CLI is not installed, so the vault could not be checked. " +
194
- "`pnpm devtools bw login` — the CLI is a devtools dependency, nothing to install.");
195
- }
196
- if (status === "unauthenticated") {
197
- return "The `bw` CLI is not signed in. Run `bw login`.";
198
- }
199
- return undefined;
200
- }
package/dist/catalog.d.ts DELETED
@@ -1,57 +0,0 @@
1
- /**
2
- * What can be reported on this instance, and for what reasons?
3
- *
4
- * Neither half of that question had an answer in the CLI before. `doctor` comes
5
- * closest, but it runs `conformance_check()` for a single app and reports
6
- * pass/fail per content type, a diagnostic rather than a listing, and it says
7
- * nothing about reasons at all.
8
- *
9
- * It matters more now than it used to, because both halves stopped being
10
- * editable at runtime. Content types are derived from each app's own schema and
11
- * report reasons are a platform-owned enum, so there is no configuration page
12
- * to open and look at. The database is the only source of truth, and this is
13
- * how a contributor reads it. `docs/platform/reporting-and-feedback.md` carries
14
- * a copy of the reason list for people who are not at a terminal, but that copy
15
- * is maintained by hand and says so. This is what settles a disagreement.
16
- *
17
- * Both calls run through a moderator client rather than an admin client.
18
- * `list_content_types` is `security definer` behind a `canModerate` check, so
19
- * an anonymous or ordinary client sees nothing, and `list_report_reasons` is
20
- * `security invoker` and subject to RLS. Using a moderator means what prints
21
- * here is what a moderator would actually see, rather than what a superuser
22
- * can. `moderation.ts`'s `withTemporaryModerator` is what supplies that
23
- * client — this module reads with it, it does not sign in on its own.
24
- */
25
- import type { DevtoolsClient } from "./instance.js";
26
- export interface CatalogReason {
27
- reason: string;
28
- title: string;
29
- description: string | null;
30
- }
31
- export interface CatalogContentType {
32
- appSlug: string;
33
- contentType: string;
34
- label: string;
35
- tableName: string;
36
- schemaName: string;
37
- visibility: string;
38
- /** Null when the table carries no foreign key to reportResolutions. */
39
- quarantineColumn: string | null;
40
- /** Null when no single-column primary key could be derived. */
41
- refColumn: string | null;
42
- authorColumn: string | null;
43
- }
44
- export interface Catalog {
45
- reasons: CatalogReason[];
46
- contentTypes: CatalogContentType[];
47
- }
48
- export declare function readCatalog(client: DevtoolsClient): Promise<Catalog>;
49
- /**
50
- * Formats the catalog for `note()`.
51
- *
52
- * Content types are grouped by app, because a contributor asks "what does *my*
53
- * app expose?". A type that cannot be quarantined or cannot be addressed is
54
- * called out inline rather than left to be inferred from a blank column. Those
55
- * are the two states that look like working configuration and are not.
56
- */
57
- export declare function renderCatalog(catalog: Catalog): string;
package/dist/catalog.js DELETED
@@ -1,57 +0,0 @@
1
- export async function readCatalog(client) {
2
- const [reasons, contentTypes] = await Promise.all([
3
- client.rpc("list_report_reasons"),
4
- client.rpc("list_content_types"),
5
- ]);
6
- if (reasons.error)
7
- throw new Error(reasons.error.message);
8
- if (contentTypes.error)
9
- throw new Error(contentTypes.error.message);
10
- return {
11
- reasons: (reasons.data ?? []),
12
- contentTypes: (contentTypes.data ?? []),
13
- };
14
- }
15
- /**
16
- * Formats the catalog for `note()`.
17
- *
18
- * Content types are grouped by app, because a contributor asks "what does *my*
19
- * app expose?". A type that cannot be quarantined or cannot be addressed is
20
- * called out inline rather than left to be inferred from a blank column. Those
21
- * are the two states that look like working configuration and are not.
22
- */
23
- export function renderCatalog(catalog) {
24
- const lines = [];
25
- lines.push("Report reasons — one global list, every app, every content type");
26
- for (const r of catalog.reasons) {
27
- lines.push(` ${r.reason.padEnd(16)} ${r.title}`);
28
- }
29
- if (catalog.reasons.length === 0) {
30
- lines.push(" (none — the presentation table is empty, which is a bug)");
31
- }
32
- lines.push("");
33
- lines.push("Content types — derived from each app's schema, not configured");
34
- const byApp = new Map();
35
- for (const t of catalog.contentTypes) {
36
- byApp.set(t.appSlug, [...(byApp.get(t.appSlug) ?? []), t]);
37
- }
38
- if (byApp.size === 0) {
39
- lines.push(" (none — no registered app declares any moderatable content)");
40
- }
41
- for (const [app, types] of [...byApp].sort()) {
42
- lines.push(` ${app}`);
43
- for (const t of types) {
44
- const notes = [];
45
- if (!t.quarantineColumn)
46
- notes.push("no quarantine column");
47
- if (!t.refColumn)
48
- notes.push("NOT ADDRESSABLE: no single-column key");
49
- if (!t.authorColumn)
50
- notes.push("no author column derived");
51
- const suffix = notes.length > 0 ? ` (${notes.join("; ")})` : "";
52
- lines.push(` ${t.contentType.padEnd(14)} ${t.schemaName}.${t.tableName}` +
53
- ` [${t.visibility}]${suffix}`);
54
- }
55
- }
56
- return lines.join("\n");
57
- }
@@ -1,13 +0,0 @@
1
- export declare function needsFrameworkBuild(app: string): boolean;
2
- /** Build only `app`'s workspace dependencies — all `vinext dev` needs, since
3
- * it compiles the app itself on demand. */
4
- export declare function buildWorkspaceDeps(app: string): Promise<number>;
5
- /** The pnpm invocations `buildWorkerApp` runs, in order. */
6
- export declare function workerBuildCommands(app: string): string[][];
7
- /**
8
- * Build `app` for a local `wrangler dev`. `env` applies to the framework
9
- * build only — it bakes tier-scoped values (NEXT_PUBLIC_*, CLOUDFLARE_ENV)
10
- * into the bundle; the dependency builds are tier-independent `tsc` runs.
11
- * Resolves to the first non-zero exit code, or 0.
12
- */
13
- export declare function buildWorkerApp(app: string, env?: NodeJS.ProcessEnv): Promise<number>;
package/dist/cf/build.js DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * The build a Worker app needs before `wrangler dev` can boot it locally.
3
- *
4
- * The vinext apps' `wrangler.jsonc` is the PRE-build shape: `main` is
5
- * `cloudflare/worker.ts`, which imports vinext's app-router entry and its
6
- * `virtual:vinext-*` modules, and `assets.directory` is `dist/client`. Plain
7
- * `wrangler dev` against that file can never work — esbuild cannot resolve
8
- * Vite's virtual modules, and on a fresh clone `dist/client` does not exist
9
- * yet ("The directory specified by the "assets.directory" field ... does not
10
- * exist"). `vinext build` writes `dist/` plus the `.wrangler/deploy/
11
- * config.json` redirect that points a bare `wrangler dev` at the built
12
- * `dist/server/wrangler.json` instead.
13
- *
14
- * Building every time, rather than only when `dist/` is missing, also keeps
15
- * a checkout from running a stale bundle — or one a previous `cf preview
16
- * --tier staging` baked for another tier.
17
- *
18
- * `workflows run`/`serve` use `vinext dev` instead and need none of this
19
- * except the dependency build (`buildWorkspaceDeps`).
20
- *
21
- * The app's workspace dependencies build first, the way `devtools run build`
22
- * orders them (see `run/pick.ts`): their `exports` resolve to gitignored
23
- * `dist/` output, so on a fresh clone `vinext build` fails to load
24
- * `next.config.ts` without them.
25
- */
26
- import { run } from "../db/run.js";
27
- /** Apps deployed without a framework build: Wrangler bundles `main` itself. */
28
- const NO_FRAMEWORK_BUILD = new Set(["sandbox"]);
29
- export function needsFrameworkBuild(app) {
30
- return !NO_FRAMEWORK_BUILD.has(app);
31
- }
32
- function workspaceDepsBuildCommand(app) {
33
- return ["-r", "--if-present", "--filter", `${app}^...`, "run", "build"];
34
- }
35
- /** Build only `app`'s workspace dependencies — all `vinext dev` needs, since
36
- * it compiles the app itself on demand. */
37
- export function buildWorkspaceDeps(app) {
38
- return run(workspaceDepsBuildCommand(app));
39
- }
40
- /** The pnpm invocations `buildWorkerApp` runs, in order. */
41
- export function workerBuildCommands(app) {
42
- const commands = [workspaceDepsBuildCommand(app)];
43
- if (needsFrameworkBuild(app)) {
44
- commands.push(["--filter", app, "exec", "vinext", "build"]);
45
- }
46
- return commands;
47
- }
48
- /**
49
- * Build `app` for a local `wrangler dev`. `env` applies to the framework
50
- * build only — it bakes tier-scoped values (NEXT_PUBLIC_*, CLOUDFLARE_ENV)
51
- * into the bundle; the dependency builds are tier-independent `tsc` runs.
52
- * Resolves to the first non-zero exit code, or 0.
53
- */
54
- export async function buildWorkerApp(app, env) {
55
- const [, ...framework] = workerBuildCommands(app);
56
- const depsCode = await buildWorkspaceDeps(app);
57
- if (depsCode !== 0)
58
- return depsCode;
59
- for (const command of framework) {
60
- const code = await run(command, env);
61
- if (code !== 0)
62
- return code;
63
- }
64
- return 0;
65
- }
@@ -1 +0,0 @@
1
- export declare function runCf(argv: readonly string[]): Promise<number>;