@reddoorla/maintenance 0.87.0 → 0.88.2

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 (186) hide show
  1. package/dist/airtable-QG5ICI3Y.js +9 -0
  2. package/dist/{announce-I2Z4JN3I.js → announce-GKD5YJNB.js} +9 -9
  3. package/dist/{blux-ZGDSHI3U.js → blux-DN4MI532.js} +5 -5
  4. package/dist/{bump-deps-CGVELRLK.js → bump-deps-KGH5UYSF.js} +4 -4
  5. package/dist/{chunk-SDDPUAWH.js → chunk-6I6L3CMX.js} +2 -2
  6. package/dist/{chunk-ZBDZTJUO.js → chunk-7FFUBF4I.js} +5 -2
  7. package/dist/chunk-7FFUBF4I.js.map +1 -0
  8. package/dist/{chunk-HSI6Q7MI.js → chunk-7TMWBXX7.js} +159 -29
  9. package/dist/chunk-7TMWBXX7.js.map +1 -0
  10. package/dist/{chunk-DEWJMMG3.js → chunk-A2UNJIKH.js} +15 -7
  11. package/dist/chunk-A2UNJIKH.js.map +1 -0
  12. package/dist/{chunk-NCMZMLY7.js → chunk-ANJNTAMM.js} +3 -69
  13. package/dist/chunk-ANJNTAMM.js.map +1 -0
  14. package/dist/{chunk-3JQ2FO3D.js → chunk-B23FFWU4.js} +2 -2
  15. package/dist/{chunk-DJZWYIPP.js → chunk-BGTYPVLT.js} +1 -1
  16. package/dist/chunk-BGTYPVLT.js.map +1 -0
  17. package/dist/chunk-BRYXFH77.js +70 -0
  18. package/dist/chunk-BRYXFH77.js.map +1 -0
  19. package/dist/{chunk-NDWYJLRF.js → chunk-DGZWFI2G.js} +3 -3
  20. package/dist/{chunk-HHMMUR4P.js → chunk-GQDDWYU2.js} +45 -8
  21. package/dist/chunk-GQDDWYU2.js.map +1 -0
  22. package/dist/{chunk-BBUSGEES.js → chunk-J3WEZGIN.js} +6 -6
  23. package/dist/{chunk-BNM5POSJ.js → chunk-JRHIFA5R.js} +2 -2
  24. package/dist/{chunk-TD2NNXLZ.js → chunk-K6SLF6GI.js} +5 -5
  25. package/dist/{chunk-MT2JINSS.js → chunk-KJGKZLCT.js} +3 -3
  26. package/dist/{chunk-NXNPDCF3.js → chunk-KMCZA7CH.js} +11 -1
  27. package/dist/chunk-KMCZA7CH.js.map +1 -0
  28. package/dist/{chunk-3SF7D4DY.js → chunk-MFHS7IQ2.js} +36 -1
  29. package/dist/{chunk-3SF7D4DY.js.map → chunk-MFHS7IQ2.js.map} +1 -1
  30. package/dist/{chunk-SNSTJLQW.js → chunk-NO74DXDP.js} +48 -2
  31. package/dist/chunk-NO74DXDP.js.map +1 -0
  32. package/dist/{chunk-C6B4RHXR.js → chunk-O3GT46R2.js} +8 -2
  33. package/dist/chunk-O3GT46R2.js.map +1 -0
  34. package/dist/{chunk-C6CLRKPV.js → chunk-PR343K7F.js} +2 -2
  35. package/dist/{chunk-CROKUHJB.js → chunk-Q47DU255.js} +4 -4
  36. package/dist/{chunk-EVBCK2KC.js → chunk-RARREDQE.js} +2 -1
  37. package/dist/{chunk-EVBCK2KC.js.map → chunk-RARREDQE.js.map} +1 -1
  38. package/dist/chunk-RUPPWAD6.js +7 -0
  39. package/dist/chunk-RUPPWAD6.js.map +1 -0
  40. package/dist/{chunk-VSBFM7DS.js → chunk-SJOXWJ5Y.js} +3 -3
  41. package/dist/{chunk-HEWSRXYV.js → chunk-ULHPALI5.js} +3 -3
  42. package/dist/{chunk-XUGSZP7Z.js → chunk-W45FZEGE.js} +2 -2
  43. package/dist/{chunk-JHGHLKFS.js → chunk-WS6NU475.js} +2 -2
  44. package/dist/{chunk-ZGBEIWSC.js → chunk-XP5WM2LV.js} +5 -5
  45. package/dist/{chunk-6TWWY7ZU.js → chunk-Z3JXLULD.js} +2 -2
  46. package/dist/cli/bin.js +27 -27
  47. package/dist/cli/bin.js.map +1 -1
  48. package/dist/cli/commands/audit.d.ts +4 -0
  49. package/dist/cli/commands/audit.js +16 -13
  50. package/dist/cli/commands/audit.js.map +1 -1
  51. package/dist/client-JYKPVCJX.js +11 -0
  52. package/dist/configs/playwright-a11y.js +1 -1
  53. package/dist/{convert-to-pnpm-QFJOEP5V.js → convert-to-pnpm-NWB5FCQ2.js} +4 -4
  54. package/dist/{db-UXNIZ2RG.js → db-V6UIS433.js} +121 -26
  55. package/dist/db-V6UIS433.js.map +1 -0
  56. package/dist/{digest-FW6QMZZJ.js → digest-2JEAEJK6.js} +53 -29
  57. package/dist/digest-2JEAEJK6.js.map +1 -0
  58. package/dist/digest-state-CRZRNGDI.js +25 -0
  59. package/dist/digest-state-CRZRNGDI.js.map +1 -0
  60. package/dist/digest-state-F37XUCS5.js +13 -0
  61. package/dist/{dump-P6EBSIOH.js → dump-EPL7AZFR.js} +38 -13
  62. package/dist/dump-EPL7AZFR.js.map +1 -0
  63. package/dist/{email-KJXASVH5.js → email-SL7464YI.js} +16 -6
  64. package/dist/{email-KJXASVH5.js.map → email-SL7464YI.js.map} +1 -1
  65. package/dist/{ensure-site-4DCEBAPM.js → ensure-site-FU6527OU.js} +2 -2
  66. package/dist/{fleet-state-7TJ2GYT2.js → fleet-state-R5LU7IC3.js} +4 -2
  67. package/dist/{forms-notify-target-2EAWCWBW.js → forms-notify-target-SGDGXXK5.js} +6 -6
  68. package/dist/{github-signals-E2WPYTBK.js → github-signals-GWGCKEE2.js} +7 -6
  69. package/dist/{github-signals-E2WPYTBK.js.map → github-signals-GWGCKEE2.js.map} +1 -1
  70. package/dist/{header-image-6IC53W27.js → header-image-7A7XLVEU.js} +2 -2
  71. package/dist/{health-endpoint-RFXL2F4A.js → health-endpoint-UVX6R5N5.js} +4 -4
  72. package/dist/{health-mirror-L5JBEJVA.js → health-mirror-GCDN7BJL.js} +11 -6
  73. package/dist/health-mirror-GCDN7BJL.js.map +1 -0
  74. package/dist/index.js +128 -88
  75. package/dist/index.js.map +1 -1
  76. package/dist/{init-XRAQZYID.js → init-FGTYNGUI.js} +9 -9
  77. package/dist/{launch-IX4Y5JJC.js → launch-5H7LPEGP.js} +12 -11
  78. package/dist/{launch-IX4Y5JJC.js.map → launch-5H7LPEGP.js.map} +1 -1
  79. package/dist/migrate-YITCXBLS.js +7 -0
  80. package/dist/{notify-C645PGD5.js → notify-4HP4GZ2H.js} +5 -5
  81. package/dist/{onboard-O4IAHJMO.js → onboard-BKCYBAVW.js} +4 -4
  82. package/dist/{orchestrate-YLWQ6KK2.js → orchestrate-JNKCWU5K.js} +9 -9
  83. package/dist/{parity-56KA2DGR.js → parity-NBYZUBFU.js} +4 -2
  84. package/dist/pdf-N5QLQ5ZQ.js +38 -0
  85. package/dist/pdf-N5QLQ5ZQ.js.map +1 -0
  86. package/dist/{pipeline-3AANZFCR.js → pipeline-WYTXSLHX.js} +3 -3
  87. package/dist/{preflight-TNORKGCX.js → preflight-IMTYDDD4.js} +10 -10
  88. package/dist/{prismic-ci-G2AK53VL.js → prismic-ci-MY2U6NN7.js} +4 -4
  89. package/dist/{prismic-models-O6Z3OK3A.js → prismic-models-NJN5JORF.js} +6 -6
  90. package/dist/prospect/types.d.ts +14 -0
  91. package/dist/{prospect-audit-KSXUNQ5J.js → prospect-audit-JMBRVXSG.js} +32 -13
  92. package/dist/prospect-audit-JMBRVXSG.js.map +1 -0
  93. package/dist/render-VX72AESY.js +10 -0
  94. package/dist/{render-from-row-BWSOIYDS.js → render-from-row-DOYJDO2L.js} +5 -5
  95. package/dist/{replay-C2LXMJZP.js → replay-4S6ENYCH.js} +3 -3
  96. package/dist/{report-R3E3Y7HR.js → report-TALY5P3L.js} +17 -17
  97. package/dist/{report-mirror-4MNORYQ4.js → report-mirror-AAK3MXHY.js} +12 -6
  98. package/dist/report-mirror-AAK3MXHY.js.map +1 -0
  99. package/dist/schema-75PJECAE.js +18 -0
  100. package/dist/schema-75PJECAE.js.map +1 -0
  101. package/dist/{self-updating-UCIWESAX.js → self-updating-NTCCWJYZ.js} +4 -4
  102. package/dist/{selftest-VGHWPN72.js → selftest-NZTR4SM2.js} +11 -11
  103. package/dist/{site-mirror-NIREX5ME.js → site-mirror-Q763PLGG.js} +17 -7
  104. package/dist/site-mirror-Q763PLGG.js.map +1 -0
  105. package/dist/{smoke-suite-U3UKD3NS.js → smoke-suite-4JFZZYVW.js} +4 -4
  106. package/dist/{spam-classifier-XLBFCO7G.js → spam-classifier-T5H4VTP6.js} +4 -2
  107. package/dist/{submissions-IQEJX6FK.js → submissions-TEHPBSZJ.js} +4 -4
  108. package/dist/{svelte-codemods-GOKXTBHW.js → svelte-codemods-QAPDZC6F.js} +4 -4
  109. package/dist/{sync-CTUVXMCK.js → sync-GBFV45WX.js} +4 -2
  110. package/dist/{sync-CTUVXMCK.js.map → sync-GBFV45WX.js.map} +1 -1
  111. package/dist/{sync-configs-SPTLFJCC.js → sync-configs-34JIA6KZ.js} +4 -4
  112. package/dist/{upgrade-W6ZR3FJH.js → upgrade-IR2OO3KP.js} +4 -4
  113. package/dist/{webhook-VOGOW3W6.js → webhook-XM6EV4XT.js} +2 -2
  114. package/dist/{write-audits-to-airtable-CMKHGJNQ.js → write-audits-to-airtable-OBHGR7DQ.js} +5 -2
  115. package/dist/write-audits-to-airtable-OBHGR7DQ.js.map +1 -0
  116. package/package.json +1 -1
  117. package/dist/airtable-ZBUPQJSC.js +0 -9
  118. package/dist/chunk-C6B4RHXR.js.map +0 -1
  119. package/dist/chunk-DEWJMMG3.js.map +0 -1
  120. package/dist/chunk-DJZWYIPP.js.map +0 -1
  121. package/dist/chunk-HHMMUR4P.js.map +0 -1
  122. package/dist/chunk-HSI6Q7MI.js.map +0 -1
  123. package/dist/chunk-NCMZMLY7.js.map +0 -1
  124. package/dist/chunk-NXNPDCF3.js.map +0 -1
  125. package/dist/chunk-SNSTJLQW.js.map +0 -1
  126. package/dist/chunk-ZBDZTJUO.js.map +0 -1
  127. package/dist/client-AUE3XUPO.js +0 -11
  128. package/dist/db-UXNIZ2RG.js.map +0 -1
  129. package/dist/digest-FW6QMZZJ.js.map +0 -1
  130. package/dist/dump-P6EBSIOH.js.map +0 -1
  131. package/dist/health-mirror-L5JBEJVA.js.map +0 -1
  132. package/dist/migrate-GQH237A6.js +0 -7
  133. package/dist/prospect-audit-KSXUNQ5J.js.map +0 -1
  134. package/dist/render-IFOGFB5T.js +0 -10
  135. package/dist/report-mirror-4MNORYQ4.js.map +0 -1
  136. package/dist/site-mirror-NIREX5ME.js.map +0 -1
  137. /package/dist/{airtable-ZBUPQJSC.js.map → airtable-QG5ICI3Y.js.map} +0 -0
  138. /package/dist/{announce-I2Z4JN3I.js.map → announce-GKD5YJNB.js.map} +0 -0
  139. /package/dist/{blux-ZGDSHI3U.js.map → blux-DN4MI532.js.map} +0 -0
  140. /package/dist/{bump-deps-CGVELRLK.js.map → bump-deps-KGH5UYSF.js.map} +0 -0
  141. /package/dist/{chunk-SDDPUAWH.js.map → chunk-6I6L3CMX.js.map} +0 -0
  142. /package/dist/{chunk-3JQ2FO3D.js.map → chunk-B23FFWU4.js.map} +0 -0
  143. /package/dist/{chunk-NDWYJLRF.js.map → chunk-DGZWFI2G.js.map} +0 -0
  144. /package/dist/{chunk-BBUSGEES.js.map → chunk-J3WEZGIN.js.map} +0 -0
  145. /package/dist/{chunk-BNM5POSJ.js.map → chunk-JRHIFA5R.js.map} +0 -0
  146. /package/dist/{chunk-TD2NNXLZ.js.map → chunk-K6SLF6GI.js.map} +0 -0
  147. /package/dist/{chunk-MT2JINSS.js.map → chunk-KJGKZLCT.js.map} +0 -0
  148. /package/dist/{chunk-C6CLRKPV.js.map → chunk-PR343K7F.js.map} +0 -0
  149. /package/dist/{chunk-CROKUHJB.js.map → chunk-Q47DU255.js.map} +0 -0
  150. /package/dist/{chunk-VSBFM7DS.js.map → chunk-SJOXWJ5Y.js.map} +0 -0
  151. /package/dist/{chunk-HEWSRXYV.js.map → chunk-ULHPALI5.js.map} +0 -0
  152. /package/dist/{chunk-XUGSZP7Z.js.map → chunk-W45FZEGE.js.map} +0 -0
  153. /package/dist/{chunk-JHGHLKFS.js.map → chunk-WS6NU475.js.map} +0 -0
  154. /package/dist/{chunk-ZGBEIWSC.js.map → chunk-XP5WM2LV.js.map} +0 -0
  155. /package/dist/{chunk-6TWWY7ZU.js.map → chunk-Z3JXLULD.js.map} +0 -0
  156. /package/dist/{client-AUE3XUPO.js.map → client-JYKPVCJX.js.map} +0 -0
  157. /package/dist/{convert-to-pnpm-QFJOEP5V.js.map → convert-to-pnpm-NWB5FCQ2.js.map} +0 -0
  158. /package/dist/{fleet-state-7TJ2GYT2.js.map → digest-state-F37XUCS5.js.map} +0 -0
  159. /package/dist/{ensure-site-4DCEBAPM.js.map → ensure-site-FU6527OU.js.map} +0 -0
  160. /package/dist/{migrate-GQH237A6.js.map → fleet-state-R5LU7IC3.js.map} +0 -0
  161. /package/dist/{forms-notify-target-2EAWCWBW.js.map → forms-notify-target-SGDGXXK5.js.map} +0 -0
  162. /package/dist/{header-image-6IC53W27.js.map → header-image-7A7XLVEU.js.map} +0 -0
  163. /package/dist/{health-endpoint-RFXL2F4A.js.map → health-endpoint-UVX6R5N5.js.map} +0 -0
  164. /package/dist/{init-XRAQZYID.js.map → init-FGTYNGUI.js.map} +0 -0
  165. /package/dist/{notify-C645PGD5.js.map → migrate-YITCXBLS.js.map} +0 -0
  166. /package/dist/{orchestrate-YLWQ6KK2.js.map → notify-4HP4GZ2H.js.map} +0 -0
  167. /package/dist/{onboard-O4IAHJMO.js.map → onboard-BKCYBAVW.js.map} +0 -0
  168. /package/dist/{parity-56KA2DGR.js.map → orchestrate-JNKCWU5K.js.map} +0 -0
  169. /package/dist/{pipeline-3AANZFCR.js.map → parity-NBYZUBFU.js.map} +0 -0
  170. /package/dist/{render-IFOGFB5T.js.map → pipeline-WYTXSLHX.js.map} +0 -0
  171. /package/dist/{preflight-TNORKGCX.js.map → preflight-IMTYDDD4.js.map} +0 -0
  172. /package/dist/{prismic-ci-G2AK53VL.js.map → prismic-ci-MY2U6NN7.js.map} +0 -0
  173. /package/dist/{prismic-models-O6Z3OK3A.js.map → prismic-models-NJN5JORF.js.map} +0 -0
  174. /package/dist/{render-from-row-BWSOIYDS.js.map → render-VX72AESY.js.map} +0 -0
  175. /package/dist/{spam-classifier-XLBFCO7G.js.map → render-from-row-DOYJDO2L.js.map} +0 -0
  176. /package/dist/{replay-C2LXMJZP.js.map → replay-4S6ENYCH.js.map} +0 -0
  177. /package/dist/{report-R3E3Y7HR.js.map → report-TALY5P3L.js.map} +0 -0
  178. /package/dist/{self-updating-UCIWESAX.js.map → self-updating-NTCCWJYZ.js.map} +0 -0
  179. /package/dist/{selftest-VGHWPN72.js.map → selftest-NZTR4SM2.js.map} +0 -0
  180. /package/dist/{smoke-suite-U3UKD3NS.js.map → smoke-suite-4JFZZYVW.js.map} +0 -0
  181. /package/dist/{write-audits-to-airtable-CMKHGJNQ.js.map → spam-classifier-T5H4VTP6.js.map} +0 -0
  182. /package/dist/{submissions-IQEJX6FK.js.map → submissions-TEHPBSZJ.js.map} +0 -0
  183. /package/dist/{svelte-codemods-GOKXTBHW.js.map → svelte-codemods-QAPDZC6F.js.map} +0 -0
  184. /package/dist/{sync-configs-SPTLFJCC.js.map → sync-configs-34JIA6KZ.js.map} +0 -0
  185. /package/dist/{upgrade-W6ZR3FJH.js.map → upgrade-IR2OO3KP.js.map} +0 -0
  186. /package/dist/{webhook-VOGOW3W6.js.map → webhook-XM6EV4XT.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/audit.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { Listr } from \"listr2\";\nimport { runOneAudit, ALL_AUDIT_NAMES } from \"../../audits/index.js\";\nimport type { AuditName, AuditResult, Site } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport {\n prepareFleetSites,\n formatSkippedNotice,\n type SkippedSite,\n} from \"../fleet/prepare-sites.js\";\nimport { isHttpUrl } from \"../../util/url.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport { fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\nimport type { AirtableBase } from \"../../reports/airtable/client.js\";\nimport type { HealthMirror } from \"../../audits/health-mirror.js\";\nimport type { FleetEvent } from \"../../db/fleet-events.js\";\n\nexport type AuditCommandOptions = {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n /**\n * After running, push the lighthouse scores to the matching Websites row\n * in Airtable. `true` (no value) = derive slug from cwd/package.json#name;\n * string = explicit slug (e.g. \"med-solutions-of-texas\").\n */\n writeAirtable?: string | boolean;\n /** Exit non-zero if any a11y violations are found (overrides warn). For CI gates. */\n failOnViolations?: boolean;\n /** Audit this deployed URL directly (lighthouse only; single-site). */\n url?: string;\n /** Max sites to audit in parallel (fleet mode). Unset = all at once;\n * `1` = sequential (used by the nightly CI workflow). */\n concurrency?: string;\n};\n\nfunction parseOnly(value: string | undefined): AuditName[] | undefined {\n if (!value) return undefined;\n const names = value.split(\",\").map((s) => s.trim());\n for (const n of names) {\n if (!ALL_AUDIT_NAMES.includes(n as AuditName)) {\n throw Object.assign(new Error(`unknown audit in --only: ${n}`), { exitCode: 2 });\n }\n }\n return names as AuditName[];\n}\n\nfunction formatTable(results: AuditResult[]): string {\n return results\n .map((r) => `${r.audit.padEnd(12)} ${r.status.padEnd(5)} ${r.site}\\n ${r.summary}`)\n .join(\"\\n\");\n}\n\nexport function auditExitCode(results: AuditResult[], failOnViolations: boolean): number {\n if (results.some((r) => r.status === \"fail\")) return 1;\n if (failOnViolations) {\n const a11yViolations = results\n .filter((r) => r.audit === \"a11y\")\n .reduce(\n (n, r) =>\n n + ((r.details as { totalViolations?: number } | undefined)?.totalViolations ?? 0),\n 0,\n );\n if (a11yViolations > 0) return 1;\n }\n return 0;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1_000) return `${ms}ms`;\n const totalSeconds = Math.round(ms / 1_000);\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}m${s.toString().padStart(2, \"0\")}s`;\n}\n\ntype Renderer = \"default\" | \"silent\";\n\n/** Parse the `--concurrency <n>` flag into a Listr `concurrent` value. Unset →\n * `true` (all sites in parallel, the interactive default). A positive integer\n * bounds how many sites audit at once — `--concurrency 1` runs sequentially,\n * which is what the nightly CI workflow uses so ~10 deployed-Lighthouse runs\n * don't saturate a 2-core runner (one flaked locally at full parallelism). */\nexport function parseConcurrency(value: string | undefined): boolean | number {\n if (value === undefined) return true;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 1) {\n throw Object.assign(new Error(`--concurrency must be a positive integer, got \"${value}\"`), {\n exitCode: 2,\n });\n }\n return n;\n}\n\n/** Build the audit-progress task list. Single-site → each audit is a sibling\n * task. Fleet → each site is a task whose `output` shows X/N audits done as\n * they complete (audits-per-site still run in parallel). Results are pushed\n * into the shared `results` array; tasks throw on `fail` status so listr2\n * paints them red, but `exitOnError: false` keeps other tasks running. */\nfunction buildAuditTasks(\n sites: Site[],\n which: AuditName[],\n results: AuditResult[],\n renderer: Renderer,\n concurrency: boolean | number,\n) {\n const singleSite = sites.length === 1;\n\n if (singleSite) {\n const site = sites[0]!;\n return new Listr(\n which.map((name) => ({\n title: name,\n task: async (_ctx, task) => {\n const start = Date.now();\n const result = await runOneAudit(site, name);\n results.push(result);\n const elapsed = formatDuration(Date.now() - start);\n task.title = `${name}: ${result.summary} (${elapsed})`;\n if (result.status === \"fail\") throw new Error(result.summary);\n },\n })),\n { concurrent: true, exitOnError: false, renderer },\n );\n }\n\n return new Listr(\n sites.map((site) => {\n const label = site.name || site.path; // `||`: empty slug must fall back to path, not blank\n return {\n title: label,\n task: async (_ctx, task) => {\n const start = Date.now();\n let done = 0;\n task.output = `0/${which.length} audits`;\n const settled = await Promise.all(\n which.map(async (name) => {\n const r = await runOneAudit(site, name);\n results.push(r);\n done += 1;\n task.output = `${done}/${which.length} audits`;\n return r;\n }),\n );\n const elapsed = formatDuration(Date.now() - start);\n const failed = settled.filter((r) => r.status === \"fail\").length;\n const warned = settled.filter((r) => r.status === \"warn\").length;\n const note =\n failed > 0\n ? `${failed} failed`\n : warned > 0\n ? `${warned} warning${warned === 1 ? \"\" : \"s\"}`\n : \"all green\";\n task.title = `${label}: ${note} (${elapsed})`;\n if (failed > 0) throw new Error(`${label}: ${failed} audit(s) failed`);\n },\n };\n }),\n { concurrent: concurrency, exitOnError: false, renderer },\n );\n}\n\ntype WriteSummary = Awaited<\n ReturnType<typeof import(\"../../audits/write-audits-to-airtable.js\").writeAuditsToAirtable>\n>;\n\nfunction formatWriteSummary(summary: WriteSummary): string {\n const lines = summary.writes.map((w) => {\n if (w.audit === \"lighthouse\") {\n const s = w.counts as {\n performance: number;\n accessibility: number;\n bestPractices: number;\n seo: number;\n };\n return ` lighthouse: P=${s.performance} A=${s.accessibility} BP=${s.bestPractices} SEO=${s.seo}`;\n }\n if (w.audit === \"a11y\") {\n return ` a11y: ${(w.counts as { violations: number }).violations} violations`;\n }\n if (w.audit === \"deps\") {\n const c = w.counts as { drifted: number; majorBehind: number };\n return ` deps: ${c.drifted} drifted (${c.majorBehind} major)`;\n }\n const c = w.counts as { critical: number; high: number; moderate: number; low: number };\n return ` security: ${c.critical}C/${c.high}H/${c.moderate}M/${c.low}L`;\n });\n return `→ wrote to Websites[${summary.siteName}]:\\n${lines.join(\"\\n\")}`;\n}\n\n/** Listr renderer choice. `--json` → silent so stdout stays clean for piping.\n * Otherwise listr's `default` renderer auto-falls back to `simple` in\n * non-TTY contexts (CI, log capture, our own integration tests). */\nfunction rendererFor(json: boolean | undefined): Renderer {\n return json ? \"silent\" : \"default\";\n}\n\n/** When `--url` is set but audits other than lighthouse also ran (those use the\n * local checkout, not the deployed URL), return a one-line operator notice;\n * null when there's nothing to warn about. Keeps the mixed-provenance result\n * table from silently confusing the operator. */\nexport function deployedUrlNotice(\n which: AuditName[],\n url: string | undefined,\n cwd: string,\n): string | null {\n if (url === undefined) return null;\n const others = which.filter((n) => n !== \"lighthouse\");\n if (others.length === 0) return null;\n return `note: --url only affects lighthouse; ${others.join(\", \")} ran against the local checkout at ${cwd}`;\n}\n\n/** Audits that need no repo checkout — they probe an external resource. lighthouse hits the\n * live URL; domain probes DNS/TLS of the URL's host; browser drives the deployed URL;\n * netlify-deploy queries the Netlify API by site id; function-health fetches the deployed\n * `/health` endpoint. */\nconst CHECKOUT_FREE_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\n \"lighthouse\",\n \"domain\",\n \"browser\",\n \"netlify-deploy\",\n \"function-health\",\n \"form-e2e\",\n]);\n\n/** Audits whose checkout-free run is keyed off `site.netlifyId` (the Netlify API) rather than\n * the deployed URL. Everything else in CHECKOUT_FREE_AUDITS reads `site.deployedUrl`. */\nconst NETLIFY_ID_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\"netlify-deploy\"]);\n\n/** A fleet site needs a local checkout unless every requested audit is checkout-free AND the site\n * has the external resource each one runs against (`deployedUrl` for lighthouse/domain/browser,\n * `netlifyId` for netlify-deploy). */\nexport function auditNeedsCheckout(site: Site, which: AuditName[]): boolean {\n const deployedCapable = which.every((n) => {\n if (!CHECKOUT_FREE_AUDITS.has(n)) return false;\n return NETLIFY_ID_AUDITS.has(n) ? site.netlifyId !== undefined : site.deployedUrl !== undefined;\n });\n return !deployedCapable;\n}\n\n/** Apply a single-site `--url` to the resolved sites. Returns the input\n * untouched when no url is given; otherwise requires exactly one site and\n * stamps `deployedUrl` on it so the lighthouse audit takes its deployed path.\n * The `--url`+`--fleet` combination is rejected earlier in `runAuditCommand`;\n * this length guard also covers any future multi-site single-run resolver. */\nexport function applyDeployedUrl(sites: Site[], url: string | undefined): Site[] {\n if (url === undefined) return sites;\n if (sites.length !== 1) {\n throw Object.assign(\n new Error(`--url expects exactly one site, but ${sites.length} resolved.`),\n { exitCode: 2 },\n );\n }\n // Scheme-allowlist: the URL is handed straight to Chrome/lhci, so only\n // http(s) is safe (a file:///gopher:// value would be a local-file read /\n // SSRF). Same predicate the inventory paths use.\n if (!isHttpUrl(url)) {\n throw Object.assign(new Error(`--url must be an http(s) URL (got: ${JSON.stringify(url)})`), {\n exitCode: 2,\n });\n }\n return [{ ...sites[0]!, deployedUrl: url }];\n}\n\nexport async function runAuditCommand(\n site: string | undefined,\n opts: AuditCommandOptions,\n): Promise<{ output: string; code: number }> {\n const which = parseOnly(opts.only) ?? ALL_AUDIT_NAMES;\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n // A literal --write-airtable=<slug> is single-site (the slug names one row).\n // Boolean --write-airtable + --fleet is fine: each site's slug comes from the\n // inventory, so there's no cwd-derived-slug ambiguity.\n if (typeof opts.writeAirtable === \"string\" && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--write-airtable=<slug> is single-site; with --fleet each site's slug comes from the inventory. Use --write-airtable (no slug) + --fleet.\",\n ),\n { exitCode: 2 },\n );\n }\n\n if (opts.url !== undefined && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--url is single-site only and cannot be combined with --fleet. Audit a single site instead.\",\n ),\n { exitCode: 2 },\n );\n }\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n sites = applyDeployedUrl(sites, opts.url);\n\n let skippedPrep: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, {\n workdir,\n needsCheckout: (s) => auditNeedsCheckout(s, which),\n });\n sites = prep.prepared;\n skippedPrep = prep.skipped;\n }\n\n const results: AuditResult[] = [];\n const renderer = rendererFor(opts.json);\n await buildAuditTasks(sites, which, results, renderer, parseConcurrency(opts.concurrency)).run();\n\n let output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);\n\n // Surface any site that couldn't be prepared (no auditable target, clone\n // failure) — visibly, but without reding the run. One misconfigured inventory\n // row is an operator fix, not an outage; the other sites still audited and\n // wrote back. \"No silent caps\": a dropped site is never invisible.\n const skipNotice = formatSkippedNotice(skippedPrep);\n if (skipNotice && !opts.json) output += `\\n\\n${skipNotice}`;\n\n // \"Did every site actually get measured?\" — emitted for any fleet sweep that ran\n // `smoke`, independent of --write-airtable, because it is a statement about the\n // RUN, not about persistence. Deliberately not folded into FLEET_WRITE_SUMMARY:\n // an unmeasured site still writes (it just writes nothing new), so the write gate\n // counts it as a success. That gap is exactly what hid four nights of timeouts.\n if (opts.fleet !== undefined && which.includes(\"smoke\") && !opts.json) {\n const { formatUnmeasuredSmokeSummary } = await import(\"../../audits/smoke.js\");\n output += `\\n\\n${formatUnmeasuredSmokeSummary(results)}`;\n }\n\n // Did any site fail to write back to Airtable? The fleet writer collects\n // per-site failures instead of throwing, so without this the command would\n // exit 0 while rows silently failed to persist — automation keying on `$?`\n // would see a clean run. (The single-site writer throws on failure, so it's\n // already non-zero via the propagated error.)\n let writeBackFailed = false;\n if (opts.writeAirtable !== undefined) {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n\n if (opts.fleet !== undefined) {\n const wb = await runFleetWriteBack({ results, which });\n if (wb.anyFailed) writeBackFailed = true;\n // Gate on !json: the write-summary is human text; appending it after the\n // results array would corrupt `--json` output (the other notices already\n // guard this way). The write itself still happens regardless of --json.\n if (!opts.json) output += `\\n\\n${wb.summary}`;\n } else {\n const { resolveSlugFromCwd } = await import(\"../../audits/lighthouse-airtable.js\");\n const { writeAuditsToAirtable } = await import(\"../../audits/write-audits-to-airtable.js\");\n const slug =\n typeof opts.writeAirtable === \"string\" && opts.writeAirtable.length > 0\n ? opts.writeAirtable\n : await resolveSlugFromCwd(cwd);\n let writeSummary: WriteSummary | null = null;\n await new Listr(\n [\n {\n title: `Write to Airtable[${slug}]`,\n task: async (_ctx, task) => {\n const base = openBase(readAirtableConfig());\n task.output = \"loading Websites…\";\n const websites = await listWebsites(base);\n task.output = \"writing scores…\";\n writeSummary = await writeAuditsToAirtable({ base, websites, slug, results });\n // #539 Phase 5: the FLEET path has mirrored since Phase 3, this\n // single-site one never did — same write, same columns, reaching\n // Turso only via the hourly sync. The summary already carries the\n // exact FieldSet Airtable got, so no signature change is needed.\n if (writeSummary.siteId && writeSummary.fields) {\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n await (await makeSiteMirror()).health(writeSummary.siteId, writeSummary.fields);\n }\n task.title = `Wrote to Websites[${writeSummary.siteName}] (${writeSummary.writes.length} audit type${writeSummary.writes.length === 1 ? \"\" : \"s\"})`;\n },\n },\n ],\n { renderer },\n ).run();\n if (writeSummary && !opts.json) output += `\\n\\n${formatWriteSummary(writeSummary)}`;\n }\n }\n\n const notice = deployedUrlNotice(which, opts.url, cwd);\n if (notice && !opts.json) output += `\\n\\n${notice}`;\n\n const code = Math.max(\n auditExitCode(results, opts.failOnViolations === true),\n writeBackFailed ? 1 : 0,\n );\n return { output, code };\n}\n\n/** The fleet `--write-airtable` step: write every site's audits back to its\n * Websites row, dual-write each just-written FieldSet into Turso (#539\n * Phase 3), and record fleet-activity events. Extracted from runAuditCommand\n * so the mirror WIRING itself is pinned by test — the mutation this seam\n * kills is dropping the `...(mirror ? { mirror } : {})` pass-through, which\n * would silently stop all five nightly sweeps from mirroring while every\n * sweep stayed green (adversarial review of #566, finding 6). Deps default\n * to the real fleet wiring; dynamic imports keep the no-write paths from\n * loading Airtable/db clients. */\nexport async function runFleetWriteBack(args: {\n results: AuditResult[];\n which: AuditName[];\n deps?: {\n openBase?: () => AirtableBase;\n makeMirror?: () => Promise<HealthMirror | null>;\n recordEvents?: (events: FleetEvent[], now: Date) => Promise<void>;\n };\n}): Promise<{ summary: string; anyFailed: boolean }> {\n const { results, which, deps = {} } = args;\n const { writeFleetAuditsToAirtable, formatFleetWriteSummary } =\n await import(\"../../audits/write-audits-to-airtable.js\");\n let base: AirtableBase;\n if (deps.openBase) {\n base = deps.openBase();\n } else {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n base = openBase(readAirtableConfig());\n }\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n const websites = await listWebsites(base);\n // Phase 3 dual-write (#539): mirror each site's written FieldSet into\n // site_health. Null when libSQL creds are absent — Airtable write-back\n // proceeds exactly as before.\n const makeMirror =\n deps.makeMirror ??\n (async () => {\n const { makeHealthMirrorBestEffort } = await import(\"../../audits/health-mirror.js\");\n return makeHealthMirrorBestEffort();\n });\n const mirror = await makeMirror();\n const fleetWrite = await writeFleetAuditsToAirtable({\n base,\n websites,\n results,\n ...(mirror ? { mirror } : {}),\n });\n // Record fleet-activity events (vuln_cleared / cert_renewed rode along on each\n // WriteSummary) plus a per-sweep rollup. Best-effort: a missing Turso cred no-ops.\n const sweep = which.includes(\"security\") ? \"security\" : \"lighthouse\";\n const now = new Date();\n const auditEvents = fleetWrite.written.flatMap((w) => w.events ?? []);\n await (deps.recordEvents ?? recordFleetEventsBestEffort)(\n [...auditEvents, fleetSweptEvent(sweep, fleetWrite.written.length, now.toISOString())],\n now,\n );\n return { summary: formatFleetWriteSummary(fleetWrite), anyFailed: fleetWrite.failed.length > 0 };\n}\n"],"mappings":"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","names":["c"]}
