@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
@@ -1 +0,0 @@
1
- export declare function runGen(argv: readonly string[]): Promise<number>;
@@ -1,25 +0,0 @@
1
- /**
2
- * Dispatch for `devtools gen *`.
3
- *
4
- * `gen og-assets` and `gen email-templates` delegate to the owning package
5
- * scripts via pnpm. `gen campus-map` and `gen hypno` are devtools-owned.
6
- */
7
- import { run } from "../db/run.js";
8
- import { runGenCampusMap } from "./campus-map.js";
9
- import { runGenHypno } from "./hypno.js";
10
- export async function runGen(argv) {
11
- const sub = argv[0];
12
- if (sub === "campus-map")
13
- return runGenCampusMap();
14
- if (sub === "hypno")
15
- return runGenHypno();
16
- if (sub === "og-assets") {
17
- return run(["--filter", "@devdogsuga/open-graph", "run", "generate"]);
18
- }
19
- if (sub === "email-templates") {
20
- return run(["--filter", "@devdogsuga/email", "run", "compile"]);
21
- }
22
- process.stderr.write(`devtools gen: unknown subcommand "${sub ?? "(none)"}". ` +
23
- "Expected: campus-map, hypno, og-assets, email-templates.\n");
24
- return 1;
25
- }
@@ -1 +0,0 @@
1
- export declare function runGenHypno(): Promise<number>;
package/dist/gen/hypno.js DELETED
@@ -1,134 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * `devtools gen hypno`
4
- *
5
- * Bakes the hero's spiral into a pre-blurred raster at
6
- * `apps/platform/src/assets/hypno.webp`.
7
- *
8
- * Why this exists: the spiral used to be an inline 24-path SVG, ~3,530 CSS px
9
- * square, rotating forever under a `blur-sm` filter. Measured on a production
10
- * build, that single element held the page to 36.8 FPS at idle; hiding it
11
- * restored 60.4. The cost is the live filter, not the geometry — shrinking the
12
- * surface 6x bought 3 FPS, removing the blur at full size bought 25. Rotating a
13
- * texture that already has the blur painted into it is free by comparison, and
14
- * it keeps both the motion and the exact hazy look.
15
- *
16
- * Two details make the bake match the original:
17
- *
18
- * 1. The source sets `vector-effect: non-scaling-stroke`, so its 2px stroke is
19
- * 2 *device* px no matter how far the SVG is scaled up. A raster does not
20
- * work that way — its stroke scales with the image. So the stroke is
21
- * converted into viewBox units for the size the element actually renders at
22
- * (2 * 1926.25 / RENDER_PX), which reproduces the same apparent weight.
23
- * 2. At the raster sizes worth shipping, both the stroke and the blur land
24
- * below one pixel, and Skia's box-approximated blur is inaccurate at
25
- * sub-pixel sigma. Everything is therefore drawn at SS x the target and
26
- * downsampled, which resolves both correctly.
27
- */
28
- import { readFileSync, writeFileSync } from "node:fs";
29
- import { join } from "node:path";
30
- import { createRequire } from "node:module";
31
- import { findRepoRoot } from "../repo/root.js";
32
- // Lazy — computed inside `runGenHypno`/`loadChromium`, not at module load.
33
- // This file used to compute these as top-level `const`s, which called
34
- // `findRepoRoot()` (and threw `RepoNotFoundError`) the instant anything
35
- // imported this module — including `devtools --help` from outside a repo,
36
- // since the command tree pulls every command module in transitively. See
37
- // `workers.ts`'s header for the general pattern.
38
- function srcPath() {
39
- return join(findRepoRoot(), "apps", "platform", "src", "components", "HeroSection", "Hypno.tsx");
40
- }
41
- function outPath() {
42
- return join(findRepoRoot(), "apps", "platform", "src", "assets", "hypno.webp");
43
- }
44
- const RENDER_PX = 3536;
45
- const VIEWBOX = 1926.25;
46
- const BLUR_CSS_PX = 8;
47
- const SS = 3;
48
- const STROKE = "rgba(216,180,254,0.75)";
49
- const BUDGET = 60_000;
50
- async function loadChromium() {
51
- const require_ = createRequire(import.meta.url);
52
- for (const spec of [
53
- "playwright",
54
- "playwright-core",
55
- join(findRepoRoot(), "node_modules/.pnpm/playwright@1.62.1/node_modules/playwright"),
56
- ]) {
57
- try {
58
- const mod = (await import(require_.resolve(spec)));
59
- const chromium = mod.chromium ?? mod.default?.chromium;
60
- if (chromium)
61
- return chromium;
62
- }
63
- catch {
64
- /* try the next one */
65
- }
66
- }
67
- throw new Error("playwright not resolvable. Run `pnpm dlx playwright@1.62 install chromium` " +
68
- "or run this command from a workspace where playwright is installed.");
69
- }
70
- export async function runGenHypno() {
71
- const src = readFileSync(srcPath(), "utf8");
72
- const paths = [...src.matchAll(/<path d="([^"]+)"/g)].map((m) => m[1]);
73
- if (paths.length !== 24) {
74
- process.stderr.write(`expected 24 paths in Hypno.tsx, found ${paths.length}\n`);
75
- return 1;
76
- }
77
- const strokeW = (2 * VIEWBOX) / RENDER_PX;
78
- const svg = (px) => `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}"
79
- viewBox="0 0 ${VIEWBOX} ${VIEWBOX}">
80
- <g fill="none" stroke="${STROKE}" stroke-width="${strokeW}" stroke-linecap="round" stroke-linejoin="round">
81
- ${paths.map((d) => `<path d="${d}"/>`).join("\n")}
82
- </g></svg>`;
83
- const chromium = await loadChromium();
84
- const browser = await chromium.launch();
85
- const page = await browser.newPage();
86
- await page.goto("data:text/html,<html><body></body></html>");
87
- const bake = async (target, quality) => page.evaluate(async ({ markup, target, ss, blur, quality, }) => {
88
- const big = target * ss;
89
- const img = new Image();
90
- img.src =
91
- "data:image/svg+xml;charset=utf-8," + encodeURIComponent(markup);
92
- await new Promise((r) => {
93
- img.onload = r;
94
- img.onerror = r;
95
- });
96
- const hi = document.createElement("canvas");
97
- hi.width = hi.height = big;
98
- const hx = hi.getContext("2d");
99
- hx.filter = `blur(${blur * ss}px)`;
100
- hx.drawImage(img, 0, 0, big, big);
101
- const lo = document.createElement("canvas");
102
- lo.width = lo.height = target;
103
- const lx = lo.getContext("2d");
104
- lx.imageSmoothingEnabled = true;
105
- lx.imageSmoothingQuality = "high";
106
- lx.drawImage(hi, 0, 0, target, target);
107
- return lo.toDataURL("image/webp", quality);
108
- }, {
109
- markup: svg(target * SS),
110
- target,
111
- ss: SS,
112
- blur: (BLUR_CSS_PX * target) / RENDER_PX,
113
- quality,
114
- });
115
- const bytes = (dataUrl) => Buffer.from(dataUrl.split(",")[1], "base64").byteLength;
116
- console.log("size quality bytes");
117
- const results = [];
118
- for (const target of [512, 768, 1024]) {
119
- for (const q of [0.5, 0.65, 0.8]) {
120
- const url = await bake(target, q);
121
- const b = bytes(url);
122
- results.push({ target, q, b, url });
123
- console.log(`${String(target).padStart(4)} ${q.toFixed(2)} ${String(b).padStart(7)}`);
124
- }
125
- }
126
- const pick = results
127
- .filter((r) => r.b <= BUDGET)
128
- .sort((a, b) => b.target - a.target || b.q - a.q)[0] ??
129
- results.sort((a, b) => a.b - b.b)[0];
130
- writeFileSync(outPath(), Buffer.from(pick.url.split(",")[1], "base64"));
131
- console.log(`\nwrote ${outPath()}\n ${pick.target}px @ q=${pick.q} -> ${pick.b} bytes (budget ${BUDGET})`);
132
- await browser.close();
133
- return 0;
134
- }
@@ -1,80 +0,0 @@
1
- export declare class GhError extends Error {
2
- }
3
- export interface GhSecret {
4
- name: string;
5
- updatedAt: string;
6
- }
7
- /** Unlike `GhSecret`, this carries the value: variables are readable. */
8
- export interface GhVariable {
9
- name: string;
10
- value: string;
11
- updatedAt: string;
12
- }
13
- /** Environment secret names and when each last changed. Never values. */
14
- export declare function listSecrets(environment: string): Promise<GhSecret[]>;
15
- /**
16
- * Sets one secret, passing the value on **stdin**.
17
- *
18
- * Not `--body`, which would put a live credential in argv where `ps` can read
19
- * it. Not `--env-file` either, despite it being one call for the whole set:
20
- * that hands the file to `gh`'s own dotenv parser, and this tool has already
21
- * parsed it with different rules. Two parsers over one file agree right up
22
- * until a multi-line value, a PEM private key being the case, and then disagree
23
- * silently, storing something that looks like a key and is not.
24
- *
25
- * One process per secret is the cost. It is paid once per rotation.
26
- */
27
- export declare function setSecret(environment: string, name: string, value: string): Promise<void>;
28
- export declare function deleteSecret(environment: string, name: string): Promise<void>;
29
- /**
30
- * Environment variable names, VALUES, and when each last changed.
31
- *
32
- * The value is the point. It is what turns the GitHub half of `env audit` from
33
- * a presence check into a real comparison for these keys: a variable whose
34
- * value drifted from Bitwarden is detectable, where the same drift in a secret
35
- * is not detectable by anything.
36
- */
37
- export declare function listVariables(environment: string): Promise<GhVariable[]>;
38
- /**
39
- * Sets one variable, passing the value on **stdin**.
40
- *
41
- * Stdin rather than `--body` even though a variable is public by definition:
42
- * argv is shared machinery, and a rule with an exception ("stdin for secrets,
43
- * argv for variables") is a rule somebody applies to the wrong call. The
44
- * `--env-file` form is refused for the reason `setSecret` refuses it: it would
45
- * hand the file to `gh`'s own dotenv parser, which agrees with this tool's
46
- * parser right up until a multi-line value and then disagrees silently.
47
- */
48
- export declare function setVariable(environment: string, name: string, value: string): Promise<void>;
49
- export declare function deleteVariable(environment: string, name: string): Promise<void>;
50
- /**
51
- * A repository-level variable. NAME ONLY, and that is deliberate.
52
- *
53
- * `gh variable list` will return the value here as readily as it does for an
54
- * environment variable, and fetching it would invite the obvious next step,
55
- * comparing it against Bitwarden. That comparison has no correct answer. This
56
- * copy is unmanaged whatever it holds, so a value that matches today proves
57
- * nothing except that somebody set them the same way once; reporting only the
58
- * mismatch would then read as "the matching one is fine", which is the belief
59
- * this whole check exists to break.
60
- */
61
- export interface GhRepositoryVariable {
62
- name: string;
63
- updatedAt: string;
64
- }
65
- /**
66
- * The repository's OWN variables, the ones with no environment.
67
- *
68
- * ⚠️ The reason this exists at all: an environment variable of the same name
69
- * **shadows** a repository one, and nothing in GitHub's UI says so. Every job
70
- * that runs in the environment reads the environment copy, so a repository
71
- * variable a person set by hand before `env push` routed that key keeps its
72
- * stale value indefinitely and invisibly, right up until somebody deletes the
73
- * environment copy, at which point the stale value silently becomes live.
74
- *
75
- * No `--env`, and that missing flag IS the call. Everything else in this file
76
- * addresses one environment; this addresses the repository itself, which is
77
- * the scope nothing here writes to and therefore the scope nothing here would
78
- * otherwise notice.
79
- */
80
- export declare function listRepositoryVariables(): Promise<GhRepositoryVariable[]>;
package/dist/gh/client.js DELETED
@@ -1,259 +0,0 @@
1
- /**
2
- * A thin wrapper over the `gh` CLI, for environment secrets and variables.
3
- *
4
- * `gh` rather than the REST API for the same reason `bws` is used over its API:
5
- * a secret's value has to be encrypted client-side with a libsodium sealed box
6
- * against the environment's public key before it can be sent. `gh` does that
7
- * locally. Doing it by hand means fetching the key, pulling in a sodium
8
- * binding, and owning that code forever to avoid one dependency that is already
9
- * installed.
10
- *
11
- * ## Secrets and variables are two different stores
12
- *
13
- * They share an environment and nothing else, and the differences are the whole
14
- * reason the public per-environment values go to the second:
15
- *
16
- * | | Sent as | Readable back | In logs |
17
- * |------------|------------|---------------|----------------|
18
- * | secret | sealed box | never | masked, by substring |
19
- * | variable | plaintext | value and all | verbatim |
20
- *
21
- * The masking is not a nicety either way. `PROJECT_REF` as a *secret* rewrites
22
- * `https://supabase.com/dashboard/project/<ref>` to `.../***`, which is the
23
- * paused-project gate's whole output. And because the ref is a substring of
24
- * every Supabase hostname, it corrupts unrelated log lines across the repo. The
25
- * readability is what lets `env audit` compare a variable by VALUE, which it
26
- * cannot do for anything on the top row.
27
- *
28
- * ⚠️ The corollary, and the reason each setter is its own function rather than
29
- * one with a flag: putting a secret in the variable store publishes its value
30
- * to anyone with read access to the repository's Actions config, silently and
31
- * irreversibly. There is no operation here that can be pointed at the wrong
32
- * store by a boolean somebody passed the wrong way round.
33
- */
34
- import { spawn } from "node:child_process";
35
- import { promisify } from "node:util";
36
- import { execFile } from "node:child_process";
37
- const run = promisify(execFile);
38
- const MAX_BUFFER = 16 * 1024 * 1024;
39
- export class GhError extends Error {
40
- }
41
- let warnedAboutTokenOverride = false;
42
- /**
43
- * Says, once, when `gh` is about to ignore its own login.
44
- *
45
- * `gh` prefers `GH_TOKEN` / `GITHUB_TOKEN` from the environment over the
46
- * account somebody logged in with, and `with-env` loads `.env` for every
47
- * devtools command, so a token line in that file takes over EVERY `gh` call
48
- * silently. That is how a leftover classic PAT kept authenticating after the
49
- * operator's real login was a perfectly valid OAuth token, right up until
50
- * the org's classic-PAT block turned it into a 403 with no visible cause.
51
- * stderr and not clack, so it shows the same way from every command shape.
52
- */
53
- function warnOnEnvTokenOverride() {
54
- if (warnedAboutTokenOverride)
55
- return;
56
- const source = process.env.GH_TOKEN
57
- ? "GH_TOKEN"
58
- : process.env.GITHUB_TOKEN
59
- ? "GITHUB_TOKEN"
60
- : undefined;
61
- if (!source)
62
- return;
63
- warnedAboutTokenOverride = true;
64
- console.error(`gh: authenticating with ${source} from the environment, NOT your ` +
65
- "`gh auth login` account. with-env loads .env, so a token line there " +
66
- "takes over silently — comment it out if that is not what you meant.");
67
- }
68
- /**
69
- * Turns a spawn failure into something with a next step in it.
70
- *
71
- * The three that happen are a missing binary, an unauthenticated CLI, and an
72
- * environment that does not exist yet. The last reports as a bare 404, because
73
- * an environment you cannot see and one that was never created look identical
74
- * from here.
75
- */
76
- function describe(err) {
77
- const e = err;
78
- if (e.code === "ENOENT") {
79
- return ("The `gh` CLI is not installed, or is not on PATH.\n" +
80
- "Install it from https://cli.github.com, then run `gh auth login`.");
81
- }
82
- const stderr = (e.stderr ?? "").trim();
83
- if (/personal access token \(classic\)/i.test(stderr)) {
84
- return (`${stderr}\n\n` +
85
- "The organization forbids classic PATs (by design — §0.3), and gh " +
86
- "authenticated with one anyway. gh prefers GH_TOKEN / GITHUB_TOKEN " +
87
- "from the environment over your `gh auth login`, and with-env loads " +
88
- ".env — so the usual culprit is a leftover ghp_ token line in that " +
89
- "file. Comment it out (and revoke the token at " +
90
- "github.com/settings/tokens); your stored gh login then takes over.");
91
- }
92
- if (/not logged into|authentication required|gh auth login/i.test(stderr)) {
93
- return `${stderr}\n\nRun \`gh auth login\`. Managing environment secrets needs the \`repo\` scope.`;
94
- }
95
- if (/404|not found/i.test(stderr)) {
96
- return (`${stderr}\n\n` +
97
- "A 404 usually means the environment does not exist yet — GitHub creates " +
98
- "environments explicitly, and a typo silently addresses a different one. " +
99
- "Check Settings → Environments.");
100
- }
101
- if (/403|forbidden|must have admin/i.test(stderr)) {
102
- return `${stderr}\n\nSetting environment secrets needs admin on the repository.`;
103
- }
104
- if (/\b429\b|rate.?limit|too many requests/i.test(stderr)) {
105
- return (`${stderr}\n\n` +
106
- "GitHub rate-limited this. A push is one API call per secret, so a " +
107
- "burst of pushes can trip the secondary limit — wait a minute and " +
108
- "re-run; already-written values are simply overwritten with themselves.");
109
- }
110
- const message = e.message ?? "";
111
- return stderr || message || "gh failed with no output.";
112
- }
113
- /** Environment secret names and when each last changed. Never values. */
114
- export async function listSecrets(environment) {
115
- warnOnEnvTokenOverride();
116
- try {
117
- const { stdout } = await run("gh", ["secret", "list", "--env", environment, "--json", "name,updatedAt"], { maxBuffer: MAX_BUFFER, shell: false });
118
- return stdout.trim() === "" ? [] : JSON.parse(stdout);
119
- }
120
- catch (err) {
121
- throw new GhError(describe(err));
122
- }
123
- }
124
- /**
125
- * Sets one secret, passing the value on **stdin**.
126
- *
127
- * Not `--body`, which would put a live credential in argv where `ps` can read
128
- * it. Not `--env-file` either, despite it being one call for the whole set:
129
- * that hands the file to `gh`'s own dotenv parser, and this tool has already
130
- * parsed it with different rules. Two parsers over one file agree right up
131
- * until a multi-line value, a PEM private key being the case, and then disagree
132
- * silently, storing something that looks like a key and is not.
133
- *
134
- * One process per secret is the cost. It is paid once per rotation.
135
- */
136
- export async function setSecret(environment, name, value) {
137
- warnOnEnvTokenOverride();
138
- await new Promise((resolve, reject) => {
139
- const child = spawn("gh", ["secret", "set", name, "--env", environment], {
140
- stdio: ["pipe", "ignore", "pipe"],
141
- shell: false,
142
- });
143
- let stderr = "";
144
- child.stderr.on("data", (c) => (stderr += c.toString()));
145
- child.on("error", (err) => reject(new GhError(describe(err))));
146
- child.on("close", (code) => code === 0
147
- ? resolve()
148
- : reject(new GhError(describe({ stderr, message: `gh exited ${code}` }))));
149
- // Exactly the bytes, with no trailing newline: `gh` stores stdin verbatim,
150
- // so a stray "\n" becomes part of the secret and every comparison against
151
- // it fails in a way that reads like a wrong value rather than a stray byte.
152
- child.stdin.end(value);
153
- });
154
- }
155
- export async function deleteSecret(environment, name) {
156
- warnOnEnvTokenOverride();
157
- try {
158
- await run("gh", ["secret", "delete", name, "--env", environment], {
159
- shell: false,
160
- });
161
- }
162
- catch (err) {
163
- throw new GhError(describe(err));
164
- }
165
- }
166
- // ── variables ────────────────────────────────────────────────────────────────
167
- /**
168
- * Environment variable names, VALUES, and when each last changed.
169
- *
170
- * The value is the point. It is what turns the GitHub half of `env audit` from
171
- * a presence check into a real comparison for these keys: a variable whose
172
- * value drifted from Bitwarden is detectable, where the same drift in a secret
173
- * is not detectable by anything.
174
- */
175
- export async function listVariables(environment) {
176
- warnOnEnvTokenOverride();
177
- try {
178
- const { stdout } = await run("gh", [
179
- "variable",
180
- "list",
181
- "--env",
182
- environment,
183
- "--json",
184
- "name,value,updatedAt",
185
- ], { maxBuffer: MAX_BUFFER, shell: false });
186
- return stdout.trim() === "" ? [] : JSON.parse(stdout);
187
- }
188
- catch (err) {
189
- throw new GhError(describe(err));
190
- }
191
- }
192
- /**
193
- * Sets one variable, passing the value on **stdin**.
194
- *
195
- * Stdin rather than `--body` even though a variable is public by definition:
196
- * argv is shared machinery, and a rule with an exception ("stdin for secrets,
197
- * argv for variables") is a rule somebody applies to the wrong call. The
198
- * `--env-file` form is refused for the reason `setSecret` refuses it: it would
199
- * hand the file to `gh`'s own dotenv parser, which agrees with this tool's
200
- * parser right up until a multi-line value and then disagrees silently.
201
- */
202
- export async function setVariable(environment, name, value) {
203
- warnOnEnvTokenOverride();
204
- await new Promise((resolve, reject) => {
205
- const child = spawn("gh", ["variable", "set", name, "--env", environment], {
206
- stdio: ["pipe", "ignore", "pipe"],
207
- shell: false,
208
- });
209
- let stderr = "";
210
- child.stderr.on("data", (c) => (stderr += c.toString()));
211
- child.on("error", (err) => reject(new GhError(describe(err))));
212
- child.on("close", (code) => code === 0
213
- ? resolve()
214
- : reject(new GhError(describe({ stderr, message: `gh exited ${code}` }))));
215
- // No trailing newline, for a reason that bites harder here than it does for
216
- // a secret: the stored value IS readable, so a stray "\n" turns every
217
- // `audit` run into a value-drift error against a Bitwarden copy that is
218
- // actually identical, and no amount of re-pushing fixes it.
219
- child.stdin.end(value);
220
- });
221
- }
222
- export async function deleteVariable(environment, name) {
223
- warnOnEnvTokenOverride();
224
- try {
225
- await run("gh", ["variable", "delete", name, "--env", environment], {
226
- shell: false,
227
- });
228
- }
229
- catch (err) {
230
- throw new GhError(describe(err));
231
- }
232
- }
233
- /**
234
- * The repository's OWN variables, the ones with no environment.
235
- *
236
- * ⚠️ The reason this exists at all: an environment variable of the same name
237
- * **shadows** a repository one, and nothing in GitHub's UI says so. Every job
238
- * that runs in the environment reads the environment copy, so a repository
239
- * variable a person set by hand before `env push` routed that key keeps its
240
- * stale value indefinitely and invisibly, right up until somebody deletes the
241
- * environment copy, at which point the stale value silently becomes live.
242
- *
243
- * No `--env`, and that missing flag IS the call. Everything else in this file
244
- * addresses one environment; this addresses the repository itself, which is
245
- * the scope nothing here writes to and therefore the scope nothing here would
246
- * otherwise notice.
247
- */
248
- export async function listRepositoryVariables() {
249
- warnOnEnvTokenOverride();
250
- try {
251
- const { stdout } = await run("gh", ["variable", "list", "--json", "name,updatedAt"], { maxBuffer: MAX_BUFFER, shell: false });
252
- return stdout.trim() === ""
253
- ? []
254
- : JSON.parse(stdout);
255
- }
256
- catch (err) {
257
- throw new GhError(describe(err));
258
- }
259
- }
@@ -1,83 +0,0 @@
1
- export declare const GITHUB_ENVIRONMENTS: readonly ["preflight", "staging", "production", "production-apply"];
2
- export type GithubEnvironment = (typeof GITHUB_ENVIRONMENTS)[number];
3
- export declare function isGithubEnvironment(v: string): v is GithubEnvironment;
4
- export interface GithubEnvironmentSpec {
5
- /** BWS project to compare against, or null when nothing backs it. */
6
- bwsProject: string | null;
7
- /** Deployment branch policy, for the summary line. */
8
- branch: string;
9
- /**
10
- * Keys allowed here, or null for "whatever the file defines".
11
- *
12
- * ⚠️ NULL ON EVERY ROW TODAY, and that is the deliberate state rather than a
13
- * field nobody got round to filling in. `production-apply` used to carry the
14
- * apply-tier pair here, on the reasoning that "holding a third credential
15
- * makes the reviewer gate stop meaning anything", which conflated two
16
- * constraints and kept only the one that was not the gate. An allowlist here
17
- * restricts what the REVIEWED environment may hold; the gate is
18
- * `production.excludeKeys`, which restricts what the UNREVIEWED one may hold.
19
- * Only the second is a security property, and it is enforced below.
20
- *
21
- * Kept as a field because the mechanism is worth having and costs one branch
22
- * in `accepts()`: a future environment that genuinely holds a fixed short
23
- * list, a third-party integration's two keys and nothing else, states it here
24
- * rather than as an `excludeKeys` of everything else.
25
- */
26
- onlyKeys: readonly string[] | null;
27
- /**
28
- * Keys that must never reach this environment.
29
- *
30
- * The whole reviewer gate now lives in this field: `production` (and
31
- * `staging`) exclude the apply-tier set, and nothing else stops a
32
- * write-capable credential from landing in an environment that deploys with
33
- * nobody in front of it.
34
- */
35
- excludeKeys: readonly string[];
36
- /** Extra confirmation before writing. */
37
- guarded: boolean;
38
- }
39
- export declare const GITHUB_ENVIRONMENT_SPECS: Record<GithubEnvironment, GithubEnvironmentSpec>;
40
- /** The GitHub environments fed by one Bitwarden project, in precedence order. */
41
- export declare function githubTargets(bwsProject: string): GithubEnvironment[];
42
- /** Whether one environment takes a given key. */
43
- export declare function accepts(environment: GithubEnvironment, key: string): boolean;
44
- /**
45
- * The PRIMARY environment a key belongs in, or `null` for "nowhere here".
46
- *
47
- * `null` is not an error. Pushing `staging` with an apply-only credential in
48
- * the file is the ordinary case: that key belongs to production and has no home
49
- * in the staging environment.
50
- *
51
- * ⚠️ "Primary", not "only". Since `production-apply` became a superset, an
52
- * ordinary production key legitimately lives in TWO environments and this
53
- * returns the first, the unreviewed one, where the deploy reads it. A caller
54
- * asking "is this copy misplaced?" wants `acceptedBy()` instead; answering that
55
- * question with this function reports every correctly-pushed copy in
56
- * `production-apply` as a stray that somebody should delete.
57
- */
58
- export declare function routeTo(bwsProject: string, key: string): GithubEnvironment | null;
59
- /**
60
- * EVERY environment of a project that takes a key, in precedence order.
61
- *
62
- * The set-shaped question, for callers that compare against what a push
63
- * actually wrote rather than against one destination. `routeTo()` is this with
64
- * `[0]` taken, and the difference is only ever visible for `production`, the
65
- * one project that fans out to two environments.
66
- */
67
- export declare function acceptedBy(bwsProject: string, key: string): GithubEnvironment[];
68
- /**
69
- * `accepts()` for an environment name that has not been narrowed yet, in the
70
- * argument order `AuditInput.accepted` wants.
71
- *
72
- * Exists so that the predicate `env audit` hands the audit is a NAME rather
73
- * than a lambda written at the call site: `runEnvAudit` cannot be unit-tested
74
- * without mocking Bitwarden, GitHub and Cloudflare at once, so anything spelled
75
- * out there is untested by construction.
76
- *
77
- * ⚠️ An unrecognised environment is REFUSED, not accepted. The string comes
78
- * from whatever `gh` listed, and the audit uses this to decide whether a found
79
- * copy is a stray. Failing open here would silence the finding for a copy
80
- * sitting in an environment this repository has never heard of, which is
81
- * exactly the case worth hearing about.
82
- */
83
- export declare function acceptsKey(key: string, environment: string): boolean;