@reddoorla/maintenance 0.85.2 → 0.87.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 (265) hide show
  1. package/README.md +8 -0
  2. package/dist/airtable-ZBUPQJSC.js +9 -0
  3. package/dist/{announce-GIAYN67S.js → announce-I2Z4JN3I.js} +58 -30
  4. package/dist/announce-I2Z4JN3I.js.map +1 -0
  5. package/dist/attachments-NLZIJ5NU.js +9 -0
  6. package/dist/{bump-deps-SWJB6H5V.js → bump-deps-CGVELRLK.js} +9 -9
  7. package/dist/{chunk-SDUJIANJ.js → chunk-2NJQLSEU.js} +3 -3
  8. package/dist/chunk-2NQUCS7O.js +243 -0
  9. package/dist/chunk-2NQUCS7O.js.map +1 -0
  10. package/dist/{chunk-ZTPPCHDB.js → chunk-3G25KIWW.js} +4 -4
  11. package/dist/{chunk-DTTIFYAM.js → chunk-3JQ2FO3D.js} +44 -405
  12. package/dist/chunk-3JQ2FO3D.js.map +1 -0
  13. package/dist/chunk-3SF7D4DY.js +359 -0
  14. package/dist/chunk-3SF7D4DY.js.map +1 -0
  15. package/dist/{chunk-37S6L6G4.js → chunk-5UINJNAJ.js} +72 -2
  16. package/dist/chunk-5UINJNAJ.js.map +1 -0
  17. package/dist/{chunk-THEFTZ7F.js → chunk-62CMMGEO.js} +5 -5
  18. package/dist/{chunk-FDIFTIXC.js → chunk-6TWWY7ZU.js} +2 -2
  19. package/dist/{chunk-PBWDDDQH.js → chunk-7BZUOZPU.js} +2 -2
  20. package/dist/{chunk-577HCGZO.js → chunk-7NYN2P6I.js} +44 -26
  21. package/dist/chunk-7NYN2P6I.js.map +1 -0
  22. package/dist/{chunk-UYCAF77E.js → chunk-7RJ2HMMJ.js} +3 -3
  23. package/dist/{chunk-Q276MZ7E.js → chunk-7UAYMAKR.js} +2 -2
  24. package/dist/{chunk-MGR2H7F4.js → chunk-BBUSGEES.js} +58 -45
  25. package/dist/chunk-BBUSGEES.js.map +1 -0
  26. package/dist/chunk-BNM5POSJ.js +284 -0
  27. package/dist/chunk-BNM5POSJ.js.map +1 -0
  28. package/dist/{chunk-R6FPFMOS.js → chunk-BQRFQSGY.js} +3 -3
  29. package/dist/{chunk-NWB4O5EY.js → chunk-C6CLRKPV.js} +5 -5
  30. package/dist/chunk-C6CLRKPV.js.map +1 -0
  31. package/dist/{chunk-7J53SUT6.js → chunk-CROKUHJB.js} +4 -4
  32. package/dist/chunk-DEWJMMG3.js +274 -0
  33. package/dist/chunk-DEWJMMG3.js.map +1 -0
  34. package/dist/{chunk-SEEA6RXF.js → chunk-DJZWYIPP.js} +2 -2
  35. package/dist/chunk-DJZWYIPP.js.map +1 -0
  36. package/dist/chunk-EVBCK2KC.js +68 -0
  37. package/dist/chunk-EVBCK2KC.js.map +1 -0
  38. package/dist/chunk-F3JWOPTG.js +202 -0
  39. package/dist/chunk-F3JWOPTG.js.map +1 -0
  40. package/dist/{chunk-GZNBCHAZ.js → chunk-HEWSRXYV.js} +4 -4
  41. package/dist/chunk-HHMMUR4P.js +335 -0
  42. package/dist/chunk-HHMMUR4P.js.map +1 -0
  43. package/dist/chunk-HSI6Q7MI.js +1238 -0
  44. package/dist/chunk-HSI6Q7MI.js.map +1 -0
  45. package/dist/{chunk-MPBXOJ3N.js → chunk-IY6256GK.js} +33 -2
  46. package/dist/chunk-IY6256GK.js.map +1 -0
  47. package/dist/{chunk-LS6QPANO.js → chunk-IYSEC543.js} +2 -2
  48. package/dist/{chunk-DC5H7KEQ.js → chunk-JHGHLKFS.js} +2 -2
  49. package/dist/{chunk-4DHQCN5H.js → chunk-LBYOLBW7.js} +17 -2
  50. package/dist/chunk-LBYOLBW7.js.map +1 -0
  51. package/dist/{chunk-6QRLRT3P.js → chunk-MT2JINSS.js} +16 -265
  52. package/dist/chunk-MT2JINSS.js.map +1 -0
  53. package/dist/{chunk-TP5M2NQB.js → chunk-NCMZMLY7.js} +3 -3
  54. package/dist/{chunk-COZPL62X.js → chunk-NDWYJLRF.js} +24 -139
  55. package/dist/chunk-NDWYJLRF.js.map +1 -0
  56. package/dist/{chunk-3YXDSCM6.js → chunk-NXNPDCF3.js} +29 -11
  57. package/dist/chunk-NXNPDCF3.js.map +1 -0
  58. package/dist/chunk-NYUYNYQL.js +48 -0
  59. package/dist/chunk-NYUYNYQL.js.map +1 -0
  60. package/dist/chunk-P6AORG32.js +415 -0
  61. package/dist/chunk-P6AORG32.js.map +1 -0
  62. package/dist/chunk-PTDMVGKL.js +31 -0
  63. package/dist/chunk-PTDMVGKL.js.map +1 -0
  64. package/dist/{chunk-D4FVAIEJ.js → chunk-PZR4EBPY.js} +5 -2
  65. package/dist/chunk-PZR4EBPY.js.map +1 -0
  66. package/dist/{chunk-LTB6KVJW.js → chunk-SDDPUAWH.js} +13 -11
  67. package/dist/{chunk-LTB6KVJW.js.map → chunk-SDDPUAWH.js.map} +1 -1
  68. package/dist/{chunk-WYYQK4D3.js → chunk-TD2NNXLZ.js} +13 -13
  69. package/dist/{chunk-ZMIPG6TR.js → chunk-TVL5MVUF.js} +4 -4
  70. package/dist/chunk-UDRHAQT7.js +172 -0
  71. package/dist/chunk-UDRHAQT7.js.map +1 -0
  72. package/dist/chunk-VAD43MOR.js +72 -0
  73. package/dist/chunk-VAD43MOR.js.map +1 -0
  74. package/dist/chunk-VSBFM7DS.js +229 -0
  75. package/dist/chunk-VSBFM7DS.js.map +1 -0
  76. package/dist/{chunk-VRWUL4TR.js → chunk-XHHMM5HX.js} +2 -2
  77. package/dist/{chunk-RJB3QUSP.js → chunk-XIDUYLSR.js} +3 -3
  78. package/dist/{chunk-QG6OSL4M.js → chunk-XUGSZP7Z.js} +2 -2
  79. package/dist/{chunk-S56JQJ4F.js → chunk-Y53WOQIJ.js} +3 -3
  80. package/dist/{chunk-62VF32IC.js → chunk-YFAXOD7Z.js} +10 -6
  81. package/dist/chunk-YFAXOD7Z.js.map +1 -0
  82. package/dist/{chunk-ICXV25N7.js → chunk-Z3KYFUN6.js} +3 -3
  83. package/dist/chunk-ZBDZTJUO.js +129 -0
  84. package/dist/chunk-ZBDZTJUO.js.map +1 -0
  85. package/dist/chunk-ZGBEIWSC.js +146 -0
  86. package/dist/chunk-ZGBEIWSC.js.map +1 -0
  87. package/dist/cli/bin.js +51 -28
  88. package/dist/cli/bin.js.map +1 -1
  89. package/dist/cli/commands/audit.d.ts +30 -1
  90. package/dist/cli/commands/audit.js +69 -33
  91. package/dist/cli/commands/audit.js.map +1 -1
  92. package/dist/client-AUE3XUPO.js +11 -0
  93. package/dist/{convert-to-pnpm-AT52NWZB.js → convert-to-pnpm-QFJOEP5V.js} +9 -9
  94. package/dist/db-UXNIZ2RG.js +217 -0
  95. package/dist/db-UXNIZ2RG.js.map +1 -0
  96. package/dist/{digest-UVHYZUYE.js → digest-FW6QMZZJ.js} +26 -22
  97. package/dist/{digest-UVHYZUYE.js.map → digest-FW6QMZZJ.js.map} +1 -1
  98. package/dist/dump-P6EBSIOH.js +68 -0
  99. package/dist/dump-P6EBSIOH.js.map +1 -0
  100. package/dist/email-KJXASVH5.js +195 -0
  101. package/dist/email-KJXASVH5.js.map +1 -0
  102. package/dist/{ensure-site-S25MDDHW.js → ensure-site-4DCEBAPM.js} +25 -16
  103. package/dist/ensure-site-4DCEBAPM.js.map +1 -0
  104. package/dist/fleet-events-cF2Om88w.d.ts +24 -0
  105. package/dist/fleet-state-7TJ2GYT2.js +39 -0
  106. package/dist/{forms-notify-target-TWN4EH6K.js → forms-notify-target-2EAWCWBW.js} +42 -133
  107. package/dist/forms-notify-target-2EAWCWBW.js.map +1 -0
  108. package/dist/{github-signals-G4ILZAJ6.js → github-signals-E2WPYTBK.js} +42 -21
  109. package/dist/github-signals-E2WPYTBK.js.map +1 -0
  110. package/dist/{header-image-T7XD37JL.js → header-image-6IC53W27.js} +36 -13
  111. package/dist/header-image-6IC53W27.js.map +1 -0
  112. package/dist/header-images-SQBCE5OD.js +88 -0
  113. package/dist/header-images-SQBCE5OD.js.map +1 -0
  114. package/dist/{health-endpoint-242WG2OJ.js → health-endpoint-RFXL2F4A.js} +9 -9
  115. package/dist/health-mirror-L5JBEJVA.js +41 -0
  116. package/dist/health-mirror-L5JBEJVA.js.map +1 -0
  117. package/dist/import-airtable-OHVY344G.js +37 -0
  118. package/dist/index.d.ts +324 -21
  119. package/dist/index.js +215 -76
  120. package/dist/index.js.map +1 -1
  121. package/dist/{init-JN26NTWF.js → init-XRAQZYID.js} +21 -18
  122. package/dist/{init-JN26NTWF.js.map → init-XRAQZYID.js.map} +1 -1
  123. package/dist/{launch-JA7Z4CIM.js → launch-IX4Y5JJC.js} +65 -30
  124. package/dist/launch-IX4Y5JJC.js.map +1 -0
  125. package/dist/lighthouse-VHI77PFB.js +11 -0
  126. package/dist/{lighthouse-airtable-BIPM26SZ.js → lighthouse-airtable-3GUWHETW.js} +3 -3
  127. package/dist/mailchimp-XYCTEEJY.js +72 -0
  128. package/dist/mailchimp-XYCTEEJY.js.map +1 -0
  129. package/dist/migrate-GQH237A6.js +7 -0
  130. package/dist/migrate-GQH237A6.js.map +1 -0
  131. package/dist/notify-C645PGD5.js +22 -0
  132. package/dist/notify-C645PGD5.js.map +1 -0
  133. package/dist/{onboard-53CITIM7.js → onboard-O4IAHJMO.js} +9 -9
  134. package/dist/orchestrate-YLWQ6KK2.js +31 -0
  135. package/dist/orchestrate-YLWQ6KK2.js.map +1 -0
  136. package/dist/parity-56KA2DGR.js +12 -0
  137. package/dist/parity-56KA2DGR.js.map +1 -0
  138. package/dist/pipeline-3AANZFCR.js +12 -0
  139. package/dist/pipeline-3AANZFCR.js.map +1 -0
  140. package/dist/{preflight-3XPDUHFJ.js → preflight-TNORKGCX.js} +17 -13
  141. package/dist/{preflight-3XPDUHFJ.js.map → preflight-TNORKGCX.js.map} +1 -1
  142. package/dist/{prismic-ci-UTQ6LRR3.js → prismic-ci-G2AK53VL.js} +12 -12
  143. package/dist/{prismic-models-PVES6R5V.js → prismic-models-O6Z3OK3A.js} +18 -13
  144. package/dist/prismic-models-O6Z3OK3A.js.map +1 -0
  145. package/dist/prospect/types.d.ts +267 -0
  146. package/dist/prospect/types.js +1 -0
  147. package/dist/prospect/types.js.map +1 -0
  148. package/dist/prospect-audit-KSXUNQ5J.js +174 -0
  149. package/dist/prospect-audit-KSXUNQ5J.js.map +1 -0
  150. package/dist/prospect-audits-3PMIO73D.js +19 -0
  151. package/dist/prospect-audits-3PMIO73D.js.map +1 -0
  152. package/dist/{protection-audit-64PLLBLI.js → protection-audit-SJO53MEQ.js} +3 -3
  153. package/dist/recipes/sync-configs.js +3 -3
  154. package/dist/render-IFOGFB5T.js +10 -0
  155. package/dist/render-IFOGFB5T.js.map +1 -0
  156. package/dist/render-from-row-BWSOIYDS.js +17 -0
  157. package/dist/render-from-row-BWSOIYDS.js.map +1 -0
  158. package/dist/{renovate-dispatch-ICJGSFCE.js → renovate-dispatch-6GQWTRKX.js} +12 -12
  159. package/dist/{renovate-dispatch-ICJGSFCE.js.map → renovate-dispatch-6GQWTRKX.js.map} +1 -1
  160. package/dist/replay-C2LXMJZP.js +66 -0
  161. package/dist/replay-C2LXMJZP.js.map +1 -0
  162. package/dist/{report-6CKSWJNG.js → report-R3E3Y7HR.js} +100 -32
  163. package/dist/report-R3E3Y7HR.js.map +1 -0
  164. package/dist/report-mirror-4MNORYQ4.js +49 -0
  165. package/dist/report-mirror-4MNORYQ4.js.map +1 -0
  166. package/dist/reports/header-image/assets/headline-announcement.png +0 -0
  167. package/dist/reports/header-image/assets/headline-launch.png +0 -0
  168. package/dist/reports/header-image/assets/headline-maintenance.png +0 -0
  169. package/dist/reports/header-image/assets/headline-testing.png +0 -0
  170. package/dist/reports/header-image/assets/plate-clean.png +0 -0
  171. package/dist/rerender-3FM6DNRH.js +32 -0
  172. package/dist/rerender-3FM6DNRH.js.map +1 -0
  173. package/dist/{self-updating-PPQVV6UA.js → self-updating-UCIWESAX.js} +9 -9
  174. package/dist/{selftest-CQ6NOX52.js → selftest-VGHWPN72.js} +31 -24
  175. package/dist/selftest-VGHWPN72.js.map +1 -0
  176. package/dist/site-mirror-NIREX5ME.js +52 -0
  177. package/dist/site-mirror-NIREX5ME.js.map +1 -0
  178. package/dist/smoke-B2GQOSIZ.js +20 -0
  179. package/dist/smoke-B2GQOSIZ.js.map +1 -0
  180. package/dist/{smoke-suite-TMCSOYRK.js → smoke-suite-U3UKD3NS.js} +9 -9
  181. package/dist/{submissions-GJ6ITBRH.js → submissions-IQEJX6FK.js} +3 -3
  182. package/dist/{svelte-codemods-ZOBDHIES.js → svelte-codemods-GOKXTBHW.js} +9 -9
  183. package/dist/sync-CTUVXMCK.js +44 -0
  184. package/dist/sync-CTUVXMCK.js.map +1 -0
  185. package/dist/{sync-configs-CJXUX6MD.js → sync-configs-SPTLFJCC.js} +9 -9
  186. package/dist/{upgrade-WLX27UPC.js → upgrade-W6ZR3FJH.js} +9 -9
  187. package/dist/webhook-VOGOW3W6.js +38 -0
  188. package/dist/webhook-VOGOW3W6.js.map +1 -0
  189. package/dist/{websites-SADZR4XQ.js → websites-D6WYFAES.js} +6 -2
  190. package/dist/websites-D6WYFAES.js.map +1 -0
  191. package/dist/{write-audits-to-airtable-7PH75YWY.js → write-audits-to-airtable-CMKHGJNQ.js} +4 -4
  192. package/dist/write-audits-to-airtable-CMKHGJNQ.js.map +1 -0
  193. package/package.json +9 -2
  194. package/dist/airtable-HRKSA7V4.js +0 -9
  195. package/dist/announce-GIAYN67S.js.map +0 -1
  196. package/dist/chunk-37S6L6G4.js.map +0 -1
  197. package/dist/chunk-3YXDSCM6.js.map +0 -1
  198. package/dist/chunk-4DHQCN5H.js.map +0 -1
  199. package/dist/chunk-4SOCPK7A.js +0 -108
  200. package/dist/chunk-4SOCPK7A.js.map +0 -1
  201. package/dist/chunk-577HCGZO.js.map +0 -1
  202. package/dist/chunk-62VF32IC.js.map +0 -1
  203. package/dist/chunk-6QRLRT3P.js.map +0 -1
  204. package/dist/chunk-COZPL62X.js.map +0 -1
  205. package/dist/chunk-D4FVAIEJ.js.map +0 -1
  206. package/dist/chunk-DTTIFYAM.js.map +0 -1
  207. package/dist/chunk-MGR2H7F4.js.map +0 -1
  208. package/dist/chunk-MPBXOJ3N.js.map +0 -1
  209. package/dist/chunk-NWB4O5EY.js.map +0 -1
  210. package/dist/chunk-SEEA6RXF.js.map +0 -1
  211. package/dist/chunk-U5SZ4FOP.js +0 -26
  212. package/dist/chunk-U5SZ4FOP.js.map +0 -1
  213. package/dist/client-X5WUJWDE.js +0 -11
  214. package/dist/db-JSIZYT23.js +0 -23
  215. package/dist/db-JSIZYT23.js.map +0 -1
  216. package/dist/ensure-site-S25MDDHW.js.map +0 -1
  217. package/dist/forms-notify-target-TWN4EH6K.js.map +0 -1
  218. package/dist/github-signals-G4ILZAJ6.js.map +0 -1
  219. package/dist/header-image-T7XD37JL.js.map +0 -1
  220. package/dist/launch-JA7Z4CIM.js.map +0 -1
  221. package/dist/migrate-LJUFGXQZ.js +0 -7
  222. package/dist/orchestrate-KDNXZQX7.js +0 -27
  223. package/dist/prismic-models-PVES6R5V.js.map +0 -1
  224. package/dist/report-6CKSWJNG.js.map +0 -1
  225. package/dist/reports/header-image/assets/plate.png +0 -0
  226. package/dist/selftest-CQ6NOX52.js.map +0 -1
  227. /package/dist/{airtable-HRKSA7V4.js.map → airtable-ZBUPQJSC.js.map} +0 -0
  228. /package/dist/{client-X5WUJWDE.js.map → attachments-NLZIJ5NU.js.map} +0 -0
  229. /package/dist/{bump-deps-SWJB6H5V.js.map → bump-deps-CGVELRLK.js.map} +0 -0
  230. /package/dist/{chunk-SDUJIANJ.js.map → chunk-2NJQLSEU.js.map} +0 -0
  231. /package/dist/{chunk-ZTPPCHDB.js.map → chunk-3G25KIWW.js.map} +0 -0
  232. /package/dist/{chunk-THEFTZ7F.js.map → chunk-62CMMGEO.js.map} +0 -0
  233. /package/dist/{chunk-FDIFTIXC.js.map → chunk-6TWWY7ZU.js.map} +0 -0
  234. /package/dist/{chunk-PBWDDDQH.js.map → chunk-7BZUOZPU.js.map} +0 -0
  235. /package/dist/{chunk-UYCAF77E.js.map → chunk-7RJ2HMMJ.js.map} +0 -0
  236. /package/dist/{chunk-Q276MZ7E.js.map → chunk-7UAYMAKR.js.map} +0 -0
  237. /package/dist/{chunk-R6FPFMOS.js.map → chunk-BQRFQSGY.js.map} +0 -0
  238. /package/dist/{chunk-7J53SUT6.js.map → chunk-CROKUHJB.js.map} +0 -0
  239. /package/dist/{chunk-GZNBCHAZ.js.map → chunk-HEWSRXYV.js.map} +0 -0
  240. /package/dist/{chunk-LS6QPANO.js.map → chunk-IYSEC543.js.map} +0 -0
  241. /package/dist/{chunk-DC5H7KEQ.js.map → chunk-JHGHLKFS.js.map} +0 -0
  242. /package/dist/{chunk-TP5M2NQB.js.map → chunk-NCMZMLY7.js.map} +0 -0
  243. /package/dist/{chunk-WYYQK4D3.js.map → chunk-TD2NNXLZ.js.map} +0 -0
  244. /package/dist/{chunk-ZMIPG6TR.js.map → chunk-TVL5MVUF.js.map} +0 -0
  245. /package/dist/{chunk-VRWUL4TR.js.map → chunk-XHHMM5HX.js.map} +0 -0
  246. /package/dist/{chunk-RJB3QUSP.js.map → chunk-XIDUYLSR.js.map} +0 -0
  247. /package/dist/{chunk-QG6OSL4M.js.map → chunk-XUGSZP7Z.js.map} +0 -0
  248. /package/dist/{chunk-S56JQJ4F.js.map → chunk-Y53WOQIJ.js.map} +0 -0
  249. /package/dist/{chunk-ICXV25N7.js.map → chunk-Z3KYFUN6.js.map} +0 -0
  250. /package/dist/{lighthouse-airtable-BIPM26SZ.js.map → client-AUE3XUPO.js.map} +0 -0
  251. /package/dist/{convert-to-pnpm-AT52NWZB.js.map → convert-to-pnpm-QFJOEP5V.js.map} +0 -0
  252. /package/dist/{migrate-LJUFGXQZ.js.map → fleet-state-7TJ2GYT2.js.map} +0 -0
  253. /package/dist/{health-endpoint-242WG2OJ.js.map → health-endpoint-RFXL2F4A.js.map} +0 -0
  254. /package/dist/{orchestrate-KDNXZQX7.js.map → import-airtable-OHVY344G.js.map} +0 -0
  255. /package/dist/{websites-SADZR4XQ.js.map → lighthouse-VHI77PFB.js.map} +0 -0
  256. /package/dist/{write-audits-to-airtable-7PH75YWY.js.map → lighthouse-airtable-3GUWHETW.js.map} +0 -0
  257. /package/dist/{onboard-53CITIM7.js.map → onboard-O4IAHJMO.js.map} +0 -0
  258. /package/dist/{prismic-ci-UTQ6LRR3.js.map → prismic-ci-G2AK53VL.js.map} +0 -0
  259. /package/dist/{protection-audit-64PLLBLI.js.map → protection-audit-SJO53MEQ.js.map} +0 -0
  260. /package/dist/{self-updating-PPQVV6UA.js.map → self-updating-UCIWESAX.js.map} +0 -0
  261. /package/dist/{smoke-suite-TMCSOYRK.js.map → smoke-suite-U3UKD3NS.js.map} +0 -0
  262. /package/dist/{submissions-GJ6ITBRH.js.map → submissions-IQEJX6FK.js.map} +0 -0
  263. /package/dist/{svelte-codemods-ZOBDHIES.js.map → svelte-codemods-GOKXTBHW.js.map} +0 -0
  264. /package/dist/{sync-configs-CJXUX6MD.js.map → sync-configs-SPTLFJCC.js.map} +0 -0
  265. /package/dist/{upgrade-WLX27UPC.js.map → upgrade-W6ZR3FJH.js.map} +0 -0