1
+ {"version":3,"sources":["../../../src/cli/commands/audit.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { Listr } from \"listr2\";\nimport { runOneAudit, ALL_AUDIT_NAMES } from \"../../audits/index.js\";\nimport type { AuditName, AuditResult, Site } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport {\n prepareFleetSites,\n formatSkippedNotice,\n type SkippedSite,\n} from \"../fleet/prepare-sites.js\";\nimport { isHttpUrl } from \"../../util/url.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport { fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\nimport type { AirtableBase } from \"../../reports/airtable/client.js\";\nimport type { HealthMirror } from \"../../audits/health-mirror.js\";\nimport type { FleetEvent } from \"../../db/fleet-events.js\";\n\nexport type AuditCommandOptions = {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n /**\n * After running, push the lighthouse scores to the matching Websites row\n * in Airtable. `true` (no value) = derive slug from cwd/package.json#name;\n * string = explicit slug (e.g. \"med-solutions-of-texas\").\n */\n writeAirtable?: string | boolean;\n /** Exit non-zero if any a11y violations are found (overrides warn). For CI gates. */\n failOnViolations?: boolean;\n /** Audit this deployed URL directly (lighthouse only; single-site). */\n url?: string;\n /** Max sites to audit in parallel (fleet mode). Unset = all at once;\n * `1` = sequential (used by the nightly CI workflow). */\n concurrency?: string;\n};\n\nfunction parseOnly(value: string | undefined): AuditName[] | undefined {\n if (!value) return undefined;\n const names = value.split(\",\").map((s) => s.trim());\n for (const n of names) {\n if (!ALL_AUDIT_NAMES.includes(n as AuditName)) {\n throw Object.assign(new Error(`unknown audit in --only: ${n}`), { exitCode: 2 });\n }\n }\n return names as AuditName[];\n}\n\nfunction formatTable(results: AuditResult[]): string {\n return results\n .map((r) => `${r.audit.padEnd(12)} ${r.status.padEnd(5)} ${r.site}\\n ${r.summary}`)\n .join(\"\\n\");\n}\n\nexport function auditExitCode(results: AuditResult[], failOnViolations: boolean): number {\n if (results.some((r) => r.status === \"fail\")) return 1;\n if (failOnViolations) {\n const a11yViolations = results\n .filter((r) => r.audit === \"a11y\")\n .reduce(\n (n, r) =>\n n + ((r.details as { totalViolations?: number } | undefined)?.totalViolations ?? 0),\n 0,\n );\n if (a11yViolations > 0) return 1;\n }\n return 0;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1_000) return `${ms}ms`;\n const totalSeconds = Math.round(ms / 1_000);\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}m${s.toString().padStart(2, \"0\")}s`;\n}\n\ntype Renderer = \"default\" | \"silent\";\n\n/** Parse the `--concurrency <n>` flag into a Listr `concurrent` value. Unset →\n * `true` (all sites in parallel, the interactive default). A positive integer\n * bounds how many sites audit at once — `--concurrency 1` runs sequentially,\n * which is what the nightly CI workflow uses so ~10 deployed-Lighthouse runs\n * don't saturate a 2-core runner (one flaked locally at full parallelism). */\nexport function parseConcurrency(value: string | undefined): boolean | number {\n if (value === undefined) return true;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 1) {\n throw Object.assign(new Error(`--concurrency must be a positive integer, got \"${value}\"`), {\n exitCode: 2,\n });\n }\n return n;\n}\n\n/** Build the audit-progress task list. Single-site → each audit is a sibling\n * task. Fleet → each site is a task whose `output` shows X/N audits done as\n * they complete (audits-per-site still run in parallel). Results are pushed\n * into the shared `results` array; tasks throw on `fail` status so listr2\n * paints them red, but `exitOnError: false` keeps other tasks running. */\nfunction buildAuditTasks(\n sites: Site[],\n which: AuditName[],\n results: AuditResult[],\n renderer: Renderer,\n concurrency: boolean | number,\n) {\n const singleSite = sites.length === 1;\n\n if (singleSite) {\n const site = sites[0]!;\n return new Listr(\n which.map((name) => ({\n title: name,\n task: async (_ctx, task) => {\n const start = Date.now();\n const result = await runOneAudit(site, name);\n results.push(result);\n const elapsed = formatDuration(Date.now() - start);\n task.title = `${name}: ${result.summary} (${elapsed})`;\n if (result.status === \"fail\") throw new Error(result.summary);\n },\n })),\n { concurrent: true, exitOnError: false, renderer },\n );\n }\n\n return new Listr(\n sites.map((site) => {\n const label = site.name || site.path; // `||`: empty slug must fall back to path, not blank\n return {\n title: label,\n task: async (_ctx, task) => {\n const start = Date.now();\n let done = 0;\n task.output = `0/${which.length} audits`;\n const settled = await Promise.all(\n which.map(async (name) => {\n const r = await runOneAudit(site, name);\n results.push(r);\n done += 1;\n task.output = `${done}/${which.length} audits`;\n return r;\n }),\n );\n const elapsed = formatDuration(Date.now() - start);\n const failed = settled.filter((r) => r.status === \"fail\").length;\n const warned = settled.filter((r) => r.status === \"warn\").length;\n const note =\n failed > 0\n ? `${failed} failed`\n : warned > 0\n ? `${warned} warning${warned === 1 ? \"\" : \"s\"}`\n : \"all green\";\n task.title = `${label}: ${note} (${elapsed})`;\n if (failed > 0) throw new Error(`${label}: ${failed} audit(s) failed`);\n },\n };\n }),\n { concurrent: concurrency, exitOnError: false, renderer },\n );\n}\n\ntype WriteSummary = Awaited<\n ReturnType<typeof import(\"../../audits/write-audits-to-airtable.js\").writeAuditsToAirtable>\n>;\n\nfunction formatWriteSummary(summary: WriteSummary): string {\n const lines = summary.writes.map((w) => {\n if (w.audit === \"lighthouse\") {\n const s = w.counts as {\n performance: number;\n accessibility: number;\n bestPractices: number;\n seo: number;\n };\n return ` lighthouse: P=${s.performance} A=${s.accessibility} BP=${s.bestPractices} SEO=${s.seo}`;\n }\n if (w.audit === \"a11y\") {\n return ` a11y: ${(w.counts as { violations: number }).violations} violations`;\n }\n if (w.audit === \"deps\") {\n const c = w.counts as { drifted: number; majorBehind: number };\n return ` deps: ${c.drifted} drifted (${c.majorBehind} major)`;\n }\n const c = w.counts as { critical: number; high: number; moderate: number; low: number };\n return ` security: ${c.critical}C/${c.high}H/${c.moderate}M/${c.low}L`;\n });\n return `→ wrote to Websites[${summary.siteName}]:\\n${lines.join(\"\\n\")}`;\n}\n\n/** Listr renderer choice. `--json` → silent so stdout stays clean for piping.\n * Otherwise listr's `default` renderer auto-falls back to `simple` in\n * non-TTY contexts (CI, log capture, our own integration tests). */\nfunction rendererFor(json: boolean | undefined): Renderer {\n return json ? \"silent\" : \"default\";\n}\n\n/** When `--url` is set but audits other than lighthouse also ran (those use the\n * local checkout, not the deployed URL), return a one-line operator notice;\n * null when there's nothing to warn about. Keeps the mixed-provenance result\n * table from silently confusing the operator. */\nexport function deployedUrlNotice(\n which: AuditName[],\n url: string | undefined,\n cwd: string,\n): string | null {\n if (url === undefined) return null;\n const others = which.filter((n) => n !== \"lighthouse\");\n if (others.length === 0) return null;\n return `note: --url only affects lighthouse; ${others.join(\", \")} ran against the local checkout at ${cwd}`;\n}\n\n/** Audits that need no repo checkout — they probe an external resource. lighthouse hits the\n * live URL; domain probes DNS/TLS of the URL's host; browser drives the deployed URL;\n * netlify-deploy queries the Netlify API by site id; function-health fetches the deployed\n * `/health` endpoint. */\nconst CHECKOUT_FREE_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\n \"lighthouse\",\n \"domain\",\n \"browser\",\n \"netlify-deploy\",\n \"function-health\",\n \"form-e2e\",\n]);\n\n/** Audits whose checkout-free run is keyed off `site.netlifyId` (the Netlify API) rather than\n * the deployed URL. Everything else in CHECKOUT_FREE_AUDITS reads `site.deployedUrl`. */\nconst NETLIFY_ID_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\"netlify-deploy\"]);\n\n/** A fleet site needs a local checkout unless every requested audit is checkout-free AND the site\n * has the external resource each one runs against (`deployedUrl` for lighthouse/domain/browser,\n * `netlifyId` for netlify-deploy). */\nexport function auditNeedsCheckout(site: Site, which: AuditName[]): boolean {\n const deployedCapable = which.every((n) => {\n if (!CHECKOUT_FREE_AUDITS.has(n)) return false;\n return NETLIFY_ID_AUDITS.has(n) ? site.netlifyId !== undefined : site.deployedUrl !== undefined;\n });\n return !deployedCapable;\n}\n\n/** Apply a single-site `--url` to the resolved sites. Returns the input\n * untouched when no url is given; otherwise requires exactly one site and\n * stamps `deployedUrl` on it so the lighthouse audit takes its deployed path.\n * The `--url`+`--fleet` combination is rejected earlier in `runAuditCommand`;\n * this length guard also covers any future multi-site single-run resolver. */\nexport function applyDeployedUrl(sites: Site[], url: string | undefined): Site[] {\n if (url === undefined) return sites;\n if (sites.length !== 1) {\n throw Object.assign(\n new Error(`--url expects exactly one site, but ${sites.length} resolved.`),\n { exitCode: 2 },\n );\n }\n // Scheme-allowlist: the URL is handed straight to Chrome/lhci, so only\n // http(s) is safe (a file:///gopher:// value would be a local-file read /\n // SSRF). Same predicate the inventory paths use.\n if (!isHttpUrl(url)) {\n throw Object.assign(new Error(`--url must be an http(s) URL (got: ${JSON.stringify(url)})`), {\n exitCode: 2,\n });\n }\n return [{ ...sites[0]!, deployedUrl: url }];\n}\n\nexport async function runAuditCommand(\n site: string | undefined,\n opts: AuditCommandOptions,\n): Promise<{ output: string; code: number }> {\n const which = parseOnly(opts.only) ?? ALL_AUDIT_NAMES;\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n // A literal --write-airtable=<slug> is single-site (the slug names one row).\n // Boolean --write-airtable + --fleet is fine: each site's slug comes from the\n // inventory, so there's no cwd-derived-slug ambiguity.\n if (typeof opts.writeAirtable === \"string\" && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--write-airtable=<slug> is single-site; with --fleet each site's slug comes from the inventory. Use --write-airtable (no slug) + --fleet.\",\n ),\n { exitCode: 2 },\n );\n }\n\n if (opts.url !== undefined && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--url is single-site only and cannot be combined with --fleet. Audit a single site instead.\",\n ),\n { exitCode: 2 },\n );\n }\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n sites = applyDeployedUrl(sites, opts.url);\n\n let skippedPrep: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, {\n workdir,\n needsCheckout: (s) => auditNeedsCheckout(s, which),\n });\n sites = prep.prepared;\n skippedPrep = prep.skipped;\n }\n\n const results: AuditResult[] = [];\n const renderer = rendererFor(opts.json);\n await buildAuditTasks(sites, which, results, renderer, parseConcurrency(opts.concurrency)).run();\n\n let output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);\n\n // Surface any site that couldn't be prepared (no auditable target, clone\n // failure) — visibly, but without reding the run. One misconfigured inventory\n // row is an operator fix, not an outage; the other sites still audited and\n // wrote back. \"No silent caps\": a dropped site is never invisible.\n const skipNotice = formatSkippedNotice(skippedPrep);\n if (skipNotice && !opts.json) output += `\\n\\n${skipNotice}`;\n\n // \"Did every site actually get measured?\" — emitted for any fleet sweep that ran\n // `smoke`, independent of --write-airtable, because it is a statement about the\n // RUN, not about persistence. Deliberately not folded into FLEET_WRITE_SUMMARY:\n // an unmeasured site still writes (it just writes nothing new), so the write gate\n // counts it as a success. That gap is exactly what hid four nights of timeouts.\n if (opts.fleet !== undefined && which.includes(\"smoke\") && !opts.json) {\n const { formatUnmeasuredSmokeSummary } = await import(\"../../audits/smoke.js\");\n output += `\\n\\n${formatUnmeasuredSmokeSummary(results)}`;\n }\n\n // Did any site fail to write back to Airtable? The fleet writer collects\n // per-site failures instead of throwing, so without this the command would\n // exit 0 while rows silently failed to persist — automation keying on `$?`\n // would see a clean run. (The single-site writer throws on failure, so it's\n // already non-zero via the propagated error.)\n let writeBackFailed = false;\n if (opts.writeAirtable !== undefined) {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n\n if (opts.fleet !== undefined) {\n const wb = await runFleetWriteBack({ results, which });\n if (wb.anyFailed) writeBackFailed = true;\n // Gate on !json: the write-summary is human text; appending it after the\n // results array would corrupt `--json` output (the other notices already\n // guard this way). The write itself still happens regardless of --json.\n if (!opts.json) output += `\\n\\n${wb.summary}`;\n } else {\n const { resolveSlugFromCwd } = await import(\"../../audits/lighthouse-airtable.js\");\n const { writeAuditsToAirtable } = await import(\"../../audits/write-audits-to-airtable.js\");\n const slug =\n typeof opts.writeAirtable === \"string\" && opts.writeAirtable.length > 0\n ? opts.writeAirtable\n : await resolveSlugFromCwd(cwd);\n let writeSummary: WriteSummary | null = null;\n await new Listr(\n [\n {\n title: `Write to Airtable[${slug}]`,\n task: async (_ctx, task) => {\n const base = openBase(readAirtableConfig());\n task.output = \"loading Websites…\";\n const websites = await listWebsites(base);\n task.output = \"writing scores…\";\n writeSummary = await writeAuditsToAirtable({ base, websites, slug, results });\n // #539 Phase 5: the FLEET path has mirrored since Phase 3, this\n // single-site one never did — same write, same columns, reaching\n // Turso only via the hourly sync. The summary already carries the\n // exact FieldSet Airtable got, so no signature change is needed.\n if (writeSummary.siteId && writeSummary.fields) {\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n await (await makeSiteMirror()).health(writeSummary.siteId, writeSummary.fields);\n }\n task.title = `Wrote to Websites[${writeSummary.siteName}] (${writeSummary.writes.length} audit type${writeSummary.writes.length === 1 ? \"\" : \"s\"})`;\n },\n },\n ],\n { renderer },\n ).run();\n if (writeSummary && !opts.json) output += `\\n\\n${formatWriteSummary(writeSummary)}`;\n }\n }\n\n const notice = deployedUrlNotice(which, opts.url, cwd);\n if (notice && !opts.json) output += `\\n\\n${notice}`;\n\n const code = Math.max(\n auditExitCode(results, opts.failOnViolations === true),\n writeBackFailed ? 1 : 0,\n );\n return { output, code };\n}\n\n/** The fleet `--write-airtable` step: write every site's audits back to its\n * Websites row, dual-write each just-written FieldSet into Turso (#539\n * Phase 3), and record fleet-activity events. Extracted from runAuditCommand\n * so the mirror WIRING itself is pinned by test — the mutation this seam\n * kills is dropping the `...(mirror ? { mirror } : {})` pass-through, which\n * would silently stop all five nightly sweeps from mirroring while every\n * sweep stayed green (adversarial review of #566, finding 6). Deps default\n * to the real fleet wiring; dynamic imports keep the no-write paths from\n * loading Airtable/db clients. */\nexport async function runFleetWriteBack(args: {\n results: AuditResult[];\n which: AuditName[];\n deps?: {\n openBase?: () => AirtableBase;\n makeMirror?: () => Promise<HealthMirror | null>;\n recordEvents?: (events: FleetEvent[], now: Date) => Promise<void>;\n /** #612. `true` = post-freeze, where a mirror failure, a missed row or an\n * absent mirror are each fatal. Injected so both sides stay proven and the\n * freeze commit stays a one-line change. */\n strict?: boolean;\n };\n}): Promise<{ summary: string; anyFailed: boolean }> {\n const { results, which, deps = {} } = args;\n const { writeFleetAuditsToAirtable, formatFleetWriteSummary, fleetWriteFailed } =\n await import(\"../../audits/write-audits-to-airtable.js\");\n let base: AirtableBase;\n if (deps.openBase) {\n base = deps.openBase();\n } else {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n base = openBase(readAirtableConfig());\n }\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n const websites = await listWebsites(base);\n // Phase 3 dual-write (#539): mirror each site's written FieldSet into\n // site_health. Null when libSQL creds are absent — Airtable write-back\n // proceeds exactly as before.\n const makeMirror =\n deps.makeMirror ??\n (async () => {\n const { makeHealthMirrorBestEffort } = await import(\"../../audits/health-mirror.js\");\n return makeHealthMirrorBestEffort();\n });\n const mirror = await makeMirror();\n const fleetWrite = await writeFleetAuditsToAirtable({\n base,\n websites,\n results,\n ...(mirror ? { mirror } : {}),\n });\n // Record fleet-activity events (vuln_cleared / cert_renewed rode along on each\n // WriteSummary) plus a per-sweep rollup. Best-effort: a missing Turso cred no-ops.\n const sweep = which.includes(\"security\") ? \"security\" : \"lighthouse\";\n const now = new Date();\n const auditEvents = fleetWrite.written.flatMap((w) => w.events ?? []);\n await (deps.recordEvents ?? recordFleetEventsBestEffort)(\n [...auditEvents, fleetSweptEvent(sweep, fleetWrite.written.length, now.toISOString())],\n now,\n );\n // #612: post-freeze a mirror failure, a missed row, or an absent mirror all\n // become fatal — there is no hourly import left to converge them, so a sweep\n // that wrote nothing into the only store would otherwise finish green.\n return {\n summary: formatFleetWriteSummary(fleetWrite),\n anyFailed:\n deps.strict === undefined\n ? fleetWriteFailed(fleetWrite)\n : fleetWriteFailed(fleetWrite, deps.strict),\n };\n}\n"],"mappings":"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","names":["c"]}
@@ -0,0 +1,11 @@
1
+ import {
2
+ openDb,
3
+ readDbConfig
4
+ } from "./chunk-WS6NU475.js";
5
+ import "./chunk-MFHS7IQ2.js";
6
+ import "./chunk-6VAL7XJT.js";
7
+ export {
8
+ openDb,
9
+ readDbConfig
10
+ };
11
+ //# sourceMappingURL=client-JYKPVCJX.js.map
@@ -2,7 +2,7 @@ import {
2
2
  a11yRoutes,
3
3
  playwright_a11y_default,
4
4
  smokeRoutes
5
- } from "../chunk-C6B4RHXR.js";
5
+ } from "../chunk-O3GT46R2.js";
6
6
  export {
7
7
  a11yRoutes,
8
8
  playwright_a11y_default as default,
@@ -8,14 +8,14 @@ import {
8
8
  } from "./chunk-TVL5MVUF.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-CROKUHJB.js";
11
+ } from "./chunk-Q47DU255.js";
12
12
  import {
13
13
  convertToPnpm
14
14
  } from "./chunk-XIDUYLSR.js";
