@reddoorla/maintenance 0.79.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{announce-RUT247HS.js → announce-KZDU65IF.js} +6 -5
  2. package/dist/{announce-RUT247HS.js.map → announce-KZDU65IF.js.map} +1 -1
  3. package/dist/{blux-2P4FGCUL.js → blux-EATHOHKK.js} +2 -2
  4. package/dist/{chunk-FVVGDIGX.js → chunk-42A4H6SF.js} +2 -2
  5. package/dist/{chunk-SEQA7MC3.js → chunk-62VF32IC.js} +2 -2
  6. package/dist/{chunk-6SZBNALD.js → chunk-6M2IXJ3E.js} +3 -3
  7. package/dist/{chunk-MNE374CZ.js → chunk-6QRLRT3P.js} +4 -16
  8. package/dist/chunk-6QRLRT3P.js.map +1 -0
  9. package/dist/{chunk-QMXQUL5Z.js → chunk-7ADQL5Y3.js} +2 -2
  10. package/dist/{chunk-QWFHC3VH.js → chunk-AKRUYPLU.js} +3 -3
  11. package/dist/{chunk-3LWKIKZ2.js → chunk-JMAFXEQE.js} +4 -4
  12. package/dist/{chunk-KPQXB5CR.js → chunk-KKIKJWM2.js} +2 -2
  13. package/dist/chunk-N4MOEF32.js +62 -0
  14. package/dist/{chunk-LJPCMXR7.js.map → chunk-N4MOEF32.js.map} +1 -1
  15. package/dist/{chunk-EYODPUHV.js → chunk-NMAWXBJA.js} +13 -2
  16. package/dist/chunk-NMAWXBJA.js.map +1 -0
  17. package/dist/{chunk-YIQ7IWE3.js → chunk-R5KAXGSP.js} +3 -3
  18. package/dist/chunk-SEEA6RXF.js +18 -0
  19. package/dist/chunk-SEEA6RXF.js.map +1 -0
  20. package/dist/{spam-classifier-VG4ICIWS.js → chunk-SNSTJLQW.js} +4 -3
  21. package/dist/{chunk-FAQIMC26.js → chunk-TEOENEJH.js} +2 -2
  22. package/dist/{chunk-VSHC4HOI.js → chunk-YAMRVXOD.js} +93 -1
  23. package/dist/chunk-YAMRVXOD.js.map +1 -0
  24. package/dist/cli/bin.js +24 -12
  25. package/dist/cli/bin.js.map +1 -1
  26. package/dist/cli/commands/audit.js +4 -4
  27. package/dist/configs/lighthouse.d.ts +1 -1
  28. package/dist/configs/lighthouse.js +1 -1
  29. package/dist/configs/playwright-a11y.js +1 -1
  30. package/dist/{digest-NQAQI34P.js → digest-44QNZZKM.js} +8 -6
  31. package/dist/{digest-NQAQI34P.js.map → digest-44QNZZKM.js.map} +1 -1
  32. package/dist/forms-notify-target-CL3HRVKN.js +258 -0
  33. package/dist/forms-notify-target-CL3HRVKN.js.map +1 -0
  34. package/dist/{freeze-Y5OJFOST.js → freeze-Z7JZTQJS.js} +47 -7
  35. package/dist/freeze-Z7JZTQJS.js.map +1 -0
  36. package/dist/{github-signals-4LWQGD5V.js → github-signals-2BFMZLPV.js} +2 -2
  37. package/dist/index.js +17 -15
  38. package/dist/index.js.map +1 -1
  39. package/dist/{init-Y5O33BUL.js → init-RYKQFSZI.js} +6 -6
  40. package/dist/{launch-3SYLX263.js → launch-EGKYS3BD.js} +9 -8
  41. package/dist/{launch-3SYLX263.js.map → launch-EGKYS3BD.js.map} +1 -1
  42. package/dist/{orchestrate-QOQXOJLI.js → orchestrate-NY6L4Q7Q.js} +4 -3
  43. package/dist/{preflight-2PWVVIRR.js → preflight-SRDQS7Y6.js} +5 -4
  44. package/dist/{preflight-2PWVVIRR.js.map → preflight-SRDQS7Y6.js.map} +1 -1
  45. package/dist/{protection-audit-X3BHT2MB.js → protection-audit-2UR45SOC.js} +30 -2
  46. package/dist/protection-audit-2UR45SOC.js.map +1 -0
  47. package/dist/{renovate-dispatch-2VYCEH42.js → renovate-dispatch-7D7FMMJP.js} +2 -2
  48. package/dist/{report-IU4CLJS5.js → report-MLCIIML5.js} +9 -7
  49. package/dist/{report-IU4CLJS5.js.map → report-MLCIIML5.js.map} +1 -1
  50. package/dist/{self-updating-ALMUWJNX.js → self-updating-WJEHPASK.js} +3 -3
  51. package/dist/{selftest-PJFABZDM.js → selftest-ZJDB4JQB.js} +7 -6
  52. package/dist/{selftest-PJFABZDM.js.map → selftest-ZJDB4JQB.js.map} +1 -1
  53. package/dist/spam-classifier-XLBFCO7G.js +15 -0
  54. package/dist/spam-classifier-XLBFCO7G.js.map +1 -0
  55. package/dist/{submissions-SURN7BV5.js → submissions-RAYGOASP.js} +3 -3
  56. package/package.json +1 -1
  57. package/dist/chunk-EYODPUHV.js.map +0 -1
  58. package/dist/chunk-LJPCMXR7.js +0 -44
  59. package/dist/chunk-MNE374CZ.js.map +0 -1
  60. package/dist/chunk-VSHC4HOI.js.map +0 -1
  61. package/dist/freeze-Y5OJFOST.js.map +0 -1
  62. package/dist/protection-audit-X3BHT2MB.js.map +0 -1
  63. /package/dist/{blux-2P4FGCUL.js.map → blux-EATHOHKK.js.map} +0 -0
  64. /package/dist/{chunk-FVVGDIGX.js.map → chunk-42A4H6SF.js.map} +0 -0
  65. /package/dist/{chunk-SEQA7MC3.js.map → chunk-62VF32IC.js.map} +0 -0
  66. /package/dist/{chunk-6SZBNALD.js.map → chunk-6M2IXJ3E.js.map} +0 -0
  67. /package/dist/{chunk-QMXQUL5Z.js.map → chunk-7ADQL5Y3.js.map} +0 -0
  68. /package/dist/{chunk-QWFHC3VH.js.map → chunk-AKRUYPLU.js.map} +0 -0
  69. /package/dist/{chunk-3LWKIKZ2.js.map → chunk-JMAFXEQE.js.map} +0 -0
  70. /package/dist/{chunk-KPQXB5CR.js.map → chunk-KKIKJWM2.js.map} +0 -0
  71. /package/dist/{chunk-YIQ7IWE3.js.map → chunk-R5KAXGSP.js.map} +0 -0
  72. /package/dist/{spam-classifier-VG4ICIWS.js.map → chunk-SNSTJLQW.js.map} +0 -0
  73. /package/dist/{chunk-FAQIMC26.js.map → chunk-TEOENEJH.js.map} +0 -0
  74. /package/dist/{github-signals-4LWQGD5V.js.map → github-signals-2BFMZLPV.js.map} +0 -0
  75. /package/dist/{init-Y5O33BUL.js.map → init-RYKQFSZI.js.map} +0 -0
  76. /package/dist/{orchestrate-QOQXOJLI.js.map → orchestrate-NY6L4Q7Q.js.map} +0 -0
  77. /package/dist/{renovate-dispatch-2VYCEH42.js.map → renovate-dispatch-7D7FMMJP.js.map} +0 -0
  78. /package/dist/{self-updating-ALMUWJNX.js.map → self-updating-WJEHPASK.js.map} +0 -0
  79. /package/dist/{submissions-SURN7BV5.js.map → submissions-RAYGOASP.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/configs/playwright-a11y.ts"],"sourcesContent":["import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\n// Routes smoke-loaded for client-side (hydration) errors only — NOT axe-scanned.\n// Catches the class of bug where build + SSR succeed but client hydration throws\n// and blanks the page (data-dynamiq 2026-06-09: a Svelte 4->5 `run()` referenced\n// a `$state` declared after it → TDZ ReferenceError on hydrate). `/` is the one\n// route every site has; real routes carry a11y debt we don't gate on here, so we\n// assert only that they don't crash on hydrate.\nexport const smokeRoutes: A11yRoute[] = [{ path: \"/\", name: \"home\" }];\n\n// R1.1 (health-gate): the central `smoke` audit (src/audits/smoke.ts) allocates\n// a free port and passes it as REDDOOR_SMOKE_PORT so a zombie vite already\n// squatting the default 5173 can't silently hijack the run and green a stale\n// build. The per-site R1.1 config template honors it, but sites whose\n// playwright.config.ts merely re-exports this shared base (pre-R1.1 adopters\n// the smoke-suite recipe flags-but-never-rewrites) would otherwise ignore it —\n// so honor it here too and every re-exporter inherits the port binding on its\n// next package bump. Unset (local `pnpm test:smoke`) → the fixed 5173.\nconst smokePort = process.env.REDDOOR_SMOKE_PORT;\nconst port = smokePort || \"5173\";\n\n// NOTE: default export only — sites consume this as `import base from\n// \"@reddoorla/maintenance/configs/playwright-a11y\"` (or re-export the default).\n// The old `playwrightA11yConfig` named alias had zero importers and was removed.\nconst playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: `http://localhost:${port}`,\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n // `--strictPort` only when a port was explicitly allocated: fail loudly\n // rather than let vite drift to a free port the baseURL doesn't point at.\n command: smokePort\n ? `npm run vite:dev -- --port ${smokePort} --strictPort`\n : \"npm run vite:dev\",\n url: `http://localhost:${port}/dev/a11y-fixtures`,\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n"],"mappings":";AAAA,SAAS,cAAc,eAA0C;AAI1D,IAAM,aAA0B;AAAA,EACrC,EAAE,MAAM,sBAAsB,MAAM,gBAAgB;AAAA,EACpD,EAAE,MAAM,mBAAmB,MAAM,kBAAkB;AACrD;AAQO,IAAM,cAA2B,CAAC,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AAUpE,IAAM,YAAY,QAAQ,IAAI;AAC9B,IAAM,OAAO,aAAa;AAK1B,IAAM,uBAA6C,aAAa;AAAA,EAC9D,SAAS;AAAA,EACT,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY,CAAC,CAAC,QAAQ,IAAI;AAAA,EAC1B,SAAS,QAAQ,IAAI,KAAK,IAAI;AAAA,EAC9B,UAAU,QAAQ,IAAI,KAAK,WAAW;AAAA,EACtC,KAAK;AAAA,IACH,SAAS,oBAAoB,IAAI;AAAA,IACjC,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,MAAM;AAAA,MACN,KAAK,EAAE,GAAG,QAAQ,gBAAgB,EAAE;AAAA,IACtC;AAAA,EACF;AAAA,EACA,WAAW;AAAA;AAAA;AAAA;AAAA,IAIT,SAAS,YACL,8BAA8B,SAAS,kBACvC;AAAA,IACJ,KAAK,oBAAoB,IAAI;AAAA,IAC7B,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS;AAAA,EACX;AACF,CAAC;AAED,IAAO,0BAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/configs/playwright-a11y.ts"],"sourcesContent":["import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\n// Routes smoke-loaded for client-side (hydration) errors only — NOT axe-scanned.\n// Catches the class of bug where build + SSR succeed but client hydration throws\n// and blanks the page (data-dynamiq 2026-06-09: a Svelte 4->5 `run()` referenced\n// a `$state` declared after it → TDZ ReferenceError on hydrate). `/` is the one\n// route every site has; real routes carry a11y debt we don't gate on here, so we\n// assert only that they don't crash on hydrate.\nexport const smokeRoutes: A11yRoute[] = [{ path: \"/\", name: \"home\" }];\n\n// R1.1 (health-gate): the central `smoke` audit (src/audits/smoke.ts) allocates\n// a free port and passes it as REDDOOR_SMOKE_PORT so a zombie vite already\n// squatting the default 5173 can't silently hijack the run and green a stale\n// build. The per-site R1.1 config template honors it, but sites whose\n// playwright.config.ts merely re-exports this shared base (pre-R1.1 adopters\n// the smoke-suite recipe flags-but-never-rewrites) would otherwise ignore it —\n// so honor it here too and every re-exporter inherits the port binding on its\n// next package bump. Unset (local `pnpm test:smoke`) → the fixed 5173.\nconst smokePort = process.env.REDDOOR_SMOKE_PORT;\nconst port = smokePort || \"5173\";\n\n// NOTE: default export only — sites consume this as `import base from\n// \"@reddoorla/maintenance/configs/playwright-a11y\"` (or re-export the default).\n// The old `playwrightA11yConfig` named alias had zero importers and was removed.\nconst playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: `http://localhost:${port}`,\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n //\n // `--port ... --strictPort` in BOTH cases. It used to be applied only when\n // REDDOOR_SMOKE_PORT allocated one, on the reasoning that we should \"fail\n // loudly rather than let vite drift to a free port the baseURL doesn't\n // point at\" — but that argument covers the unset case just as well. 5173 is\n // equally a fixed port that `baseURL` and the readiness probe below are\n // pinned to, and vite left to itself drifts off it whenever something else\n // holds it.\n //\n // The symptom that exposed this: a non-vite process on 5173 sends vite to\n // 5174 while the probe keeps polling 5173, so the run dies on\n // \"Timed out waiting 120000ms from config.webServer\" — 120 seconds of\n // nothing, naming neither the port nor the squatter. With --strictPort it\n // is an immediate \"Port 5173 is already in use\".\n //\n // This does NOT overlap with `reuseExistingServer`: that check runs first,\n // so a dev server already serving the probe URL is still reused and the\n // command never executes. --strictPort only bites when 5173 is held by\n // something that is not the server under test, which is exactly the case\n // worth failing on.\n command: `npm run vite:dev -- --port ${port} --strictPort`,\n url: `http://localhost:${port}/dev/a11y-fixtures`,\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n"],"mappings":";AAAA,SAAS,cAAc,eAA0C;AAI1D,IAAM,aAA0B;AAAA,EACrC,EAAE,MAAM,sBAAsB,MAAM,gBAAgB;AAAA,EACpD,EAAE,MAAM,mBAAmB,MAAM,kBAAkB;AACrD;AAQO,IAAM,cAA2B,CAAC,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AAUpE,IAAM,YAAY,QAAQ,IAAI;AAC9B,IAAM,OAAO,aAAa;AAK1B,IAAM,uBAA6C,aAAa;AAAA,EAC9D,SAAS;AAAA,EACT,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY,CAAC,CAAC,QAAQ,IAAI;AAAA,EAC1B,SAAS,QAAQ,IAAI,KAAK,IAAI;AAAA,EAC9B,UAAU,QAAQ,IAAI,KAAK,WAAW;AAAA,EACtC,KAAK;AAAA,IACH,SAAS,oBAAoB,IAAI;AAAA,IACjC,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,MAAM;AAAA,MACN,KAAK,EAAE,GAAG,QAAQ,gBAAgB,EAAE;AAAA,IACtC;AAAA,EACF;AAAA,EACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAsBT,SAAS,8BAA8B,IAAI;AAAA,IAC3C,KAAK,oBAAoB,IAAI;AAAA,IAC7B,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS;AAAA,EACX;AACF,CAAC;AAED,IAAO,0BAAQ;","names":[]}
@@ -6,7 +6,18 @@ var lighthouseConfig = {
6
6
  // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects
7
7
  // the `run` form too. Keeps this config portable across the fleet
8
8
  // while sites transition to pnpm.
9
- startServerCommand: "npm run vite:dev",
9
+ //
10
+ // `--port 5173 --strictPort` because `url` above is pinned to 5173 and
11
+ // nothing else here binds vite to it. The readiness check makes the drift
12
+ // silent AND wrong rather than merely slow: `startServerReadyPattern`
13
+ // matches vite's "ready in" line whatever port it settled on, so with
14
+ // something already on 5173 vite comes up on 5174, announces itself,
15
+ // and lighthouse then collects from 5173 — auditing the squatter and
16
+ // reporting ITS scores as this site's. That is the same failure the
17
+ // audits fixed for themselves (see src/audits/lighthouse.ts, which
18
+ // allocates a free port and passes both flags); this config is what a
19
+ // site gets when it consumes the shared lighthouserc directly.
20
+ startServerCommand: "npm run vite:dev -- --port 5173 --strictPort",
10
21
  startServerReadyPattern: "ready in",
11
22
  startServerReadyTimeout: 12e4,
12
23
  numberOfRuns: 1,
@@ -34,4 +45,4 @@ export {
34
45
  lighthouseConfig,
35
46
  lighthouse_default
36
47
  };
37
- //# sourceMappingURL=chunk-EYODPUHV.js.map
48
+ //# sourceMappingURL=chunk-NMAWXBJA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/configs/lighthouse.ts"],"sourcesContent":["export const lighthouseConfig = {\n ci: {\n collect: {\n url: [\"http://localhost:5173/dev/a11y-fixtures\"],\n // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects\n // the `run` form too. Keeps this config portable across the fleet\n // while sites transition to pnpm.\n //\n // `--port 5173 --strictPort` because `url` above is pinned to 5173 and\n // nothing else here binds vite to it. The readiness check makes the drift\n // silent AND wrong rather than merely slow: `startServerReadyPattern`\n // matches vite's \"ready in\" line whatever port it settled on, so with\n // something already on 5173 vite comes up on 5174, announces itself,\n // and lighthouse then collects from 5173 — auditing the squatter and\n // reporting ITS scores as this site's. That is the same failure the\n // audits fixed for themselves (see src/audits/lighthouse.ts, which\n // allocates a free port and passes both flags); this config is what a\n // site gets when it consumes the shared lighthouserc directly.\n startServerCommand: \"npm run vite:dev -- --port 5173 --strictPort\",\n startServerReadyPattern: \"ready in\",\n startServerReadyTimeout: 120_000,\n numberOfRuns: 1,\n settings: {\n preset: \"desktop\",\n skipAudits: [\"uses-http2\"],\n },\n },\n assert: {\n assertions: {\n \"categories:accessibility\": [\"error\", { minScore: 0.95 }],\n \"categories:best-practices\": [\"error\", { minScore: 0.9 }],\n \"categories:seo\": [\"error\", { minScore: 0.9 }],\n \"categories:performance\": [\"warn\", { minScore: 0.7 }],\n },\n },\n upload: {\n target: \"temporary-public-storage\",\n },\n },\n} as const;\n\nexport default lighthouseConfig;\n"],"mappings":";AAAO,IAAM,mBAAmB;AAAA,EAC9B,IAAI;AAAA,IACF,SAAS;AAAA,MACP,KAAK,CAAC,yCAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAe/C,oBAAoB;AAAA,MACpB,yBAAyB;AAAA,MACzB,yBAAyB;AAAA,MACzB,cAAc;AAAA,MACd,UAAU;AAAA,QACR,QAAQ;AAAA,QACR,YAAY,CAAC,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,YAAY;AAAA,QACV,4BAA4B,CAAC,SAAS,EAAE,UAAU,KAAK,CAAC;AAAA,QACxD,6BAA6B,CAAC,SAAS,EAAE,UAAU,IAAI,CAAC;AAAA,QACxD,kBAAkB,CAAC,SAAS,EAAE,UAAU,IAAI,CAAC;AAAA,QAC7C,0BAA0B,CAAC,QAAQ,EAAE,UAAU,IAAI,CAAC;AAAA,MACtD;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEA,IAAO,qBAAQ;","names":[]}
@@ -5,12 +5,12 @@ import {
5
5
  import {
6
6
  isProbablyEmail,
7
7
  parseAddresses
8
- } from "./chunk-FVVGDIGX.js";
8
+ } from "./chunk-42A4H6SF.js";
9
9
  import {
10
10
  gatingHealth,
11
11
  isSendOverridden,
12
12
  listAllReports
13
- } from "./chunk-MNE374CZ.js";
13
+ } from "./chunk-6QRLRT3P.js";
14
14
  import {
15
15
  listWebsites,
16
16
  siteSlug
@@ -339,4 +339,4 @@ export {
339
339
  preflight,
340
340
  approveBlockers
341
341
  };
342
- //# sourceMappingURL=chunk-YIQ7IWE3.js.map
342
+ //# sourceMappingURL=chunk-R5KAXGSP.js.map
@@ -0,0 +1,18 @@
1
+ // src/util/html.ts
2
+ function escapeHtml(s) {
3
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4
+ }
5
+ function safeUrl(raw) {
6
+ try {
7
+ const u = new URL(raw);
8
+ if (u.protocol === "http:" || u.protocol === "https:") return raw;
9
+ } catch {
10
+ }
11
+ return "#";
12
+ }
13
+
14
+ export {
15
+ escapeHtml,
16
+ safeUrl
17
+ };
18
+ //# sourceMappingURL=chunk-SEEA6RXF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/html.ts"],"sourcesContent":["/**\n * Shared HTML/XML escape. One implementation behind the dashboard renderers\n * (`src/dashboard/render.ts`, `fleet-render.ts`), the daily digest\n * (`src/reports/digest.ts`), and the MJML email templates\n * (`src/reports/*-email/template.ts`).\n *\n * The set is the strict-XML set (`& < > \" '`), which is exactly what MJML's\n * `validationLevel: \"strict\"` parser needs and a superset of what plain HTML text\n * interpolation needs — so the SAME function serves both sinks. Site names\n * (e.g. \"Brown & Co\"), URLs, and operator commentary must not break the markup or\n * inject. The MJML templates re-export this as `escapeXml` for their callers.\n */\nexport function escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\n/** Allow only http(s) URLs in an href context; everything else collapses to \"#\". */\nexport function safeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (u.protocol === \"http:\" || u.protocol === \"https:\") return raw;\n } catch {\n // fall through\n }\n return \"#\";\n}\n"],"mappings":";AAYO,SAAS,WAAW,GAAmB;AAC5C,SAAO,EACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAGO,SAAS,QAAQ,KAAqB;AAC3C,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,QAAI,EAAE,aAAa,WAAW,EAAE,aAAa,SAAU,QAAO;AAAA,EAChE,QAAQ;AAAA,EAER;AACA,SAAO;AACT;","names":[]}
@@ -207,11 +207,12 @@ function classifySpam(input) {
207
207
  }
208
208
  return { score, reasons };
209
209
  }
210
+
210
211
  export {
212
+ SPAM_THRESHOLD,
213
+ SPAM_KEYWORDS,
211
214
  BUYER_KEYWORDS,
212
215
  DISPOSABLE_EMAIL_DOMAINS,
213
- SPAM_KEYWORDS,
214
- SPAM_THRESHOLD,
215
216
  classifySpam
216
217
  };
217
- //# sourceMappingURL=spam-classifier-VG4ICIWS.js.map
218
+ //# sourceMappingURL=chunk-SNSTJLQW.js.map
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-ZTPPCHDB.js";
22
22
  import {
23
23
  runAudits
24
- } from "./chunk-3LWKIKZ2.js";
24
+ } from "./chunk-JMAFXEQE.js";
25
25
  import {
26
26
  siteLabel
27
27
  } from "./chunk-XXTZBPUY.js";