@@ -0,0 +1,146 @@
1
+ import {
2
+ defaultReportSubject
3
+ } from "./chunk-GECESMVS.js";
4
+ import {
5
+ announcementSiteExtras,
6
+ loadBundledImages,
7
+ renderReportHtml,
8
+ resolveCopy
9
+ } from "./chunk-MT2JINSS.js";
10
+ import {
11
+ applyReportTypeHeadline
12
+ } from "./chunk-5UINJNAJ.js";
13
+ import {
14
+ siteSlug
15
+ } from "./chunk-7NYN2P6I.js";
16
+
17
+ // src/reports/send/render-email.ts
18
+ function toInlineAttachment(a) {
19
+ return {
20
+ filename: a.filename,
21
+ content: Buffer.from(a.bytes).toString("base64"),
22
+ contentType: a.contentType,
23
+ inlineContentId: a.cid
24
+ };
25
+ }
26
+ async function renderReportEmail(reportData, ctx) {
27
+ const { html } = await renderReportHtml(reportData);
28
+ const bundled = await loadBundledImages();
29
+ const attachments = [
30
+ toInlineAttachment({
31
+ bytes: ctx.header.bytes,
32
+ filename: `${ctx.cidName}.jpg`,
33
+ contentType: ctx.header.contentType,
34
+ cid: ctx.cidName
35
+ })
36
+ ];
37
+ for (const img of [bundled.check, bundled.blurred]) {
38
+ if (html.includes(`cid:${img.cid}`)) {
39
+ attachments.push(
40
+ toInlineAttachment({
41
+ bytes: img.bytes,
42
+ filename: img.filename,
43
+ contentType: img.contentType,
44
+ cid: img.cid
45
+ })
46
+ );
47
+ }
48
+ }
49
+ const subject = ctx.subjectOverride ?? defaultReportSubject({
50
+ name: reportData.siteName,
51
+ url: reportData.siteUrl,
52
+ type: reportData.reportType,
53
+ date: reportData.completedOn
54
+ });
55
+ return { html, attachments, subject };
56
+ }
57
+
58
+ // src/reports/maintenance-email/header-image.ts
59
+ import sharp from "sharp";
60
+ var DEFAULT_DISPLAY_WIDTH = 600;
61
+ var RETINA_SCALE = 2;
62
+ var JPEG_QUALITY = 82;
63
+ function channelToHex(value) {
64
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
65
+ }
66
+ async function prepareHeaderImage(bytes, options = {}) {
67
+ const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;
68
+ const input = Buffer.from(bytes);
69
+ const meta = await sharp(input).metadata();
70
+ const origWidth = meta.width;
71
+ const origHeight = meta.height;
72
+ if (!origWidth || !origHeight) {
73
+ throw new Error("prepareHeaderImage: could not read source image dimensions");
74
+ }
75
+ const displayWidth = Math.min(requestedDisplayWidth, origWidth);
76
+ const displayHeight = Math.round(displayWidth * origHeight / origWidth);
77
+ const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);
78
+ const out = await sharp(input).resize({ width: targetSourceWidth, withoutEnlargement: true }).flatten({ background: "#ffffff" }).jpeg({ quality: JPEG_QUALITY }).toBuffer();
79
+ const { dominant } = await sharp(out).stats();
80
+ const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;
81
+ return {
82
+ bytes: new Uint8Array(out),
83
+ contentType: "image/jpeg",
84
+ displayWidth,
85
+ displayHeight,
86
+ placeholderColor
87
+ };
88
+ }
89
+
90
+ // src/reports/send/render-from-row.ts
91
+ function requireLighthouse(report) {
92
+ if (!report.lighthouse) {
93
+ throw new Error(
94
+ `Report ${report.reportId} has no Lighthouse scores \u2014 all four cells (Lighthouse \u2014 Performance / Accessibility / Best Practices / SEO) must be numeric on the Reports row; one non-numeric or blank cell nulls all four`
95
+ );
96
+ }
97
+ return report.lighthouse;
98
+ }
99
+ async function renderReportFromRow(site, report, headerPlateBytes) {
100
+ const withHeadline = await applyReportTypeHeadline(headerPlateBytes, report.reportType);
101
+ const header = await prepareHeaderImage(withHeadline);
102
+ const slug = siteSlug(site.name);
103
+ const cidName = `${slug}-header`;
104
+ const gaPeriodDays = report.reportType === "Announcement" ? 30 : windowDays(report.periodStart, report.periodEnd);
105
+ const reportData = {
106
+ siteName: site.name,
107
+ siteUrl: site.url,
108
+ reportType: report.reportType,
109
+ completedOn: report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date(),
110
+ lighthouse: requireLighthouse(report),
111
+ gaUsersCurrent: report.gaUsersCurrent ?? void 0,
112
+ gaUsersPrevious: report.gaUsersPrevious ?? void 0,
113
+ gaPeriodDays,
114
+ searchPosition: report.searchFoundPage1 && report.searchPosition !== null ? report.searchPosition : void 0,
115
+ lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,
116
+ commentary: report.commentary,
117
+ copy: resolveCopy(site),
118
+ headerImageCid: cidName,
119
+ headerWidth: header.displayWidth,
120
+ headerHeight: header.displayHeight,
121
+ headerBgColor: header.placeholderColor,
122
+ // Announcement-only: re-derive cadence + improvements from the site row so the SENT email
123
+ // keeps its cadence copy + improvement callouts (not stored on the Reports row).
124
+ ...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
125
+ };
126
+ return renderReportEmail(reportData, {
127
+ header,
128
+ cidName,
129
+ subjectOverride: report.subjectOverride ?? void 0
130
+ });
131
+ }
132
+ function windowDays(start, end) {
133
+ if (!start || !end) return void 0;
134
+ const ms = new Date(end).getTime() - new Date(start).getTime();
135
+ if (!Number.isFinite(ms) || ms <= 0) return void 0;
136
+ return Math.round(ms / (24 * 60 * 60 * 1e3));
137
+ }
138
+
139
+ export {
140
+ renderReportEmail,
141
+ prepareHeaderImage,
142
+ requireLighthouse,
143
+ renderReportFromRow,
144
+ windowDays
145
+ };
146
+ //# sourceMappingURL=chunk-ZGBEIWSC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reports/send/render-email.ts","../src/reports/maintenance-email/header-image.ts","../src/reports/send/render-from-row.ts"],"sourcesContent":["import { renderReportHtml } from \"../render.js\";\nimport { loadBundledImages } from \"../maintenance-email/assets/index.js\";\nimport { defaultReportSubject } from \"../subject.js\";\nimport type { ReportData } from \"../types.js\";\nimport type { ResendSendInput } from \"./resend.js\";\n\n/** A single Resend inline attachment (CID-referenced). */\nexport type InlineAttachment = NonNullable<ResendSendInput[\"attachments\"]>[number];\n\n/** The downscaled header image + display metadata produced by `prepareHeaderImage`. */\nexport type PreparedHeader = {\n bytes: Uint8Array;\n contentType: string;\n displayWidth: number;\n displayHeight: number;\n placeholderColor: string;\n};\n\nexport type RenderedReportEmail = {\n html: string;\n attachments: InlineAttachment[];\n subject: string;\n};\n\n/** Build a Resend inline (CID-referenced) attachment from raw bytes — the header image and both\n * bundled images share this exact shape. */\nfunction toInlineAttachment(a: {\n bytes: Uint8Array;\n filename: string;\n contentType: string;\n cid: string;\n}): InlineAttachment {\n return {\n filename: a.filename,\n content: Buffer.from(a.bytes).toString(\"base64\"),\n contentType: a.contentType,\n inlineContentId: a.cid,\n };\n}\n\n/**\n * Render a report email from fully-assembled `ReportData`: produce the HTML, the gated inline\n * attachments, and the subject. The per-site header attaches always; the two bundled images\n * (`rd-check-png`, `rd-blurred-tests-jpg`) attach only when their cid actually appears in the\n * rendered HTML (a dangling inline part shows as a stray download in some clients). The subject\n * is `subjectOverride` when given, else `defaultReportSubject`. Shared by the production send path\n * (`sendOne`) and the `selftest` command so the rendered email, attachments, and subject can't\n * drift between them. The only I/O is `loadBundledImages` (a disk read of two bundled images).\n */\nexport async function renderReportEmail(\n reportData: ReportData,\n ctx: { header: PreparedHeader; cidName: string; subjectOverride?: string | undefined },\n): Promise<RenderedReportEmail> {\n const { html } = await renderReportHtml(reportData);\n const bundled = await loadBundledImages();\n const attachments: InlineAttachment[] = [\n toInlineAttachment({\n bytes: ctx.header.bytes,\n filename: `${ctx.cidName}.jpg`,\n contentType: ctx.header.contentType,\n cid: ctx.cidName,\n }),\n ];\n for (const img of [bundled.check, bundled.blurred]) {\n if (html.includes(`cid:${img.cid}`)) {\n attachments.push(\n toInlineAttachment({\n bytes: img.bytes,\n filename: img.filename,\n contentType: img.contentType,\n cid: img.cid,\n }),\n );\n }\n }\n const subject =\n ctx.subjectOverride ??\n defaultReportSubject({\n name: reportData.siteName,\n url: reportData.siteUrl,\n type: reportData.reportType,\n date: reportData.completedOn,\n });\n return { html, attachments, subject };\n}\n","import sharp from \"sharp\";\n\nexport type PreparedHeaderImage = {\n /** Resized JPEG bytes to attach inline (CID) in place of the Airtable original. */\n bytes: Uint8Array;\n /** Always \"image/jpeg\" — we re-encode for predictable size and a flat white background. */\n contentType: string;\n /** CSS display width in px (≤ requested, never wider than the source has pixels for). */\n displayWidth: number;\n /** CSS display height in px, source aspect ratio preserved (no distortion). */\n displayHeight: number;\n /** Dominant-color hex (e.g. \"#cfc3a8\"), used as the loading/blocked placeholder box. */\n placeholderColor: string;\n};\n\nexport type PrepareHeaderImageOptions = {\n /** Intended CSS display width. The email body is 600px, so that's the default. */\n displayWidth?: number;\n};\n\nconst DEFAULT_DISPLAY_WIDTH = 600;\n/** Encode the source at 2× display width so it stays crisp on retina screens. */\nconst RETINA_SCALE = 2;\n/** Quality is for *resized* pixels — at 1200px the texture/text read as sharp; bytes are tiny. */\nconst JPEG_QUALITY = 82;\n\nfunction channelToHex(value: number): string {\n return Math.max(0, Math.min(255, Math.round(value)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/**\n * Downscale an oversized header image for email: 2× the display width (retina) at most,\n * never upscaled, re-encoded as JPEG on a flat white background. Also reports the display\n * dimensions (so the template can reserve the box and stop reflow) and a dominant color\n * (so the reserved box shows a matched placeholder while the image loads).\n *\n * Root cause this addresses: Airtable headers can be multi-MB / 2400px+ while the email\n * renders them at ~600px — shipping ~16× more pixels than the display can use.\n */\nexport async function prepareHeaderImage(\n bytes: Uint8Array,\n options: PrepareHeaderImageOptions = {},\n): Promise<PreparedHeaderImage> {\n const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;\n const input = Buffer.from(bytes);\n\n const meta = await sharp(input).metadata();\n const origWidth = meta.width;\n const origHeight = meta.height;\n if (!origWidth || !origHeight) {\n throw new Error(\"prepareHeaderImage: could not read source image dimensions\");\n }\n\n // Never claim a wider display than the source can fill at 1×.\n const displayWidth = Math.min(requestedDisplayWidth, origWidth);\n const displayHeight = Math.round((displayWidth * origHeight) / origWidth);\n\n // Encode at 2× display for retina, but never enlarge a smaller original.\n const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);\n\n const out = await sharp(input)\n .resize({ width: targetSourceWidth, withoutEnlargement: true })\n .flatten({ background: \"#ffffff\" })\n .jpeg({ quality: JPEG_QUALITY })\n .toBuffer();\n\n const { dominant } = await sharp(out).stats();\n const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;\n\n return {\n bytes: new Uint8Array(out),\n contentType: \"image/jpeg\",\n displayWidth,\n displayHeight,\n placeholderColor,\n };\n}\n","import { siteSlug } from \"../airtable/websites.js\";\nimport type { WebsiteRow } from \"../airtable/websites.js\";\nimport type { ReportRow } from \"../airtable/reports.js\";\nimport { resolveCopy } from \"../copy.js\";\nimport { announcementSiteExtras } from \"../announcement-email/template.js\";\nimport { renderReportEmail, type RenderedReportEmail } from \"./render-email.js\";\nimport type { ReportData } from \"../types.js\";\nimport { prepareHeaderImage } from \"../maintenance-email/header-image.js\";\nimport { applyReportTypeHeadline } from \"../header-image/index.js\";\n\n/**\n * The scores a render cannot proceed without, or a message naming the exact\n * Airtable cause. Exported so `sendOne` can fail fast on it BEFORE the header\n * fetch and the sharp downscale, while the renderer stays independently safe for\n * a caller that did not — one message, one rule, two call sites.\n */\nexport function requireLighthouse(report: ReportRow): NonNullable<ReportRow[\"lighthouse\"]> {\n if (!report.lighthouse) {\n throw new Error(\n `Report ${report.reportId} has no Lighthouse scores — all four cells ` +\n `(Lighthouse — Performance / Accessibility / Best Practices / SEO) must be numeric ` +\n `on the Reports row; one non-numeric or blank cell nulls all four`,\n );\n }\n return report.lighthouse;\n}\n\n/**\n * Assemble and render EXACTLY the email a send would produce, from a stored\n * report row plus the site's clean header plate.\n *\n * Lifted verbatim out of `sendOne`, which is the point: it is now the ONE path\n * from a row to rendered output, so the on-demand re-render behind the console's\n * report preview cannot drift from what the client actually receives. A preview\n * whose only job is fidelity is worthless if it renders through a second code\n * path that agrees with the sender by coincidence.\n *\n * Deliberately NOT included: recipient resolution, the health gate, and the\n * Resend call. Those are send concerns — a preview must be renderable for a\n * report that is not yet approvable, and must never be able to email anyone.\n *\n * `headerPlateBytes` is the CLEAN plate, not a finished header: this stamps the\n * report type's headline onto it and downscales the result, which is why the\n * caller can hand over Airtable's attachment bytes or Turso's stored BLOB\n * interchangeably.\n */\nexport async function renderReportFromRow(\n site: WebsiteRow,\n report: ReportRow,\n headerPlateBytes: Uint8Array,\n): Promise<RenderedReportEmail> {\n // The stored header is the CLEAN plate; stamp this report type's headline\n // onto it (all four types are registered — see assets/index.ts HEADLINE_FILES),\n // then downscale the (often multi-MB / 2400px+) result to email display\n // size, getting back display dims + a placeholder color so the template can\n // reserve the box.\n const withHeadline = await applyReportTypeHeadline(headerPlateBytes, report.reportType);\n const header = await prepareHeaderImage(withHeadline);\n\n const slug = siteSlug(site.name);\n const cidName = `${slug}-header`;\n const gaPeriodDays =\n report.reportType === \"Announcement\" ? 30 : windowDays(report.periodStart, report.periodEnd);\n const reportData: ReportData = {\n siteName: site.name,\n siteUrl: site.url,\n reportType: report.reportType,\n completedOn: report.completedOn ? new Date(report.completedOn) : new Date(),\n lighthouse: requireLighthouse(report),\n gaUsersCurrent: report.gaUsersCurrent ?? undefined,\n gaUsersPrevious: report.gaUsersPrevious ?? undefined,\n gaPeriodDays,\n searchPosition:\n report.searchFoundPage1 && report.searchPosition !== null ? report.searchPosition : undefined,\n lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,\n commentary: report.commentary,\n copy: resolveCopy(site),\n headerImageCid: cidName,\n headerWidth: header.displayWidth,\n headerHeight: header.displayHeight,\n headerBgColor: header.placeholderColor,\n // Announcement-only: re-derive cadence + improvements from the site row so the SENT email\n // keeps its cadence copy + improvement callouts (not stored on the Reports row).\n ...(report.reportType === \"Announcement\" ? announcementSiteExtras(site) : {}),\n };\n\n return renderReportEmail(reportData, {\n header,\n cidName,\n subjectOverride: report.subjectOverride ?? undefined,\n });\n}\n\n/** Whole days spanned by an ISO period window, or undefined when either bound is missing or the\n * span isn't positive. Drives the analytics trend's \"vs the previous N days\" label. */\nexport function windowDays(start: string | null, end: string | null): number | undefined {\n if (!start || !end) return undefined;\n const ms = new Date(end).getTime() - new Date(start).getTime();\n if (!Number.isFinite(ms) || ms <= 0) return undefined;\n return Math.round(ms / (24 * 60 * 60 * 1000));\n}\n"],"mappings":"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","names":[]}
package/dist/cli/bin.js CHANGED
@@ -111,7 +111,7 @@ cli.command("sync-configs [site]", "Sync canonical configs into a site.").option
111
111
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
112
112
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
113
113
  async (site, opts) => runOrExit(
114
- async () => (await import("../sync-configs-CJXUX6MD.js")).runSyncConfigsCommand(site, opts),
114
+ async () => (await import("../sync-configs-SPTLFJCC.js")).runSyncConfigsCommand(site, opts),
115
115
  opts
116
116
  )