15
15
  import "./chunk-3G25KIWW.js";
16
16
  import "./chunk-OTGB6YQO.js";
17
- import "./chunk-6TWWY7ZU.js";
18
- import "./chunk-EVBCK2KC.js";
17
+ import "./chunk-Z3JXLULD.js";
18
+ import "./chunk-RARREDQE.js";
19
19
  import "./chunk-XTK5VIQB.js";
20
20
  import "./chunk-LBYOLBW7.js";
21
21
  import "./chunk-XXTZBPUY.js";
@@ -51,4 +51,4 @@ async function runConvertToPnpmCommand(site, opts) {
51
51
  export {
52
52
  runConvertToPnpmCommand
53
53
  };
54
- //# sourceMappingURL=convert-to-pnpm-QFJOEP5V.js.map
54
+ //# sourceMappingURL=convert-to-pnpm-NWB5FCQ2.js.map
@@ -1,9 +1,9 @@
1
1
  // src/cli/commands/db.ts
2
2
  async function runDbCommand(action, opts) {
3
3
  if (action === "migrate") {
4
- const { readDbConfig } = await import("./client-AUE3XUPO.js");
4
+ const { readDbConfig } = await import("./client-JYKPVCJX.js");
5
5
  const cfg = opts.url ? { url: opts.url } : readDbConfig();
6
- const { runMigrations } = await import("./migrate-GQH237A6.js");
6
+ const { runMigrations } = await import("./migrate-YITCXBLS.js");
7
7
  const { createClient } = await import("@libsql/client");
8
8
  const client = createClient(cfg.url === ":memory:" ? { url: ":memory:" } : cfg);
9
9
  const ran = await runMigrations(client);
@@ -13,7 +13,7 @@ async function runDbCommand(action, opts) {
13
13
  };
14
14
  }
15
15
  if (action === "replay-deadletters") {
16
- const { readDbConfig, openDb } = await import("./client-AUE3XUPO.js");
16
+ const { readDbConfig, openDb } = await import("./client-JYKPVCJX.js");
17
17
  const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
18
18
  const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
19
19
  const { getWebsiteBySlug } = await import("./websites-D6WYFAES.js");
@@ -26,12 +26,12 @@ async function runDbCommand(action, opts) {
26
26
  listRecentSubmissionsForEmail,
27
27
  markSubmissionsSpamRetro
28
28
  } = await import("./submissions-3I3GAQAX.js");
29
- const { makeNotify } = await import("./notify-C645PGD5.js");
30
- const { classifySpam } = await import("./spam-classifier-XLBFCO7G.js");
31
- const { forwardNewsletterToWebhook } = await import("./webhook-VOGOW3W6.js");
29
+ const { makeNotify } = await import("./notify-4HP4GZ2H.js");
30
+ const { classifySpam } = await import("./spam-classifier-T5H4VTP6.js");
31
+ const { forwardNewsletterToWebhook } = await import("./webhook-XM6EV4XT.js");
32
32
  const { addMailchimpMember, mailchimpTagsFor } = await import("./mailchimp-XYCTEEJY.js");
33
33
  const { defaultResendClient } = await import("./resend-G2BJMAQV.js");
34
- const { replayDeadLetters } = await import("./replay-C2LXMJZP.js");
34
+ const { replayDeadLetters } = await import("./replay-4S6ENYCH.js");
35
35
  let send = null;
36
36
  try {
37
37
  send = defaultResendClient().send;
@@ -75,7 +75,7 @@ async function runDbCommand(action, opts) {
75
75
  return { output: lines.join("\n"), code: result.stillFailing.length > 0 ? 1 : 0 };
76
76
  }
77
77
  if (action === "import-airtable" || action === "parity" || action === "sync") {
78
- const { readDbConfig, openDb } = await import("./client-AUE3XUPO.js");
78
+ const { readDbConfig, openDb } = await import("./client-JYKPVCJX.js");
79
79
  const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
80
80
  const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
81
81
  const base = openBase(readAirtableConfig());
@@ -116,7 +116,7 @@ async function runDbCommand(action, opts) {
116
116
  return { output: lines.join("\n"), code: 0 };
117
117
  }
118
118
  if (action === "sync") {
119
- const { syncFleetState, formatSyncResult } = await import("./sync-CTUVXMCK.js");
119
+ const { syncFleetState, formatSyncResult } = await import("./sync-GBFV45WX.js");
120
120
  const result2 = await syncFleetState(db, {
121
121
  ...io,
122
122
  fetchAttachment: async (url) => {
@@ -133,12 +133,12 @@ async function runDbCommand(action, opts) {
133
133
  code: result2.parity.mismatches.length > 0 ? 1 : 0
134
134
  };
135
135
  }
136
- const { checkFleetParity, formatParityResult } = await import("./parity-56KA2DGR.js");
136
+ const { checkFleetParity, formatParityResult } = await import("./parity-NBYZUBFU.js");
137
137
  const result = await checkFleetParity(db, io);
138
138
  return { output: formatParityResult(result), code: result.mismatches.length > 0 ? 1 : 0 };
139
139
  }
140
140
  if (action === "backfill-header-images") {
141
- const { readDbConfig, openDb } = await import("./client-AUE3XUPO.js");
141
+ const { readDbConfig, openDb } = await import("./client-JYKPVCJX.js");
142
142
  const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
143
143
  const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
144
144
  const base = openBase(readAirtableConfig());
@@ -159,19 +159,94 @@ async function runDbCommand(action, opts) {
159
159
  });
160
160
  return { output: formatBackfillResult(result), code: result.failed.length > 0 ? 1 : 0 };
161
161
  }
162
+ if (action === "backfill-digest-state") {
163
+ const { readDbConfig, openDb } = await import("./client-JYKPVCJX.js");
164
+ const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
165
+ const { readDigestState: readTurso, writeDigestState: writeTurso } = await import("./digest-state-CRZRNGDI.js");
166
+ const existing = await readTurso(db);
167
+ if (Object.keys(existing).length > 0) {
168
+ return {
169
+ output: `DIGEST_BACKFILL skipped=1 reason=turso-already-populated keys=${Object.keys(existing).length}`,
170
+ code: 0
171
+ };
172
+ }
173
+ const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
174
+ const base = openBase(readAirtableConfig());
175
+ const { readDigestState: readAirtable, DIGEST_STATE_TABLE } = await import("./digest-state-F37XUCS5.js");
176
+ const rows = await base(DIGEST_STATE_TABLE).select({ maxRecords: 1, pageSize: 1 }).all();
177
+ const snap = await readAirtable(base);
178
+ const keys = Object.keys(snap).length;
179
+ await writeTurso(db, snap);
180
+ const stored = (await db.selectFrom("digest_state").selectAll().execute()).length;
181
+ const after = Object.keys(await readTurso(db)).length;
182
+ return {
183
+ output: `DIGEST_BACKFILL source=${rows.length > 0 ? "row" : "absent"} copied=${keys} verified=${after} rows=${stored}`,
184
+ code: after === keys && stored === 1 ? 0 : 1
185
+ };
186
+ }
162
187
  if (action === "dump") {
163
- const { readDbConfig } = await import("./client-AUE3XUPO.js");
188
+ const { readDbConfig } = await import("./client-JYKPVCJX.js");
164
189
  const cfg = opts.url ? { url: opts.url } : readDbConfig();
165
190
  const { createClient } = await import("@libsql/client");
166
191
  const client = createClient(cfg.url === ":memory:" ? { url: ":memory:" } : cfg);
167
- const { dumpDatabase } = await import("./dump-P6EBSIOH.js");
168
- const sql = await dumpDatabase({
192
+ const { dumpDatabase } = await import("./dump-EPL7AZFR.js");
193
+ const sql = await dumpDatabase(
194
+ {
195
+ execute: async (q) => {
196
+ const r = await client.execute(q);
197
+ return { columns: r.columns, rows: r.rows };
198
+ }
199
+ },
200
+ (/* @__PURE__ */ new Date()).toISOString()
201
+ );
202
+ return { output: sql, code: 0 };
203
+ }
204
+ if (action === "restore") {
205
+ const file = opts.file;
206
+ if (!file) return { output: "restore: pass the dump path via --file", code: 1 };
207
+ if (!opts.url) {
208
+ return {
209
+ output: "restore: pass the TARGET database via --url (never defaults, to keep production out of reach)",
210
+ code: 1
211
+ };
212
+ }
213
+ const { readFile } = await import("fs/promises");
214
+ const sql = await readFile(file, "utf-8");
215
+ const { parseDumpManifest } = await import("./dump-EPL7AZFR.js");
216
+ const manifest = parseDumpManifest(sql);
217
+ if (!manifest) return { output: "RESTORE refused=manifest-absent", code: 1 };
218
+ const { createClient } = await import("@libsql/client");
219
+ const target = createClient({ url: opts.url });
220
+ const existing = await target.execute(
221
+ "SELECT COUNT(*) AS n FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
222
+ );
223
+ if (Number(existing.rows[0]?.n ?? 0) > 0) {
224
+ return { output: "RESTORE refused=target-not-empty", code: 1 };
225
+ }
226
+ await target.executeMultiple(sql);
227
+ const { tableCounts, headerImageBytes } = await import("./dump-EPL7AZFR.js");
228
+ const exec = {
169
229
  execute: async (q) => {
170
- const r = await client.execute(q);
230
+ const r = await target.execute(q);
171
231
  return { columns: r.columns, rows: r.rows };
172
232
  }
173
- });
174
- return { output: sql, code: 0 };
233
+ };
234
+ const counts = await tableCounts(exec);
235
+ const bytes = await headerImageBytes(exec);
236
+ const bad = [];
237
+ for (const [t, want] of Object.entries(manifest.tables)) {
238
+ if ((counts[t] ?? 0) !== want) bad.push(`${t}: origin=${want} restored=${counts[t] ?? 0}`);
239
+ }
240
+ if (bytes !== manifest.blobBytes)
241
+ bad.push(`header_image bytes: origin=${manifest.blobBytes} restored=${bytes}`);
242
+ const rows = Object.values(counts).reduce((a, b) => a + b, 0);
243
+ return {
244
+ output: [
245
+ ...bad.map((b) => `\u2717 ${b}`),
246
+ `RESTORE loaded=true tables=${Object.keys(counts).length} rows=${rows} blob_bytes=${bytes} mismatches=${bad.length}`
247
+ ].join("\n"),
248
+ code: bad.length > 0 ? 1 : 0
249
+ };
175
250
  }
176
251
  if (action === "verify-dump") {
177
252
  const file = opts.file;
@@ -185,33 +260,53 @@ async function runDbCommand(action, opts) {
185
260
  } catch (err) {
186
261
  return { output: `DUMP_VERIFY loaded=false error=${String(err)}`, code: 1 };
187
262
  }
188
- const { tableCounts, countInsertsInDump } = await import("./dump-P6EBSIOH.js");
189
- const restored = await tableCounts({
263
+ const { tableCounts, headerImageBytes, parseDumpManifest } = await import("./dump-EPL7AZFR.js");
264
+ const manifest = parseDumpManifest(sql);
265
+ if (!manifest) {
266
+ return {
267
+ output: "DUMP_VERIFY loaded=true manifest=absent \u2014 dump predates the origin manifest",
268
+ code: 1
269
+ };
270
+ }
271
+ const exec = {
190
272
  execute: async (q) => {
191
273
  const r = await scratch.execute(q);
192
274
  return { columns: r.columns, rows: r.rows };
193
275
  }
194
- });
195
- const expected = countInsertsInDump(sql);
276
+ };
277
+ const restored = await tableCounts(exec);
278
+ const restoredBlobBytes = await headerImageBytes(exec);
196
279
  const mismatches = [];
197
- for (const [table, want] of Object.entries(expected)) {
280
+ for (const [table, want] of Object.entries(manifest.tables)) {
198
281
  if ((restored[table] ?? 0) !== want) {
199
- mismatches.push(`${table}: dump=${want} restored=${restored[table] ?? 0}`);
282
+ mismatches.push(`${table}: origin=${want} restored=${restored[table] ?? 0}`);
200
283
  }
201
284
  }
285
+ for (const table of Object.keys(restored)) {
286
+ if (!(table in manifest.tables)) mismatches.push(`${table}: not in origin manifest`);
287
+ }
288
+ const { DATABASE_TABLES } = await import("./schema-75PJECAE.js");
289
+ for (const table of DATABASE_TABLES) {
290
+ if (!(table in restored)) mismatches.push(`${table}: MISSING from the backup entirely`);
291
+ }
292
+ if (restoredBlobBytes !== manifest.blobBytes) {
293
+ mismatches.push(
294
+ `header_image bytes: origin=${manifest.blobBytes} restored=${restoredBlobBytes}`
295
+ );
296
+ }
202
297
  const total = Object.values(restored).reduce((a, b) => a + b, 0);
203
298
  const lines = [
204
299
  ...mismatches.map((m) => `\u2717 ${m}`),
205
- `DUMP_VERIFY loaded=true tables=${Object.keys(restored).length} rows=${total} mismatches=${mismatches.length}`
300
+ `DUMP_VERIFY loaded=true tables=${Object.keys(restored).length} rows=${total} blob_bytes=${restoredBlobBytes} mismatches=${mismatches.length}`
206
301
  ];
207
302
  return { output: lines.join("\n"), code: mismatches.length > 0 ? 1 : 0 };
208
303
  }
209
304
  return {
210
- output: `unknown db action '${action}'. Use: migrate, replay-deadletters, import-airtable, parity, sync, backfill-header-images, dump, verify-dump.`,
305
+ output: `unknown db action '${action}'. Use: migrate, replay-deadletters, import-airtable, parity, sync, backfill-header-images, backfill-digest-state, dump, verify-dump, restore.`,
211
306
  code: 1
212
307
  };
213
308
  }
214
309
  export {
215
310
  runDbCommand
216
311
  };
217
- //# sourceMappingURL=db-UXNIZ2RG.js.map
312
+ //# sourceMappingURL=db-V6UIS433.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/db.ts"],"sourcesContent":["export type DbCommandOptions = {\n /** Override the libSQL url (tests use \":memory:\"); otherwise read from env. */\n url?: string;\n /** verify-dump: path to the dump file to load into a scratch engine. */\n file?: string;\n cwd?: string;\n verbose?: boolean;\n};\n\n/** `db <action>` — migrate | replay-deadletters | import-airtable | parity | sync | dump | verify-dump. The db layer is imported\n * dynamically so a non-db CLI invocation (and `--help`) never loads\n * @libsql/client. Config is resolved inside each branch so an unknown action\n * returns without needing any Turso env. */\nexport async function runDbCommand(\n action: string,\n opts: DbCommandOptions,\n): Promise<{ output: string; code: number }> {\n if (action === \"migrate\") {\n const { readDbConfig } = await import(\"../../db/client.js\");\n const cfg = opts.url ? { url: opts.url } : readDbConfig();\n const { runMigrations } = await import(\"../../db/migrate.js\");\n const { createClient } = await import(\"@libsql/client\");\n const client = createClient(cfg.url === \":memory:\" ? { url: \":memory:\" } : cfg);\n const ran = await runMigrations(client);\n return {\n output: ran.length ? `Applied migrations: ${ran.join(\", \")}` : \"Already up to date.\",\n code: 0,\n };\n }\n\n // Re-run every lead that dead-lettered during a site-lookup outage (#539\n // Phase 0) through the normal ingest pipeline. Exit 1 while any row is STILL\n // failing — the store hasn't recovered and the operator should not read the\n // run as \"all leads landed\". Zero rows is a clean 0: nothing owed.\n if (action === \"replay-deadletters\") {\n const { readDbConfig, openDb } = await import(\"../../db/client.js\");\n const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const { getWebsiteBySlug } = await import(\"../../reports/airtable/websites.js\");\n const base = openBase(readAirtableConfig());\n const {\n createSubmission,\n stampNotified,\n stampFanout,\n findRecentDuplicateSubmissions,\n listRecentSubmissionsForEmail,\n markSubmissionsSpamRetro,\n } = await import(\"../../db/submissions.js\");\n const { makeNotify } = await import(\"../../forms/notify.js\");\n const { classifySpam } = await import(\"../../forms/spam-classifier.js\");\n const { forwardNewsletterToWebhook } = await import(\"../../forms/webhook.js\");\n const { addMailchimpMember, mailchimpTagsFor } = await import(\"../../forms/mailchimp.js\");\n const { defaultResendClient } = await import(\"../../reports/send/resend.js\");\n const { replayDeadLetters } = await import(\"../../forms/replay.js\");\n\n // Same degradation as the ingest handler: an unconfigured Resend key means\n // replayed leads land un-emailed (notify=failed) rather than blocking replay.\n let send = null;\n try {\n send = defaultResendClient().send;\n } catch (err) {\n console.error(`[db] Resend unconfigured; replaying without email: ${String(err)}`);\n }\n\n // Mirrors the production handler's deps minus `deadLetter` (replayDeadLetters\n // forbids and strips it — a throwing lookup must retry, not duplicate) and\n // minus `defer` (a CLI has no post-response phase; the inline tail is fine).\n const result = await replayDeadLetters(db, {\n getWebsiteBySlug: (s) => getWebsiteBySlug(base, s),\n createSubmission: (input) => createSubmission(db, input),\n notify: makeNotify(send),\n stampNotified: (id, status, messageId) => stampNotified(db, id, status, messageId),\n now: () => new Date(),\n classifySpam: (n, outcome) =>\n classifySpam({\n name: n.name,\n email: n.email,\n ...(n.message !== undefined ? { message: n.message } : {}),\n formType: n.formType,\n extraFields: n.extraFields,\n turnstile: outcome,\n }),\n findRecentDuplicates: (message, since) =>\n findRecentDuplicateSubmissions(db, message, since.toISOString()),\n listRecentSubmissionsForEmail: (email, since) =>\n listRecentSubmissionsForEmail(db, email, since.toISOString()),\n retroBucket: (ids, reason) => markSubmissionsSpamRetro(db, ids, reason),\n forwardNewsletter: (url, submission, site) =>\n forwardNewsletterToWebhook(url, submission, site),\n addToMailchimp: (site, submission) =>\n addMailchimpMember({\n apiKey: site.mailchimpApiKey ?? \"\",\n audienceId: site.mailchimpAudienceId ?? \"\",\n email: submission.email,\n name: submission.name,\n tags: mailchimpTagsFor(submission.formType),\n }),\n stampFanout: (id, fanoutStatus) => stampFanout(db, id, fanoutStatus),\n });\n\n const lines = [\n ...result.replayed.map(\n (r) => `replayed ${r.id} → ${r.outcome}${r.submissionId ? ` (${r.submissionId})` : \"\"}`,\n ),\n ...result.stillFailing.map((r) => `still failing ${r.id}: ${r.error}`),\n `DEADLETTER_REPLAY replayed=${result.replayed.length} still_failing=${result.stillFailing.length}`,\n ];\n return { output: lines.join(\"\\n\"), code: result.stillFailing.length > 0 ? 1 : 0 };\n }\n\n // Phase 1.3/1.4 of #539. Both read the same two Airtable tables raw (id +\n // fields, no mapRow coercion — the importer's mapping is the authority) and\n // share that mapping, so parity is definitionally checked against what the\n // importer writes.\n if (action === \"import-airtable\" || action === \"parity\" || action === \"sync\") {\n const { readDbConfig, openDb } = await import(\"../../db/client.js\");\n const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const base = openBase(readAirtableConfig());\n const listRaw = async (table: string) =>\n (await base(table).select().all()).map((r) => ({\n id: r.id,\n fields: r.fields as Record<string, unknown>,\n }));\n const io = {\n listWebsiteRecords: () => listRaw(\"Websites\"),\n listReportRecords: () => listRaw(\"Reports\"),\n now: () => new Date(),\n };\n\n if (action === \"import-airtable\") {\n const { importFleetState, formatReapSummary } = await import(\"../../db/import-airtable.js\");\n const summary = await importFleetState(db, {\n ...io,\n // Attachment bodies ride expiring signed URLs; a failed fetch imports the\n // row with rendered_html null and is NAMED in the summary, never silent.\n fetchAttachment: async (url) => {\n try {\n const res = await fetch(url);\n return res.ok ? await res.text() : null;\n } catch {\n return null;\n }\n },\n });\n const lines = [\n `imported ${summary.sites} site(s) → sites/site_health/site_schedule`,\n `imported ${summary.reports} report(s)`,\n ];\n if (summary.renderedHtmlMisses.length > 0) {\n lines.push(\n `⚠ ${summary.renderedHtmlMisses.length} report(s) imported WITHOUT Rendered HTML ` +\n `(fetch failed / URL expired): ${summary.renderedHtmlMisses.join(\", \")}`,\n );\n }\n // The import deletes rows Airtable no longer has, so this one-shot path\n // reports the reap exactly as `db sync` does — same formatter, no second\n // copy to fall out of step.\n lines.push(...formatReapSummary(summary.reaped));\n return { output: lines.join(\"\\n\"), code: 0 };\n }\n\n // Phase 2 backbone (#539): one hourly pass = import (attachment fetches\n // only where the stored row lacks a body) + parity + one retry to absorb\n // the import-read/parity-read race. Exit 1 on persistent mismatch.\n if (action === \"sync\") {\n const { syncFleetState, formatSyncResult } = await import(\"../../db/sync.js\");\n const result = await syncFleetState(db, {\n ...io,\n fetchAttachment: async (url) => {\n try {\n const res = await fetch(url);\n return res.ok ? await res.text() : null;\n } catch {\n return null;\n }\n },\n });\n return {\n output: formatSyncResult(result),\n code: result.parity.mismatches.length > 0 ? 1 : 0,\n };\n }\n\n const { checkFleetParity, formatParityResult } = await import(\"../../db/parity.js\");\n const result = await checkFleetParity(db, io);\n return { output: formatParityResult(result), code: result.mismatches.length > 0 ? 1 : 0 };\n }\n\n // One-shot completion of design D5 (#539 Phase 2): copy every site's CURRENT\n // Airtable \"Header image\" attachment into sites.header_image*. Idempotent —\n // an already-populated BLOB is never overwritten (a re-run must not clobber\n // a freshly generated image with a stale Airtable copy). Exit 1 when any\n // fetch failed, so a partial backfill is never read as complete.\n if (action === \"backfill-header-images\") {\n const { readDbConfig, openDb } = await import(\"../../db/client.js\");\n const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const base = openBase(readAirtableConfig());\n const { backfillHeaderImages, formatBackfillResult } =\n await import(\"../../db/header-images.js\");\n const result = await backfillHeaderImages(db, {\n listWebsiteRecords: async () =>\n (await base(\"Websites\").select().all()).map((r) => ({\n id: r.id,\n fields: r.fields as Record<string, unknown>,\n })),\n fetchBytes: async (url) => {\n try {\n const res = await fetch(url);\n return res.ok ? new Uint8Array(await res.arrayBuffer()) : null;\n } catch {\n return null;\n }\n },\n });\n return { output: formatBackfillResult(result), code: result.failed.length > 0 ? 1 : 0 };\n }\n\n // #609: one-shot copy of the single Airtable \"Digest State\" row into Turso,\n // so the first digest run after the read repoint sees yesterday's snapshot\n // instead of an empty one. An empty read is not a crash — it badges EVERY\n // item NEW, which lands in the operator's inbox reading as \"the whole fleet\n // degraded overnight\". REFUSES to overwrite a snapshot Turso already holds:\n // a re-run must never replace a fresher snapshot with a stale Airtable copy.\n if (action === \"backfill-digest-state\") {\n const { readDbConfig, openDb } = await import(\"../../db/client.js\");\n const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());\n const { readDigestState: readTurso, writeDigestState: writeTurso } =\n await import(\"../../db/digest-state.js\");\n const existing = await readTurso(db);\n if (Object.keys(existing).length > 0) {\n return {\n output: `DIGEST_BACKFILL skipped=1 reason=turso-already-populated keys=${Object.keys(existing).length}`,\n code: 0,\n };\n }\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const base = openBase(readAirtableConfig());\n const { readDigestState: readAirtable, DIGEST_STATE_TABLE } =\n await import(\"../../alerts/digest-state.js\");\n // `source` separates \"Airtable has no row\" from \"Airtable has a row holding\n // an empty snapshot\" — the reader collapses BOTH to {}, so copied=0 alone\n // cannot tell a quiet fleet from a failed read. Learned by running this: the\n // first real run printed copied=0 and only a hand probe showed the row was\n // there and genuinely empty.\n const rows = await base(DIGEST_STATE_TABLE).select({ maxRecords: 1, pageSize: 1 }).all();\n const snap = await readAirtable(base);\n const keys = Object.keys(snap).length;\n await writeTurso(db, snap);\n // Read it BACK. A returning write is not evidence the row landed — the same\n // rule forms-notify-target learned on 2026-08-03. `stored` counts the ROW,\n // not its keys, so an empty-but-present snapshot verifies as written.\n const stored = (await db.selectFrom(\"digest_state\").selectAll().execute()).length;\n const after = Object.keys(await readTurso(db)).length;\n return {\n output:\n `DIGEST_BACKFILL source=${rows.length > 0 ? \"row\" : \"absent\"} ` +\n `copied=${keys} verified=${after} rows=${stored}`,\n code: after === keys && stored === 1 ? 0 : 1,\n };\n }\n\n // Phase 1.5 of #539: platform-auth-free SQL dump to stdout-adjacent output.\n // The nightly backup workflow redirects this to a file, encrypts, uploads;\n // the rehearsed restore loads it into stock sqlite3 and compares row counts.\n if (action === \"dump\") {\n const { readDbConfig } = await import(\"../../db/client.js\");\n const cfg = opts.url ? { url: opts.url } : readDbConfig();\n const { createClient } = await import(\"@libsql/client\");\n const client = createClient(cfg.url === \":memory:\" ? { url: \":memory:\" } : cfg);\n const { dumpDatabase } = await import(\"../../db/dump.js\");\n const sql = await dumpDatabase(\n {\n execute: async (q) => {\n const r = await client.execute(q);\n return { columns: r.columns, rows: r.rows as Array<Record<string, unknown>> };\n },\n },\n new Date().toISOString(),\n );\n return { output: sql, code: 0 };\n }\n\n // Load a dump back into a REAL libSQL target (#612 review). The nightly\n // rehearsal loads into `:memory:`, which proves the SQL parses — it does not\n // prove you can get the data back into Turso, and that is the operation an\n // actual recovery needs. Replaying ~17 MB of SQL with megabytes of inline hex\n // over HTTP is materially different from an in-process load, and it had never\n // been done. Refuses to touch a database that already holds rows: a restore\n // is for an EMPTY target, and pointing this at production by mistake should\n // cost nothing.\n if (action === \"restore\") {\n const file = opts.file;\n if (!file) return { output: \"restore: pass the dump path via --file\", code: 1 };\n if (!opts.url) {\n return {\n output:\n \"restore: pass the TARGET database via --url (never defaults, to keep production out of reach)\",\n code: 1,\n };\n }\n const { readFile } = await import(\"node:fs/promises\");\n const sql = await readFile(file, \"utf-8\");\n const { parseDumpManifest } = await import(\"../../db/dump.js\");\n const manifest = parseDumpManifest(sql);\n if (!manifest) return { output: \"RESTORE refused=manifest-absent\", code: 1 };\n const { createClient } = await import(\"@libsql/client\");\n const target = createClient({ url: opts.url });\n const existing = await target.execute(\n \"SELECT COUNT(*) AS n FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\n );\n if (Number(existing.rows[0]?.n ?? 0) > 0) {\n return { output: \"RESTORE refused=target-not-empty\", code: 1 };\n }\n await target.executeMultiple(sql);\n const { tableCounts, headerImageBytes } = await import(\"../../db/dump.js\");\n const exec = {\n execute: async (q: string) => {\n const r = await target.execute(q);\n return { columns: r.columns, rows: r.rows as Array<Record<string, unknown>> };\n },\n };\n const counts = await tableCounts(exec);\n const bytes = await headerImageBytes(exec);\n // Same origin-anchored comparison as verify-dump: a restore that \"succeeded\"\n // with fewer rows than the origin held is not a restore.\n const bad: string[] = [];\n for (const [t, want] of Object.entries(manifest.tables)) {\n if ((counts[t] ?? 0) !== want) bad.push(`${t}: origin=${want} restored=${counts[t] ?? 0}`);\n }\n if (bytes !== manifest.blobBytes)\n bad.push(`header_image bytes: origin=${manifest.blobBytes} restored=${bytes}`);\n const rows = Object.values(counts).reduce((a, b) => a + b, 0);\n return {\n output: [\n ...bad.map((b) => `✗ ${b}`),\n `RESTORE loaded=true tables=${Object.keys(counts).length} rows=${rows} blob_bytes=${bytes} mismatches=${bad.length}`,\n ].join(\"\\n\"),\n code: bad.length > 0 ? 1 : 0,\n };\n }\n\n // The restore rehearsal (Phase 1.5's hard gate), runnable every night: load\n // the dump into a FRESH in-memory engine and compare what came back against\n // the ORIGIN MANIFEST the dump carries.\n //\n // It used to compare against INSERT counts parsed out of the dump text — i.e.\n // the dump against itself. Both sides derived from one artifact, so a dump\n // that collected 5 of 44 sites shrank both numbers together and verified\n // clean. The manifest is read from the live database before any row is\n // serialised, which is the only measurement that can notice a short dump.\n //\n // A dump that cannot restore is not a backup — and per the repo's instrument\n // rule the check emits its machine line on every run, clean included.\n if (action === \"verify-dump\") {\n const file = opts.file;\n if (!file) return { output: \"verify-dump: pass the dump path via --file\", code: 1 };\n const { readFile } = await import(\"node:fs/promises\");\n const sql = await readFile(file, \"utf-8\");\n const { createClient } = await import(\"@libsql/client\");\n const scratch = createClient({ url: \":memory:\" });\n try {\n await scratch.executeMultiple(sql);\n } catch (err) {\n return { output: `DUMP_VERIFY loaded=false error=${String(err)}`, code: 1 };\n }\n const { tableCounts, headerImageBytes, parseDumpManifest } = await import(\"../../db/dump.js\");\n const manifest = parseDumpManifest(sql);\n if (!manifest) {\n // Refuse rather than fall back to self-comparison. Falling back would\n // re-enable exactly the blind spot the manifest exists to close, and it\n // would do so silently on the one artifact nobody was watching.\n return {\n output: \"DUMP_VERIFY loaded=true manifest=absent — dump predates the origin manifest\",\n code: 1,\n };\n }\n const exec = {\n execute: async (q: string) => {\n const r = await scratch.execute(q);\n return { columns: r.columns, rows: r.rows as Array<Record<string, unknown>> };\n },\n };\n const restored = await tableCounts(exec);\n const restoredBlobBytes = await headerImageBytes(exec);\n const mismatches: string[] = [];\n for (const [table, want] of Object.entries(manifest.tables)) {\n if ((restored[table] ?? 0) !== want) {\n mismatches.push(`${table}: origin=${want} restored=${restored[table] ?? 0}`);\n }\n }\n // A table the origin held and the dump never mentioned would otherwise be\n // invisible: absent from `restored` AND absent from the loop above.\n for (const table of Object.keys(restored)) {\n if (!(table in manifest.tables)) mismatches.push(`${table}: not in origin manifest`);\n }\n // Coverage: every table the APP owns must be present. `tables=N` used to be\n // printed and never asserted, so a table a migration failed to create — or\n // that the dump lost — rode green forever. `digest_state` and\n // `prospect_audits` were both absent from every artifact the night this was\n // found, purely because they postdated the last run.\n const { DATABASE_TABLES } = await import(\"../../db/schema.js\");\n for (const table of DATABASE_TABLES) {\n if (!(table in restored)) mismatches.push(`${table}: MISSING from the backup entirely`);\n }\n if (restoredBlobBytes !== manifest.blobBytes) {\n mismatches.push(\n `header_image bytes: origin=${manifest.blobBytes} restored=${restoredBlobBytes}`,\n );\n }\n const total = Object.values(restored).reduce((a, b) => a + b, 0);\n const lines = [\n ...mismatches.map((m) => `✗ ${m}`),\n `DUMP_VERIFY loaded=true tables=${Object.keys(restored).length} rows=${total} blob_bytes=${restoredBlobBytes} mismatches=${mismatches.length}`,\n ];\n return { output: lines.join(\"\\n\"), code: mismatches.length > 0 ? 1 : 0 };\n }\n\n return {\n output: `unknown db action '${action}'. Use: migrate, replay-deadletters, import-airtable, parity, sync, backfill-header-images, backfill-digest-state, dump, verify-dump, restore.`,\n code: 1,\n };\n}\n"],"mappings":"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","names":["result"]}