@@ -143,4 +143,4 @@ export {
143
143
  DEFAULT_INIT_STEPS,
144
144
  init
145
145
  };
146
- //# sourceMappingURL=chunk-FAQIMC26.js.map
146
+ //# sourceMappingURL=chunk-TEOENEJH.js.map
@@ -31,6 +31,57 @@ function mapRollupState(state) {
31
31
  function mapMergeable(state) {
32
32
  return state === "MERGEABLE" || state === "CONFLICTING" ? state : "UNKNOWN";
33
33
  }
34
+ var KNOWN_DASHBOARD_SECTIONS = /* @__PURE__ */ new Set([
35
+ // 43+
36
+ "pending approval",
37
+ "group size not met",
38
+ "awaiting schedule",
39
+ "rate-limited",
40
+ "errored",
41
+ "pr creation approval required",
42
+ "pr edited (blocked)",
43
+ "pending status checks",
44
+ "pending branch automerge",
45
+ "other branches",
46
+ "open",
47
+ "pr closed (blocked)",
48
+ "repository problems",
49
+ "config migration needed",
50
+ "config migration needed (blocked)",
51
+ "deprecations / replacements",
52
+ "abandoned dependencies",
53
+ "vulnerabilities",
54
+ "detected dependencies",
55
+ // <=42, kept so an older self-hosted Renovate does not read as drift
56
+ "edited/blocked",
57
+ "ignored or blocked"
58
+ ]);
59
+ var BLOCKED_SECTION_RE = /^#{1,2}\s+(?:PR Edited \(Blocked\)|Edited\/Blocked)\s*$/i;
60
+ var SECTION_HEADING_RE = /^#{1,2}\s+(.*?)\s*$/;
61
+ function parseBlockedBranches(body) {
62
+ const found = [];
63
+ let inBlocked = false;
64
+ for (const line of body.split("\n")) {
65
+ if (SECTION_HEADING_RE.test(line)) {
66
+ inBlocked = BLOCKED_SECTION_RE.test(line);
67
+ continue;
68
+ }
69
+ if (!inBlocked) continue;
70
+ const marker = line.match(/rebase-branch=([^\s>]+)/);
71
+ if (marker?.[1]) found.push(marker[1]);
72
+ }
73
+ return [...new Set(found)];
74
+ }
75
+ function parseUnknownSections(body) {
76
+ const unknown = [];
77
+ for (const line of body.split("\n")) {
78
+ const heading = line.match(/^##\s+(.*?)\s*$/);
79
+ const title = heading?.[1];
80
+ if (!title) continue;
81
+ if (!KNOWN_DASHBOARD_SECTIONS.has(title.toLowerCase())) unknown.push(title);
82
+ }
83
+ return [...new Set(unknown)];
84
+ }
34
85
  function makeGitHub(deps) {
35
86
  const spawn = deps.spawn ?? defaultSpawn;
36
87
  const env = { ...process.env, GH_TOKEN: deps.token };
@@ -342,12 +393,53 @@ function makeGitHub(deps) {
342
393
  '.workflow_runs[0].created_at // ""'
343
394
  ]);
344
395
  return { present: true, state: wf.stdout.trim(), lastSuccessAt: runs.trim() || null };
396
+ },
397
+ async dependencyDashboard(repo) {
398
+ const out = await gh([
399
+ "api",
400
+ "--paginate",
401
+ `repos/${repo}/issues?state=open&per_page=100`,
402
+ "--jq",
403
+ '[.[] | select(.pull_request | not) | select(.title == "Dependency Dashboard") | (.body // "")] | join("\\n# ---\\n")'
404
+ ]);
405
+ const body = out.trim();
406
+ if (body.length === 0) return { present: false };
407
+ return {
408
+ present: true,
409
+ blockedBranches: parseBlockedBranches(body),
410
+ unknownSections: parseUnknownSections(body)
411
+ };
412
+ },
413
+ async branchTip(repo, branch) {
414
+ assertUrlSegment("branch", branch);
415
+ let out;
416
+ try {
417
+ out = await gh([
418
+ "api",
419
+ `repos/${repo}/commits/${branch}`,
420
+ "--jq",
421
+ // .author is the GitHub ACCOUNT (null when the commit email matches
422
+ // none), .commit.author is the raw git identity, which always exists.
423
+ '{type: (.author.type // ""), login: (.author.login // ""), email: .commit.author.email, name: .commit.author.name, date: .commit.committer.date}'
424
+ ]);
425
+ } catch (e) {
426
+ if (/HTTP 404/.test(e instanceof Error ? e.message : String(e))) return null;
427
+ throw e;
428
+ }
429
+ const raw = JSON.parse(out);
430
+ return { authorIsMachine: isMachineAuthor(raw), committedAt: raw.date };
345
431
  }
346
432
  };