117
117
  );
@@ -120,7 +120,7 @@ cli.command("bump-deps [site]", "Bump dependencies.").option("--group <group>",
120
120
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
121
121
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
122
122
  async (site, opts) => runOrExit(
123
- async () => (await import("../bump-deps-SWJB6H5V.js")).runBumpDepsCommand(site, opts),
123
+ async () => (await import("../bump-deps-CGVELRLK.js")).runBumpDepsCommand(site, opts),
124
124
  opts
125
125
  )
126
126
  );
@@ -129,7 +129,7 @@ cli.command(
129
129
  "Bootstrap a repo to keep itself updated (Renovate + protection; platform auto-merge off; ci.yml is the starter's, not managed here)."
130
130
  ).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(
131
131
  async (site, opts) => runOrExit(
132
- async () => (await import("../self-updating-PPQVV6UA.js")).runSelfUpdatingCommand(site, opts),
132
+ async () => (await import("../self-updating-UCIWESAX.js")).runSelfUpdatingCommand(site, opts),
133
133
  opts
134
134
  )
135
135
  );
@@ -138,7 +138,7 @@ cli.command(
138
138
  "Add the Prismic model delivery workflow to a site via PR (one PR per repo; never pushes to main)."
139
139
  ).option("--dry", "List the sites that would be offered the workflow, without opening any PR").option("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable"').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
140
140
  async (site, opts) => runOrExit(
141
- async () => (await import("../prismic-ci-UTQ6LRR3.js")).runPrismicCiCommand(site, opts),
141
+ async () => (await import("../prismic-ci-G2AK53VL.js")).runPrismicCiCommand(site, opts),
142
142
  opts
143
143
  )
144
144
  );