347
433
  }
434
+ function isMachineAuthor(commit) {
435
+ if (commit.type === "Bot") return true;
436
+ return [commit.login, commit.email, commit.name].some(
437
+ (f) => /(\[bot\]|^renovate-bot$|^renovate-bot@)/i.test(f ?? "")
438
+ );
439
+ }
348
440
 
349
441
  export {
350
442
  assertUrlSegment,
351
443
  makeGitHub
352
444
  };
353
- //# sourceMappingURL=chunk-VSHC4HOI.js.map
445
+ //# sourceMappingURL=chunk-YAMRVXOD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/github/gh.ts"],"sourcesContent":["import { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport type { ExistingRuleset, RulesetPayload } from \"./rulesets.js\";\n\n/** Aggregate CI state of a PR's head commit, normalized from GitHub's rollup. */\nexport type CiState = \"passing\" | \"failing\" | \"pending\" | \"none\";\n\n/** GitHub's computed mergeability of a PR. `UNKNOWN` is transient — GitHub is\n * still computing it (e.g. right after a push) — so it should be read as \"not\n * known to conflict\", never as conflicting. */\nexport type PrMergeable = \"MERGEABLE\" | \"CONFLICTING\" | \"UNKNOWN\";\n\n/** A minimal open-PR summary with its head-commit CI rollup state + mergeability. */\nexport type PullRequestSummary = {\n number: number;\n title: string;\n url: string;\n headRef: string;\n ciState: CiState;\n mergeable: PrMergeable;\n};\n\n/**\n * Reject a value before it's interpolated into a `gh api` URL path. The\n * `owner/repo` split methods already validate shape; this is the defense-in-depth\n * guard for the `branch` and file-`path` segments. An unexpected value (`..`, a\n * leading `/`, whitespace, or a URL-structural char like `?#%` or a backslash)\n * could otherwise retarget the endpoint (escape the intended path, smuggle a\n * query string, or traverse). Conservative by design — legit branch names like\n * `maint/self-updating-x` and paths like `.github/workflows/ci.yml` pass.\n *\n * Both branch refs (`maint/self-updating-x`) and file paths\n * (`.github/workflows/ci.yml`) legitimately contain `/`, so a single slash is\n * allowed; what's rejected is `..`, a leading `/`, whitespace, or a\n * URL-structural char (`?`, `#`, `%`, backslash) that could escape or retarget\n * the endpoint.\n */\nexport function assertUrlSegment(kind: \"branch\" | \"path\", value: string): void {\n const structural = /[\\s?#%\\\\]|\\.\\./;\n if (value.length === 0 || value.startsWith(\"/\") || structural.test(value)) {\n throw new Error(\n `unsafe ${kind} for gh api path (illegal characters or traversal): ${JSON.stringify(value)}`,\n );\n }\n}\n\n/** Map GitHub's `statusCheckRollup.state` enum to our normalized CiState. */\nfunction mapRollupState(state: string | null | undefined): CiState {\n switch (state) {\n case \"SUCCESS\":\n return \"passing\";\n case \"FAILURE\":\n case \"ERROR\":\n return \"failing\";\n case \"PENDING\":\n case \"EXPECTED\":\n return \"pending\";\n default:\n return \"none\"; // null/undefined = no checks reported\n }\n}\n\n/** Coerce GitHub's `mergeable` enum to our PrMergeable. Anything unexpected\n * (including the literal `UNKNOWN` GitHub returns while still computing) maps to\n * `UNKNOWN` — i.e. \"not known to conflict\". Only an explicit `CONFLICTING` is. */\nfunction mapMergeable(state: string | null | undefined): PrMergeable {\n return state === \"MERGEABLE\" || state === \"CONFLICTING\" ? state : \"UNKNOWN\";\n}\n\nexport type GitHub = {\n openPullRequest: (\n repo: string,\n pr: { head: string; base: string; title: string; body: string },\n ) => Promise<{ url: string }>;\n /** Turn GitHub's platform auto-merge ON for a repo. Retained as the documented\n * ROLLBACK path — the fleet drives repos to auto-merge OFF (see\n * {@link disableRepoAutoMerge} and `recipes/self-updating`). */\n enableRepoAutoMerge: (repo: string) => Promise<void>;\n /** Turn GitHub's platform auto-merge OFF for a repo. This is the fleet default:\n * platform auto-merge is a per-PR flag anyone with write access can arm, and it\n * merges unattended outside Renovate's `packageRules`. Renovate merges from\n * inside its own run instead. */\n disableRepoAutoMerge: (repo: string) => Promise<void>;\n protectBranch: (repo: string, branch: string, requiredChecks: string[]) => Promise<void>;\n setRepoSecret: (repo: string, name: string, value: string) => Promise<void>;\n repoExists: (repo: string) => Promise<boolean>;\n defaultBranch: (repo: string) => Promise<string>;\n filesOnBranch: (repo: string, branch: string, paths: string[]) => Promise<string[]>;\n /** The raw contents of `path` on `branch`, or null when the file is absent (404).\n * Lets a caller detect a present-but-STALE file (content drift), where\n * `filesOnBranch` only sees existence. */\n fileContentsOnBranch: (repo: string, branch: string, path: string) => Promise<string | null>;\n branchProtectionContexts: (repo: string, branch: string) => Promise<string[]>;\n secretExists: (repo: string, name: string) => Promise<boolean>;\n autoMergeEnabled: (repo: string) => Promise<boolean>;\n findOpenSelfUpdatingPR: (repo: string) => Promise<string | null>;\n /** All open PRs on a repo with each head commit's normalized CI rollup state. */\n openPullRequests: (repo: string) => Promise<PullRequestSummary[]>;\n /** The default branch's latest-commit date + normalized CI rollup, one query. */\n defaultBranchStatus: (repo: string) => Promise<{ ciState: CiState; lastCommitAt: string | null }>;\n /** Renovate PRs (head `renovate/*`) merged at/after `sinceIso`. Used to record\n * `pr_automerged` fleet events. gh-shell (Actions only). */\n mergedRenovatePullRequests: (\n repo: string,\n sinceIso: string,\n ) => Promise<Array<{ number: number; title: string; url: string; mergedAt: string }>>;\n /** Fire a `workflow_dispatch` for `<workflow>` (a filename like `renovate.yml`)\n * on `ref`. Requires the token's `actions:write` scope; a 404 (no such\n * workflow) or 403 (missing scope) surfaces as a thrown error. */\n dispatchWorkflow: (repo: string, workflow: string, ref: string) => Promise<void>;\n /** REPO-sourced rulesets only (`includes_parents=false`): org-level rulesets\n * aren't ours to manage (and don't exist on the Free plan). */\n listRepoRulesets: (repo: string) => Promise<Array<{ id: number; name: string }>>;\n getRuleset: (repo: string, id: number) => Promise<ExistingRuleset>;\n createRuleset: (repo: string, payload: RulesetPayload) => Promise<void>;\n /** The update verb is PUT — `PATCH repos/{repo}/rulesets/{id}` returns 404\n * (verified live 2026-08-01), so a PATCH-based update would silently no-op\n * on every drifted repo while the recipe reported success. */\n updateRuleset: (repo: string, id: number, payload: RulesetPayload) => Promise<void>;\n /** True iff a check-run named `context` exists on `ref`'s HEAD commit. The\n * evidence gate for requiring a status check: with an empty bypass list, a\n * required context that never fires makes the repo permanently unmergeable\n * by everyone — so a context must be OBSERVED before it is required.\n * False-negatives (fresh repo, API hiccup) safely degrade to \"don't require\n * yet\"; false-positives are impossible (we name-match real runs). */\n checkContextObserved: (repo: string, ref: string, context: string) => Promise<boolean>;\n /** \"public\" | \"private\" (| \"internal\" on GHEC). Rulesets on private repos\n * need a paid plan, so callers gate on this rather than attempting. */\n repoVisibility: (repo: string) => Promise<string>;\n /** Every repo in the org (paginated) — the anti-hand-typed-list enumerator.\n * Sweeps driven by a hand-maintained or Airtable-scoped list have already\n * produced two false \"all clear\"s; enumerate from the API instead.\n * secretScanning/pushProtection come from the same listing (no extra call);\n * \"unavailable\" means the token couldn't read security_and_analysis (needs\n * admin/security read) — callers must treat that as unverified, not fine. */\n listOrgRepos: (org: string) => Promise<\n Array<{\n name: string;\n visibility: string;\n archived: boolean;\n secretScanning: string;\n pushProtection: string;\n }>\n >;\n /** Liveness of one workflow file: registered? state? when did it last\n * SUCCEED? Success, not mere existence of a run: a dead credential or\n * broken config still CREATES a run on every cron tick that fails in\n * seconds, keeping created_at perpetually fresh — counting those as alive\n * is exactly the silent-stop blindness this probe exists to catch.\n * A clean 404 is the answer `{present: false}` — any OTHER failure throws,\n * because \"couldn't check\" must never read as \"healthy\" downstream. */\n workflowHealth: (repo: string, filename: string) => Promise<WorkflowHealth>;\n /** Renovate's own Dependency Dashboard issue, which is the only place it\n * reports branches it has DECIDED TO STOP MANAGING. workflowHealth answers\n * \"did Renovate run?\"; this answers \"was it allowed to do anything?\" — a\n * distinction that cost the fleet 9 repos of frozen updates (see\n * renovateBlockedGaps). Absent dashboard is the answer `{present:false}`,\n * not an error: `dependencyDashboard` is a config a repo may disable. */\n dependencyDashboard: (repo: string) => Promise<DependencyDashboard>;\n /** Tip commit of one branch, or `null` if the branch is gone — which is the\n * answer, not an error: a dashboard naming a deleted branch is simply one\n * Renovate has not rewritten yet. */\n branchTip: (repo: string, branch: string) => Promise<BranchTip | null>;\n};\n\nexport type WorkflowHealth =\n { present: false } | { present: true; state: string; lastSuccessAt: string | null };\n\nexport type DependencyDashboard =\n { present: false } | { present: true; blockedBranches: string[]; unknownSections: string[] };\n\n/** Tip commit of a branch. `authorIsMachine` answers \"does any human own this\n * branch?\" — see renovateBlockedGaps for why that decides whether a blocked\n * branch is an orphan or a human's in-flight work. */\nexport type BranchTip = { authorIsMachine: boolean; committedAt: string };\n\n/** The section headings Renovate is known to emit, normalised to lowercase.\n * Sourced from lib/workers/repository/dependency-dashboard.ts at both live\n * majors — the wording moved once already (see BLOCKED_SECTION_RE), so the\n * legacy spellings stay in the set. Anything OUTSIDE this set means our\n * vocabulary has drifted from Renovate's and the blocked check can no longer\n * be trusted; parseUnknownSections reports it rather than reading silence as\n * health. */\nconst KNOWN_DASHBOARD_SECTIONS = new Set([\n // 43+\n \"pending approval\",\n \"group size not met\",\n \"awaiting schedule\",\n \"rate-limited\",\n \"errored\",\n \"pr creation approval required\",\n \"pr edited (blocked)\",\n \"pending status checks\",\n \"pending branch automerge\",\n \"other branches\",\n \"open\",\n \"pr closed (blocked)\",\n \"repository problems\",\n \"config migration needed\",\n \"config migration needed (blocked)\",\n \"deprecations / replacements\",\n \"abandoned dependencies\",\n \"vulnerabilities\",\n \"detected dependencies\",\n // <=42, kept so an older self-hosted Renovate does not read as drift\n \"edited/blocked\",\n \"ignored or blocked\",\n]);\n\n/** Both spellings of the \"I have stopped managing these branches\" heading.\n * Renovate renamed it `Edited/Blocked` -> `PR Edited (Blocked)` in 43.0.0,\n * and the fleet takes its Renovate major from whatever renovatebot/github-action\n * bakes in — so an ordinary action bump moves it. Matching only the current\n * wording would make this surface silently blind on the next rename. */\nconst BLOCKED_SECTION_RE = /^#{1,2}\\s+(?:PR Edited \\(Blocked\\)|Edited\\/Blocked)\\s*$/i;\n\n/** `##` (or the `# ---` join separator) opens a new section; `###` does NOT.\n * Renovate emits `### <category>` subheadings INSIDE a section when\n * dependencyDashboardCategory is set, and treating those as boundaries would\n * truncate the blocked list to nothing. */\nconst SECTION_HEADING_RE = /^#{1,2}\\s+(.*?)\\s*$/;\n\n/**\n * Pull the branches Renovate has given up on out of a Dependency Dashboard\n * body. They live under the blocked heading — \"The following updates have been\n * manually edited so Renovate will no longer make changes.\" — each as a\n * `<!-- rebase-branch=NAME -->` marker.\n *\n * Section-scoped on purpose: `rebase-branch=` markers also appear under \"Open\"\n * (where they mean \"tick to rebase\", i.e. perfectly healthy — 11 fleet repos\n * carry them today), so a whole-body grep would report those repos as blocked.\n * `PR Closed (Blocked)` is deliberately NOT read: it is Renovate's ledger of\n * operator instructions it has honoured (closing a Renovate PR is the\n * documented ignore idiom) and it uses `recreate-branch=`, not `rebase-branch=`.\n */\nexport function parseBlockedBranches(body: string): string[] {\n const found: string[] = [];\n let inBlocked = false;\n for (const line of body.split(\"\\n\")) {\n if (SECTION_HEADING_RE.test(line)) {\n inBlocked = BLOCKED_SECTION_RE.test(line);\n continue;\n }\n if (!inBlocked) continue;\n const marker = line.match(/rebase-branch=([^\\s>]+)/);\n if (marker?.[1]) found.push(marker[1]);\n }\n return [...new Set(found)];\n}\n\n/**\n * Section headings in a dashboard body that Renovate is not known to emit.\n * `parseBlockedBranches` returning `[]` is ambiguous — it means both \"nothing\n * is blocked\" and \"I did not understand this dashboard\" — and this is what\n * separates them, so a heading rename cannot turn the whole fleet green in\n * silence. Same doctrine as workflowHealth: \"couldn't check\" must never read\n * as \"healthy\" downstream.\n */\nexport function parseUnknownSections(body: string): string[] {\n const unknown: string[] = [];\n for (const line of body.split(\"\\n\")) {\n const heading = line.match(/^##\\s+(.*?)\\s*$/);\n const title = heading?.[1];\n if (!title) continue;\n if (!KNOWN_DASHBOARD_SECTIONS.has(title.toLowerCase())) unknown.push(title);\n }\n return [...new Set(unknown)];\n}\n\nexport function makeGitHub(deps: { token: string; spawn?: SpawnFn }): GitHub {\n const spawn = deps.spawn ?? defaultSpawn;\n const env = { ...process.env, GH_TOKEN: deps.token };\n\n async function gh(args: string[]): Promise<string> {\n const r = await spawn(\"gh\", args, { env, timeoutMs: 60_000 });\n if (r.code !== 0) throw new Error(`gh ${args[0]} failed (code ${r.code}): ${r.stderr.trim()}`);\n return r.stdout;\n }\n\n /** `gh api` with a JSON request body. Ruleset payloads have nested arrays/\n * objects that `-f`/`-F` field flags cannot express, and the spawn wrapper\n * has no stdin — so the body rides a private temp file via `--input`. */\n async function ghJson(method: \"POST\" | \"PUT\", path: string, body: unknown): Promise<string> {\n const dir = await mkdtemp(join(tmpdir(), \"gh-json-\"));\n const file = join(dir, \"body.json\");\n try {\n await writeFile(file, JSON.stringify(body), \"utf-8\");\n return await gh([\"api\", \"-X\", method, path, \"--input\", file]);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n return {\n async openPullRequest(repo, pr) {\n const out = await gh([\n \"pr\",\n \"create\",\n \"--repo\",\n repo,\n \"--head\",\n pr.head,\n \"--base\",\n pr.base,\n \"--title\",\n pr.title,\n \"--body\",\n pr.body,\n ]);\n return { url: out.trim() };\n },\n async enableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=true\"]);\n },\n async disableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=false\"]);\n },\n async protectBranch(repo, branch, requiredChecks) {\n assertUrlSegment(\"branch\", branch);\n const args = [\n \"api\",\n \"-X\",\n \"PUT\",\n `repos/${repo}/branches/${branch}/protection`,\n \"-H\",\n \"Accept: application/vnd.github+json\",\n \"-F\",\n \"required_status_checks[strict]=true\",\n ...requiredChecks.flatMap((c) => [\"-f\", `required_status_checks[contexts][]=${c}`]),\n \"-F\",\n \"enforce_admins=true\",\n \"-F\",\n \"required_pull_request_reviews=null\",\n \"-F\",\n \"restrictions=null\",\n ];\n await gh(args);\n },\n async setRepoSecret(repo, name, value) {\n await gh([\"secret\", \"set\", name, \"--repo\", repo, \"--body\", value]);\n },\n async repoExists(repo) {\n const r = await spawn(\"gh\", [\"api\", `repos/${repo}`], { env, timeoutMs: 60_000 });\n return r.code === 0;\n },\n async defaultBranch(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".default_branch\"]);\n return out.trim();\n },\n // filesOnBranch and branchProtectionContexts call `spawn` directly (not the\n // throwing `gh()` helper) because a 404 is an expected, meaningful answer —\n // \"file/protection absent\" — not an error. The remaining readers use `gh()`\n // since a non-200 there is a genuine failure (e.g. missing token scope).\n async filesOnBranch(repo, branch, paths) {\n assertUrlSegment(\"branch\", branch);\n const present: string[] = [];\n for (const p of paths) {\n assertUrlSegment(\"path\", p);\n const r = await spawn(\"gh\", [`api`, `repos/${repo}/contents/${p}?ref=${branch}`], {\n env,\n timeoutMs: 60_000,\n });\n if (r.code === 0) present.push(p);\n }\n return present;\n },\n async fileContentsOnBranch(repo, branch, path) {\n // Same spawn-directly rationale as filesOnBranch: a 404 (file absent) is an\n // expected answer (null), not an error. The `raw` media type returns the file\n // bytes verbatim (no base64 envelope) so the result compares directly against a\n // canonical template.\n assertUrlSegment(\"branch\", branch);\n assertUrlSegment(\"path\", path);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/contents/${path}?ref=${branch}`,\n \"-H\",\n \"Accept: application/vnd.github.raw\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return null; // 404 = file absent on this branch\n return r.stdout;\n },\n async branchProtectionContexts(repo, branch) {\n assertUrlSegment(\"branch\", branch);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/branches/${branch}/protection`,\n \"--jq\",\n \".required_status_checks.contexts[]?\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return []; // 404 = no protection configured\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n },\n async secretExists(repo, name) {\n // per_page=100: the REST default of 30 would false-negative on a repo with >30 secrets,\n // wrongly reporting an existing secret absent (→ a needless overwrite).\n const out = await gh([\n \"api\",\n `repos/${repo}/actions/secrets?per_page=100`,\n \"--jq\",\n \".secrets[].name\",\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(name);\n },\n async autoMergeEnabled(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".allow_auto_merge\"]);\n return out.trim() === \"true\";\n },\n async findOpenSelfUpdatingPR(repo) {\n // per_page=100: with the REST default of 30, a repo with >30 open PRs (plausible under\n // Renovate) could page past the existing self-updating PR and open a duplicate.\n const out = await gh([\n \"api\",\n `repos/${repo}/pulls?state=open&per_page=100`,\n \"--jq\",\n '.[] | select(.head.ref | startswith(\"maint/self-updating-\")) | .html_url',\n ]);\n const first = out\n .split(\"\\n\")\n .map((l) => l.trim())\n .find((l) => l.length > 0);\n return first ?? null;\n },\n async openPullRequests(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`openPullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"pullRequests(states:OPEN,first:100,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number title url headRefName mergeable \" +\n \"commits(last:1){nodes{commit{statusCheckRollup{state}}}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n pullRequests?: {\n nodes?: Array<{\n number: number;\n title: string;\n url: string;\n headRefName: string;\n mergeable?: string;\n commits?: {\n nodes?: Array<{ commit?: { statusCheckRollup?: { state?: string } } }>;\n };\n }>;\n };\n };\n };\n };\n const nodes = parsed.data?.repository?.pullRequests?.nodes ?? [];\n return nodes.map((n) => ({\n number: n.number,\n title: n.title,\n url: n.url,\n headRef: n.headRefName,\n ciState: mapRollupState(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state),\n mergeable: mapMergeable(n.mergeable),\n }));\n },\n async defaultBranchStatus(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`defaultBranchStatus: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"defaultBranchRef{target{... on Commit{committedDate statusCheckRollup{state}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n defaultBranchRef?: {\n target?: { committedDate?: string; statusCheckRollup?: { state?: string } | null };\n } | null;\n };\n };\n };\n const target = parsed.data?.repository?.defaultBranchRef?.target;\n return {\n ciState: mapRollupState(target?.statusCheckRollup?.state),\n lastCommitAt: target?.committedDate ?? null,\n };\n },\n async mergedRenovatePullRequests(repo, sinceIso) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`mergedRenovatePullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n // per_page=50: comfortably covers a week of Renovate merges on one repo. The\n // list endpoint returns merged_at + head.ref, so the filter is local.\n const out = await gh([\n \"api\",\n `repos/${owner}/${name}/pulls?state=closed&sort=updated&direction=desc&per_page=50`,\n ]);\n const arr = JSON.parse(out) as Array<{\n number: number;\n title: string;\n html_url: string;\n merged_at: string | null;\n head?: { ref?: string };\n }>;\n return arr\n .filter(\n (p) =>\n p.merged_at !== null &&\n p.merged_at >= sinceIso && // ISO8601 UTC strings sort lexicographically\n (p.head?.ref ?? \"\").startsWith(\"renovate/\"),\n )\n .map((p) => ({\n number: p.number,\n title: p.title,\n url: p.html_url,\n mergedAt: p.merged_at as string,\n }));\n },\n async dispatchWorkflow(repo, workflow, ref) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`dispatchWorkflow: expected \"owner/repo\", got \"${repo}\"`);\n }\n // Every segment interpolates into the API path, so guard them all like the\n // other write methods do (defense in depth). `owner`/`name` are the most\n // operator-controlled (typed into Airtable's \"Git repo\"); `workflow` is a\n // constant today; `ref` is repo-sourced. A junk value like `repo?x=1` would\n // otherwise smuggle a query string past the bare two-part shape check.\n assertUrlSegment(\"path\", owner);\n assertUrlSegment(\"path\", name);\n assertUrlSegment(\"path\", workflow);\n assertUrlSegment(\"branch\", ref);\n await gh([\n \"api\",\n \"-X\",\n \"POST\",\n `repos/${owner}/${name}/actions/workflows/${workflow}/dispatches`,\n \"-f\",\n `ref=${ref}`,\n ]);\n },\n async listRepoRulesets(repo) {\n // includes_parents=false: only rulesets SOURCED on this repo are ours to\n // heal; an org-level ruleset (paid plans) must never be PUT from here.\n const out = await gh([\n \"api\",\n `repos/${repo}/rulesets?per_page=100&includes_parents=false`,\n \"--jq\",\n '.[] | \"\\\\(.id)\\\\t\\\\(.name)\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.includes(\"\\t\")) // a tab-less line has no id/name split — never fabricate a NaN row from it\n .map((l) => {\n const tab = l.indexOf(\"\\t\");\n return { id: Number(l.slice(0, tab)), name: l.slice(tab + 1) };\n })\n .filter((r) => Number.isSafeInteger(r.id));\n },\n async getRuleset(repo, id) {\n const out = await gh([\"api\", `repos/${repo}/rulesets/${Math.trunc(id)}`]);\n return JSON.parse(out) as ExistingRuleset;\n },\n async createRuleset(repo, payload) {\n await ghJson(\"POST\", `repos/${repo}/rulesets`, payload);\n },\n async updateRuleset(repo, id, payload) {\n // PUT, not PATCH: PATCH on this endpoint 404s (verified live 2026-08-01),\n // which a naive implementation would surface as \"update failed\" at best —\n // or, with a lenient error path, as a silent no-op reported as healed.\n await ghJson(\"PUT\", `repos/${repo}/rulesets/${Math.trunc(id)}`, payload);\n },\n async checkContextObserved(repo, ref, context) {\n assertUrlSegment(\"branch\", ref);\n // spawn-direct (not the throwing gh()): a 404/409 — empty repo, no commits\n // on the ref yet — is an expected answer meaning \"no evidence\", not an\n // error. Any failure degrades to false, i.e. \"don't require the check\n // yet\", which is the safe direction (see checkContextObserved's contract).\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/commits/${ref}/check-runs?per_page=100`,\n \"--jq\",\n \".check_runs[].name\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return false;\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(context);\n },\n async repoVisibility(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".visibility\"]);\n return out.trim();\n },\n async listOrgRepos(org) {\n assertUrlSegment(\"path\", org);\n // --paginate: the org is >27 repos and the default page of 30 is exactly\n // the trap that produced the 2026-07-31 false \"queue is empty\".\n const out = await gh([\n \"api\",\n \"--paginate\",\n `orgs/${org}/repos?per_page=100`,\n \"--jq\",\n '.[] | \"\\\\(.name)\\\\t\\\\(.visibility)\\\\t\\\\(.archived)\\\\t\\\\(.security_and_analysis.secret_scanning.status // \"unavailable\")\\\\t\\\\(.security_and_analysis.secret_scanning_push_protection.status // \"unavailable\")\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => {\n const [name = \"\", visibility = \"\", archived = \"\", ss = \"\", pp = \"\"] = l.split(\"\\t\");\n return {\n name,\n visibility,\n archived: archived === \"true\",\n secretScanning: ss || \"unavailable\",\n pushProtection: pp || \"unavailable\",\n };\n });\n },\n async workflowHealth(repo, filename) {\n // spawn-direct for the workflow GET: 404 (file not registered as a\n // workflow) is an expected ANSWER, not an error. Everything else throws —\n // per the contract, an unreadable workflow must surface as unverified.\n const wf = await spawn(\n \"gh\",\n [\"api\", `repos/${repo}/actions/workflows/${filename}`, \"--jq\", \".state\"],\n { env, timeoutMs: 60_000 },\n );\n if (wf.code !== 0) {\n if (/HTTP 404/.test(wf.stderr)) return { present: false };\n throw new Error(`workflowHealth(${repo}/${filename}) failed: ${wf.stderr.trim()}`);\n }\n // status=success: filter to runs that actually CONCLUDED success. Without\n // it, a schedule whose every run fails (revoked App key, broken config)\n // reads as hours-fresh forever while zero updates flow.\n const runs = await gh([\n \"api\",\n `repos/${repo}/actions/workflows/${filename}/runs?status=success&per_page=1`,\n \"--jq\",\n '.workflow_runs[0].created_at // \"\"',\n ]);\n return { present: true, state: wf.stdout.trim(), lastSuccessAt: runs.trim() || null };\n },\n async dependencyDashboard(repo) {\n // Every open Dependency Dashboard, not just the first: the App-identity\n // migration left several repos holding TWO (alamo-anatomy had #3 and\n // #39), and the stale one is not always the one carrying the blocked\n // section. `select(.pull_request | not)` because /issues returns PRs too.\n //\n // --paginate is load-bearing, not defensive: /issues returns issues AND\n // PRs newest-first, the dashboard is created at onboarding and so is\n // typically the OLDEST open issue in the repo, and the title filter runs\n // client-side in jq AFTER the page cut. Without it, a repo with 100 open\n // items newer than its dashboard reads as {present:false} — which this\n // surface deliberately treats as not-a-gap, i.e. a blocked repo would\n // report clean. (Verified against renovatebot/renovate itself, whose\n // dashboard is issue #2958 from 2018 and carries a live blocked section.)\n const out = await gh([\n \"api\",\n \"--paginate\",\n `repos/${repo}/issues?state=open&per_page=100`,\n \"--jq\",\n '[.[] | select(.pull_request | not) | select(.title == \"Dependency Dashboard\") | (.body // \"\")] | join(\"\\\\n# ---\\\\n\")',\n ]);\n const body = out.trim();\n if (body.length === 0) return { present: false };\n return {\n present: true,\n blockedBranches: parseBlockedBranches(body),\n unknownSections: parseUnknownSections(body),\n };\n },\n async branchTip(repo, branch) {\n // Raw, not percent-encoded: the ref segment legitimately contains `/`\n // (`renovate/all-minor-patch`) and GitHub resolves it as a ref only when\n // the slash is literal. assertUrlSegment is what keeps that safe.\n assertUrlSegment(\"branch\", branch);\n let out: string;\n try {\n out = await gh([\n \"api\",\n `repos/${repo}/commits/${branch}`,\n \"--jq\",\n // .author is the GitHub ACCOUNT (null when the commit email matches\n // none), .commit.author is the raw git identity, which always exists.\n '{type: (.author.type // \"\"), login: (.author.login // \"\"), email: .commit.author.email, name: .commit.author.name, date: .commit.committer.date}',\n ]);\n } catch (e) {\n if (/HTTP 404/.test(e instanceof Error ? e.message : String(e))) return null;\n throw e;\n }\n const raw = JSON.parse(out) as {\n type: string;\n login: string;\n email: string;\n name: string;\n date: string;\n };\n return { authorIsMachine: isMachineAuthor(raw), committedAt: raw.date };\n },\n };\n}\n\n/**\n * Does this commit have a human behind it? Deliberately NOT just\n * `.author.type === \"Bot\"`: the identity whose orphaned branches froze nine\n * repos in August 2026 was `renovate-bot`, a PAT-driven machine account that\n * GitHub reports as type `User`. A rule keyed on the Bot type would have\n * missed the exact incident this audit exists to catch, so the known machine\n * logins are matched by name too.\n *\n * A miss here costs latency, never blindness — renovateBlockedGaps still\n * reports any blocked branch once it goes stale, whoever authored it.\n */\nexport function isMachineAuthor(commit: {\n type: string;\n login: string;\n email: string;\n name: string;\n}): boolean {\n if (commit.type === \"Bot\") return true;\n return [commit.login, commit.email, commit.name].some((f) =>\n /(\\[bot\\]|^renovate-bot$|^renovate-bot@)/i.test(f ?? \"\"),\n );\n}\n"],"mappings":"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","names":[]}
package/dist/cli/bin.js CHANGED
@@ -128,7 +128,7 @@ cli.command(
128
128
  "Bootstrap a repo to keep itself updated (Renovate + protection; platform auto-merge off; ci.yml is the starter's, not managed here)."
129
129
  ).option("--dry", "List what would be enabled without writing or opening PRs").option("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable"').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
130
130
  async (site, opts) => runOrExit(
131
- async () => (await import("../self-updating-ALMUWJNX.js")).runSelfUpdatingCommand(site, opts),
131
+ async () => (await import("../self-updating-WJEHPASK.js")).runSelfUpdatingCommand(site, opts),
132
132
  opts
133
133
  )
134
134
  );
@@ -205,14 +205,14 @@ cli.command(
205
205
  "--fleet <inventory>",
206
206
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
207
207
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
208
- async (site, opts) => runOrExit(async () => (await import("../init-Y5O33BUL.js")).runInitCommand(site, opts), opts)
208
+ async (site, opts) => runOrExit(async () => (await import("../init-RYKQFSZI.js")).runInitCommand(site, opts), opts)
209
209
  );
210
210
  cli.command(
211
211
  "launch <site>",
212
212
  "Bootstrap + first-audit a site, then draft its launch email for approval."
213
213
  ).action(
214
214
  async (site, opts) => runOrExit(
215
- async () => (await import("../launch-3SYLX263.js")).runLaunchCommand(site, opts),
215
+ async () => (await import("../launch-EGKYS3BD.js")).runLaunchCommand(site, opts),
216
216
  opts
217
217
  )
218
218
  );
@@ -227,7 +227,19 @@ cli.command(
227
227
  "Draft the monthly-report announcement email for maintenance sites (all, or one) for approval."
228
228
  ).action(
229
229
  async (site, opts) => runOrExit(
230
- async () => (await import("../announce-RUT247HS.js")).runAnnounceCommand(site, opts),
230
+ async () => (await import("../announce-KZDU65IF.js")).runAnnounceCommand(site, opts),
231
+ opts
232
+ )
233
+ );
234
+ cli.command(
235
+ "forms-notify-target [site]",
236
+ "Show who a form submission would email, and optionally flip the pre-launch guard (read-back confirmed)."
237
+ ).option("--set <on|off>", "Flip the guard: on = route to operator, off = restore.").option("--restore <status>", "Status to restore with --set off. Required; never inferred.").action(
238
+ async (site, opts) => runOrExit(
239
+ async () => (await import("../forms-notify-target-CL3HRVKN.js")).runFormsNotifyTargetCommand(
240
+ site,
241
+ opts
242
+ ),
231
243
  opts
232
244
  )
233
245
  );