@@ -153,7 +153,7 @@ cli.command(
153
153
  "Write the report to this file (the CI workflow posts it as a PR comment)"
154
154
  ).action(
155
155
  async (site, opts) => runOrExit(
156
- async () => (await import("../prismic-models-PVES6R5V.js")).runPrismicModelsCommand(site, opts),
156
+ async () => (await import("../prismic-models-O6Z3OK3A.js")).runPrismicModelsCommand(site, opts),
157
157
  opts
158
158
  )
159
159
  );
@@ -162,7 +162,7 @@ cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to
162
162
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
163
163
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
164
164
  async (upgrade, site, opts) => runOrExit(
165
- async () => (await import("../upgrade-WLX27UPC.js")).runUpgradeCommand(upgrade, site, opts),
165
+ async () => (await import("../upgrade-W6ZR3FJH.js")).runUpgradeCommand(upgrade, site, opts),
166
166
  opts
167
167
  )
168
168
  );
@@ -174,7 +174,7 @@ cli.command(
174
174
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
175
175
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
176
176
  async (site, opts) => runOrExit(
177
- async () => (await import("../convert-to-pnpm-AT52NWZB.js")).runConvertToPnpmCommand(site, opts),
177
+ async () => (await import("../convert-to-pnpm-QFJOEP5V.js")).runConvertToPnpmCommand(site, opts),
178
178
  opts
179
179
  )
180
180
  );