@@ -236,7 +248,7 @@ cli.command(
236
248
  "Operator self-tests. kind=email: preview a report email for a site (or --all) to yourself."
237
249
  ).option("--type <type>", "Report type: announcement (default) | maintenance | testing | launch").option("--to <addr>", "Recipient(s), comma-separated. Default: OPERATOR_EMAIL.").option("--all", "Send a preview for every maintenance site (to --to/operator).").option("--dry-run", "Render only; write reports/<slug>/selftest-<type>.html; do not send.").action(
238
250
  async (kind, site, opts) => runOrExit(
239
- async () => (await import("../selftest-PJFABZDM.js")).runSelftestCommand(kind, site, opts),
251
+ async () => (await import("../selftest-ZJDB4JQB.js")).runSelftestCommand(kind, site, opts),
240
252
  opts
241
253
  )
242
254
  );
@@ -248,7 +260,7 @@ cli.command(
248
260
  "Fleet mode: announcement\u2192maintenance sites; maintenance/testing\u2192all schedulable sites."
249
261
  ).action(
250
262
  async (site, opts) => runOrExit(
251
- async () => (await import("../preflight-2PWVVIRR.js")).runPreflightCommand(site, opts),
263
+ async () => (await import("../preflight-SRDQS7Y6.js")).runPreflightCommand(site, opts),
252
264
  opts
253
265
  )
254
266
  );