@@ -183,7 +183,7 @@ cli.command("svelte-codemods [site]", "Apply Svelte 5 gotcha codemods to an alre
183
183
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
184
184
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
185
185
  async (site, opts) => runOrExit(
186
- async () => (await import("../svelte-codemods-ZOBDHIES.js")).runSvelteCodemodsCommand(site, opts),
186
+ async () => (await import("../svelte-codemods-GOKXTBHW.js")).runSvelteCodemodsCommand(site, opts),
187
187
  opts
188
188
  )
189
189
  );
@@ -195,7 +195,7 @@ cli.command(
195
195
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
196
196
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
197
197
  async (site, opts) => runOrExit(
198
- async () => (await import("../health-endpoint-242WG2OJ.js")).runHealthEndpointCommand(site, opts),
198
+ async () => (await import("../health-endpoint-RFXL2F4A.js")).runHealthEndpointCommand(site, opts),
199
199
  opts
200
200
  )
201
201
  );
@@ -207,7 +207,7 @@ cli.command(
207
207
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
208
208
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
209
209
  async (site, opts) => runOrExit(
210
- async () => (await import("../smoke-suite-TMCSOYRK.js")).runSmokeSuiteCommand(site, opts),
210
+ async () => (await import("../smoke-suite-U3UKD3NS.js")).runSmokeSuiteCommand(site, opts),
211
211
  opts
212
212
  )
213
213
  );
@@ -219,7 +219,7 @@ cli.command(
219
219
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
220
220
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
221
221
  async (site, opts) => runOrExit(
222
- async () => (await import("../onboard-53CITIM7.js")).runOnboardCommand(site, opts),
222
+ async () => (await import("../onboard-O4IAHJMO.js")).runOnboardCommand(site, opts),
223
223
  opts
224
224
  )
225
225
  );
@@ -230,20 +230,20 @@ cli.command(
230
230
  "--fleet <inventory>",
231
231
  'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
232
232
  ).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
233
- async (site, opts) => runOrExit(async () => (await import("../init-JN26NTWF.js")).runInitCommand(site, opts), opts)
233
+ async (site, opts) => runOrExit(async () => (await import("../init-XRAQZYID.js")).runInitCommand(site, opts), opts)
234
234
  );
235
235
  cli.command(
236
236
  "launch <site>",
237
237
  "Bootstrap + first-audit a site, then draft its launch email for approval."
238
238
  ).action(
239
239
  async (site, opts) => runOrExit(
240
- async () => (await import("../launch-JA7Z4CIM.js")).runLaunchCommand(site, opts),
240
+ async () => (await import("../launch-IX4Y5JJC.js")).runLaunchCommand(site, opts),
241
241
  opts
242
242
  )
243
243
  );