@@ -269,7 +281,7 @@ cli.command("report [site]", "Draft or send maintenance/testing reports.").optio
269
281
  "Email the operator one daily digest of reports ready for approval (skips when empty)."
270
282
  ).action(
271
283
  async (site, opts) => runOrExit(
272
- async () => (await import("../report-IU4CLJS5.js")).runReportCommand(site, opts),
284
+ async () => (await import("../report-MLCIIML5.js")).runReportCommand(site, opts),
273
285
  opts
274
286
  )
275
287
  );
@@ -278,7 +290,7 @@ cli.command(
278
290
  "Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write Airtable."
279
291
  ).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-airtable", "Write each site's signals back to its Websites row.").action(
280
292
  async (opts) => runOrExit(
281
- async () => (await import("../github-signals-4LWQGD5V.js")).runGitHubSignalsCommand({
293
+ async () => (await import("../github-signals-2BFMZLPV.js")).runGitHubSignalsCommand({
282
294
  fleet: opts.fleet,
283
295
  writeAirtable: opts.writeAirtable
284
296
  }),
@@ -290,7 +302,7 @@ cli.command(
290
302
  "Verify every public org repo carries a sound branch ruleset (empty bypass, PR-required)."
291
303
  ).option("--org <org>", "GitHub org to sweep (enumerated from the API, never a hand list).").action(
292
304
  async (opts) => runOrExit(
293
- async () => (await import("../protection-audit-X3BHT2MB.js")).runProtectionAuditCommand({
305
+ async () => (await import("../protection-audit-2UR45SOC.js")).runProtectionAuditCommand({
294
306
  org: opts.org
295
307
  }),
296
308
  opts
@@ -310,7 +322,7 @@ cli.command(
310
322
  "Write the re-buckets: status \u2192 spam_auto, spam_score/spam_reason replaced with the new verdict + 'retro-rescore' marker."
311
323
  ).action(
312
324
  async (action, opts) => runOrExit(
313
- async () => (await import("../submissions-SURN7BV5.js")).runSubmissionsCommand(action, opts),
325
+ async () => (await import("../submissions-RAYGOASP.js")).runSubmissionsCommand(action, opts),
314
326
  opts
315
327
  )
316
328
  );
@@ -319,7 +331,7 @@ cli.command(
319
331
  "Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns."
320
332
  ).option("--fleet", "Run across every active, repo-backed site in the Airtable inventory.").action(
321
333
  async (opts) => runOrExit(
322
- async () => (await import("../renovate-dispatch-2VYCEH42.js")).runRenovateDispatchCommand({
334
+ async () => (await import("../renovate-dispatch-7D7FMMJP.js")).runRenovateDispatchCommand({
323
335
  fleet: opts.fleet
324
336
  }),
325
337
  opts
@@ -345,7 +357,7 @@ cli.command(
345
357
  "freeze: JSON declaring slots the template has no token for (video poster, a panel rebuilt from a baked image). Keys must start with 'x.'"
346
358
  ).action(
347
359
  async (action, dir, opts) => runOrExit(
348
- async () => (await import("../blux-2P4FGCUL.js")).runBluxCommand(action, dir, opts),
360
+ async () => (await import("../blux-EATHOHKK.js")).runBluxCommand(action, dir, opts),
349
361
  opts
350
362
  )
351
363
  );