244
244
  cli.command("header-image [site]", "Generate a site's report header image from its live homepage.").option("--all", "Every live site with no Header image yet (backfill)").option("--force", "With --all, regenerate sites that already have one").option("--write-airtable", "Upload to the Websites row instead of writing a local file").option("--out-dir <path>", "Directory for local output (default: reports/)").option("--settle-ms <ms>", "Override the post-load settle delay for slow/animated homepages").action(
245
245
  async (site, opts) => runOrExit(
246
- async () => (await import("../header-image-T7XD37JL.js")).runHeaderImageCommand(site, opts),
246
+ async () => (await import("../header-image-6IC53W27.js")).runHeaderImageCommand(site, opts),
247
247
  opts
248
248
  )
249
249
  );
@@ -252,7 +252,7 @@ cli.command(
252
252
  "Draft the monthly-report announcement email for maintenance sites (all, or one) for approval."
253
253
  ).action(
254
254
  async (site, opts) => runOrExit(
255
- async () => (await import("../announce-GIAYN67S.js")).runAnnounceCommand(site, opts),
255
+ async () => (await import("../announce-I2Z4JN3I.js")).runAnnounceCommand(site, opts),
256
256
  opts
257
257
  )
258
258
  );
@@ -261,7 +261,7 @@ cli.command(
261
261
  "Show who a form submission would email, and optionally flip the pre-launch guard (read-back confirmed)."
262
262
  ).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(
263
263
  async (site, opts) => runOrExit(
264
- async () => (await import("../forms-notify-target-TWN4EH6K.js")).runFormsNotifyTargetCommand(
264
+ async () => (await import("../forms-notify-target-2EAWCWBW.js")).runFormsNotifyTargetCommand(
265
265
  site,
266
266
  opts
267
267
  ),
@@ -273,7 +273,7 @@ cli.command(
273
273
  "Operator self-tests. kind=email: preview a report email for a site (or --all) to yourself."
274
274
  ).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(
275
275
  async (kind, site, opts) => runOrExit(
276
- async () => (await import("../selftest-CQ6NOX52.js")).runSelftestCommand(kind, site, opts),
276
+ async () => (await import("../selftest-VGHWPN72.js")).runSelftestCommand(kind, site, opts),
277
277
  opts
278
278
  )
279
279
  );
@@ -285,13 +285,31 @@ cli.command(
285
285
  "Fleet mode: announcement\u2192maintenance sites; maintenance/testing\u2192all schedulable sites."
286
286
  ).action(
287
287
  async (site, opts) => runOrExit(
288
- async () => (await import("../preflight-3XPDUHFJ.js")).runPreflightCommand(site, opts),
288
+ async () => (await import("../preflight-TNORKGCX.js")).runPreflightCommand(site, opts),
289
289
  opts
290
290
  )
291
291
  );
292
292
  cli.command("ensure-site <slug>", "Create/verify the Airtable Websites row for a new site.").option("--name <display name>", "Human Name for the row (client-facing copy uses it verbatim).").option("--url <url>", "Deployed URL (e.g. the Netlify site URL).").option("--contact <email>", "point of contact \u2014 the client address reports resolve to.").option("--git-repo <owner/repo>", "GitHub identity. Default on create: reddoorla/<slug>.").action(
293
293
  async (slug, opts) => runOrExit(
294
- async () => (await import("../ensure-site-S25MDDHW.js")).runEnsureSiteCommand(slug, opts),
294
+ async () => (await import("../ensure-site-4DCEBAPM.js")).runEnsureSiteCommand(slug, opts),
295
+ opts
296
+ )
297
+ );
298
+ cli.command(
299
+ "prospect-audit <url>",
300
+ "AEO/SEO audit an external prospect's site and publish the report."
301
+ ).option(
302
+ "--business <name>",
303
+ "The prospect's business name (defaults to what the model reads off the site)."
304
+ ).option(
305
+ "--competitors <list>",
306
+ "Comma-separated competitor domains to add comparison probes for."
307
+ ).option("--no-probes", "Skip the live AI-visibility probes (tier 3).").option("--out <file>", "Also write the rendered report to this file.").option("--json", "Print the raw result JSON instead of the report summary.").option(
308
+ "--email",
309
+ "Email the internal sheet (scores, what wasn't measured, top fixes, the link) to PROSPECT_AUDIT_RECIPIENTS."
310
+ ).action(
311
+ async (url, opts) => runOrExit(
312
+ async () => (await import("../prospect-audit-KSXUNQ5J.js")).runProspectAuditCommand(url, opts),
295
313
  opts
296
314
  )
297
315
  );
@@ -307,9 +325,12 @@ cli.command("report [site]", "Draft or send maintenance/testing reports.").optio
307
325
  ).option(
308
326
  "--digest",
309
327
  "Email the operator one daily digest of reports ready for approval (skips when empty)."
328
+ ).option(
329
+ "--rerender <reportId>",
330
+ "Regenerate one UNSENT report's stored HTML from its current row, so the console preview shows edited commentary. Refuses a sent report."
310
331
  ).action(
311
332
  async (site, opts) => runOrExit(
312
- async () => (await import("../report-6CKSWJNG.js")).runReportCommand(site, opts),
333
+ async () => (await import("../report-R3E3Y7HR.js")).runReportCommand(site, opts),
313
334
  opts
314
335
  )
315
336
  );
@@ -318,7 +339,7 @@ cli.command(
318
339
  "Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write Airtable."
319
340
  ).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-airtable", "Write each site's signals back to its Websites row.").action(
320
341
  async (opts) => runOrExit(
321
- async () => (await import("../github-signals-G4ILZAJ6.js")).runGitHubSignalsCommand({
342
+ async () => (await import("../github-signals-E2WPYTBK.js")).runGitHubSignalsCommand({
322
343
  fleet: opts.fleet,
323
344
  writeAirtable: opts.writeAirtable
324
345
  }),
@@ -330,7 +351,7 @@ cli.command(
330
351
  "Verify every public org repo carries a sound branch ruleset (empty bypass, PR-required)."
331
352
  ).option("--org <org>", "GitHub org to sweep (enumerated from the API, never a hand list).").action(
332
353
  async (opts) => runOrExit(
333
- async () => (await import("../protection-audit-64PLLBLI.js")).runProtectionAuditCommand({
354
+ async () => (await import("../protection-audit-SJO53MEQ.js")).runProtectionAuditCommand({
334
355
  org: opts.org
335
356
  }),
336
357
  opts
@@ -338,9 +359,9 @@ cli.command(
338
359
  );
339
360
  cli.command(
340
361
  "db <action>",
341
- "Migrate / backfill / reconcile the libSQL store (migrate | backfill | reconcile)."
342
- ).action(
343
- async (action, opts) => runOrExit(async () => (await import("../db-JSIZYT23.js")).runDbCommand(action, opts), opts)
362
+ "Operate the libSQL store (migrate | replay-deadletters | import-airtable | parity | sync | backfill-header-images | dump | verify-dump)."
363
+ ).option("--file <path>", "verify-dump: the dump file to load into a scratch engine").action(
364
+ async (action, opts) => runOrExit(async () => (await import("../db-UXNIZ2RG.js")).runDbCommand(action, opts), opts)
344
365
  );
345
366
  cli.command(
346
367
  "submissions <action>",
@@ -350,7 +371,7 @@ cli.command(
350
371
  "Write the re-buckets: status \u2192 spam_auto, spam_score/spam_reason replaced with the new verdict + 'retro-rescore' marker."
351
372
  ).action(
352
373
  async (action, opts) => runOrExit(
353
- async () => (await import("../submissions-GJ6ITBRH.js")).runSubmissionsCommand(action, opts),
374
+ async () => (await import("../submissions-IQEJX6FK.js")).runSubmissionsCommand(action, opts),
354
375
  opts
355
376
  )
356
377
  );
@@ -359,8 +380,10 @@ cli.command(
359
380
  "Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns."
360
381
  ).option("--fleet", "Run across every active, repo-backed site in the Airtable inventory.").action(
361
382
  async (opts) => runOrExit(
362
- async () => (await import("../renovate-dispatch-ICJGSFCE.js")).runRenovateDispatchCommand({
363
- fleet: opts.fleet
383
+ async () => (await import("../renovate-dispatch-6GQWTRKX.js")).runRenovateDispatchCommand({
384
+ fleet: opts.fleet,
385
+ // #539 Phase 5: mirror the auto-fix counter into site_health.
386
+ siteMirror: await (await import("../site-mirror-NIREX5ME.js")).makeSiteMirror()
364
387
  }),
365
388
  opts
366
389
  )