@reddoorla/maintenance 0.96.0 → 0.97.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 (237) hide show
  1. package/README.md +5 -6
  2. package/dist/{announce-45HVP4J4.js → announce-5ZD2SBGD.js} +39 -31
  3. package/dist/announce-5ZD2SBGD.js.map +1 -0
  4. package/dist/attachments-JFGFQRXQ.js +10 -0
  5. package/dist/{blux-RUFMNOUJ.js → blux-NXQLMS4W.js} +5 -5
  6. package/dist/{bump-deps-EL3TKMIL.js → bump-deps-775VLDYB.js} +6 -6
  7. package/dist/{chunk-IMYL67JV.js → chunk-3RLDX3Q2.js} +5 -5
  8. package/dist/{chunk-CA6ADGOC.js → chunk-6PGXKN6F.js} +2 -2
  9. package/dist/{chunk-Y5BVR4HW.js → chunk-6THEZQLA.js} +3 -3
  10. package/dist/chunk-ASLDCWBF.js +41 -0
  11. package/dist/chunk-ASLDCWBF.js.map +1 -0
  12. package/dist/{chunk-DVJNYPFR.js → chunk-AYDQQBNG.js} +2 -2
  13. package/dist/{chunk-GEUC5VGM.js → chunk-BINFZQ2T.js} +49 -4
  14. package/dist/chunk-BINFZQ2T.js.map +1 -0
  15. package/dist/{chunk-72FJZ6LO.js → chunk-DRNZVORX.js} +26 -22
  16. package/dist/chunk-DRNZVORX.js.map +1 -0
  17. package/dist/{chunk-SHGIDTYF.js → chunk-DYTYPKFN.js} +2 -2
  18. package/dist/{chunk-NVLREB5R.js → chunk-EN6CTBUN.js} +7 -7
  19. package/dist/{chunk-ZH5LOQET.js → chunk-FHFXJ3EM.js} +7 -5
  20. package/dist/chunk-FHFXJ3EM.js.map +1 -0
  21. package/dist/{chunk-H445EH33.js → chunk-FP6HOW6J.js} +10 -7
  22. package/dist/chunk-FP6HOW6J.js.map +1 -0
  23. package/dist/{chunk-RRE74VWI.js → chunk-HSMN35LU.js} +2 -2
  24. package/dist/chunk-ICSRWW5Q.js +148 -0
  25. package/dist/chunk-ICSRWW5Q.js.map +1 -0
  26. package/dist/{chunk-XG2A6GIG.js → chunk-IDBPII6G.js} +4 -4
  27. package/dist/{chunk-XG2A6GIG.js.map → chunk-IDBPII6G.js.map} +1 -1
  28. package/dist/{chunk-T76MA2FH.js → chunk-IOTOOTII.js} +12 -1
  29. package/dist/{chunk-T76MA2FH.js.map → chunk-IOTOOTII.js.map} +1 -1
  30. package/dist/{chunk-ECDJDHKT.js → chunk-JHJNNJLG.js} +2 -2
  31. package/dist/{chunk-XY7YV2YQ.js → chunk-L3S3QZXL.js} +2 -2
  32. package/dist/{chunk-XED2DNAI.js → chunk-LAG6IP7Y.js} +7 -7
  33. package/dist/{chunk-P4A6D6HO.js → chunk-LDHRIQUW.js} +32 -18
  34. package/dist/chunk-LDHRIQUW.js.map +1 -0
  35. package/dist/{chunk-UIITN37J.js → chunk-LF2TJN4X.js} +2 -2
  36. package/dist/chunk-LF2TJN4X.js.map +1 -0
  37. package/dist/{chunk-SQ4FSNYL.js → chunk-LUF52EIY.js} +2 -2
  38. package/dist/{chunk-MZZDMBDT.js → chunk-LZMAG73U.js} +19 -19
  39. package/dist/chunk-LZMAG73U.js.map +1 -0
  40. package/dist/{chunk-6INOBNRO.js → chunk-MMSMZC7S.js} +2 -2
  41. package/dist/{chunk-CGQZFQAY.js → chunk-MVKNU4QD.js} +2 -3
  42. package/dist/chunk-MVKNU4QD.js.map +1 -0
  43. package/dist/{chunk-62YWOFVN.js → chunk-PARKXDXI.js} +3 -3
  44. package/dist/{chunk-62YWOFVN.js.map → chunk-PARKXDXI.js.map} +1 -1
  45. package/dist/{chunk-44SDGPGQ.js → chunk-PZZ6HM5T.js} +3 -3
  46. package/dist/chunk-RQJFGK3A.js +51 -0
  47. package/dist/chunk-RQJFGK3A.js.map +1 -0
  48. package/dist/{chunk-KN52PM63.js → chunk-TC4GANMX.js} +47 -149
  49. package/dist/chunk-TC4GANMX.js.map +1 -0
  50. package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
  51. package/dist/chunk-TEMQPSCM.js.map +1 -0
  52. package/dist/{chunk-4UTJ4G6P.js → chunk-TJLKWQOX.js} +2 -2
  53. package/dist/{chunk-VEIIUCYL.js → chunk-TV76A6GN.js} +2 -2
  54. package/dist/{chunk-D3CTOCUR.js → chunk-TVZ6QCOE.js} +3 -3
  55. package/dist/{chunk-3EA43S3R.js → chunk-U2ZI6G7H.js} +12 -12
  56. package/dist/chunk-UPE6D4AT.js +69 -0
  57. package/dist/chunk-UPE6D4AT.js.map +1 -0
  58. package/dist/{chunk-OTEVKRAH.js → chunk-VMGHQUTV.js} +7 -16
  59. package/dist/chunk-VMGHQUTV.js.map +1 -0
  60. package/dist/chunk-VQMAAQ5M.js +53 -0
  61. package/dist/chunk-VQMAAQ5M.js.map +1 -0
  62. package/dist/{chunk-EASYCD6F.js → chunk-WMF5S2MB.js} +1 -1
  63. package/dist/{chunk-EASYCD6F.js.map → chunk-WMF5S2MB.js.map} +1 -1
  64. package/dist/{chunk-37B4LVB3.js → chunk-X56LAKPR.js} +3 -3
  65. package/dist/chunk-X56LAKPR.js.map +1 -0
  66. package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
  67. package/dist/chunk-YJBSB4LP.js.map +1 -0
  68. package/dist/{chunk-EQQ4H6L3.js → chunk-YXWBM26P.js} +2 -2
  69. package/dist/cli/bin.js +92 -50
  70. package/dist/cli/bin.js.map +1 -1
  71. package/dist/cli/commands/audit.d.ts +33 -3
  72. package/dist/cli/commands/audit.js +25 -22
  73. package/dist/cli/commands/audit.js.map +1 -1
  74. package/dist/client-DGTO65WB.js +11 -0
  75. package/dist/{convert-to-pnpm-4DON7IFS.js → convert-to-pnpm-KLWA53N5.js} +6 -6
  76. package/dist/{db-5K4CDEGJ.js → db-II5WAK4D.js} +13 -13
  77. package/dist/{digest-5AJGWS33.js → digest-CWKK2DMR.js} +28 -27
  78. package/dist/digest-CWKK2DMR.js.map +1 -0
  79. package/dist/{digest-collectors-MC5NWEUA.js → digest-collectors-CYTN2EUX.js} +15 -12
  80. package/dist/ensure-site-2QZHFVQ3.js +70 -0
  81. package/dist/ensure-site-2QZHFVQ3.js.map +1 -0
  82. package/dist/ensure-site-JEKV2ODE.js +105 -0
  83. package/dist/ensure-site-JEKV2ODE.js.map +1 -0
  84. package/dist/{ensure-site-XESZHZEB.js → ensure-site-OO4UPWNQ.js} +19 -42
  85. package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
  86. package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
  87. package/dist/{fleet-state-GJWGKTLD.js → fleet-state-RJROYXKO.js} +17 -7
  88. package/dist/{forms-notify-target-4NPLX2UG.js → forms-notify-target-X6UEOI2M.js} +17 -13
  89. package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
  90. package/dist/{github-signals-DMU74SAX.js → github-signals-QIRTHHET.js} +20 -15
  91. package/dist/github-signals-QIRTHHET.js.map +1 -0
  92. package/dist/{header-image-BRTOLHJF.js → header-image-H7ZJAV26.js} +13 -17
  93. package/dist/header-image-H7ZJAV26.js.map +1 -0
  94. package/dist/{health-endpoint-UCLMSSCN.js → health-endpoint-BJ6BKRHF.js} +7 -7
  95. package/dist/{health-mirror-ONRT4DQ5.js → health-mirror-KU3G7V2Y.js} +10 -10
  96. package/dist/{import-airtable-IIK2QUUF.js → import-airtable-UBF5GYAL.js} +3 -3
  97. package/dist/index.d.ts +102 -494
  98. package/dist/index.js +60 -49
  99. package/dist/index.js.map +1 -1
  100. package/dist/{init-3LMZTCVD.js → init-GOWDA5QS.js} +15 -15
  101. package/dist/{launch-XTDMC6LD.js → launch-7N3X2MXB.js} +36 -34
  102. package/dist/launch-7N3X2MXB.js.map +1 -0
  103. package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
  104. package/dist/{chunk-IHHAYXKY.js → match-harness-GBCDSVPS.js} +70 -4
  105. package/dist/match-harness-GBCDSVPS.js.map +1 -0
  106. package/dist/migrate-XZ7U5DFC.js +7 -0
  107. package/dist/notify-K3WVH3DM.js +1 -1
  108. package/dist/{onboard-CZYBWVWH.js → onboard-JQQIMQW7.js} +6 -6
  109. package/dist/{orchestrate-TGDFGY52.js → orchestrate-L2A5BZFY.js} +13 -10
  110. package/dist/parity-2JGVUCZD.js +14 -0
  111. package/dist/{preflight-POZZI3GN.js → preflight-O7Q6FWHU.js} +35 -11
  112. package/dist/preflight-O7Q6FWHU.js.map +1 -0
  113. package/dist/{prismic-ci-AHY5ULJR.js → prismic-ci-NAPWOXXD.js} +11 -11
  114. package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
  115. package/dist/{prismic-models-URYGODNY.js → prismic-models-LL6ORTQ2.js} +19 -16
  116. package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
  117. package/dist/{prospect-audit-AJEE3Z4L.js → prospect-audit-FMPGQIRQ.js} +2 -2
  118. package/dist/{protection-audit-3J3UEOVT.js → protection-audit-YXJKFU6O.js} +3 -3
  119. package/dist/{protection-audit-3J3UEOVT.js.map → protection-audit-YXJKFU6O.js.map} +1 -1
  120. package/dist/recipes/sync-configs.js +3 -3
  121. package/dist/{render-from-row-65DC6ZP3.js → render-from-row-75JTED2Z.js} +6 -4
  122. package/dist/{renovate-dispatch-ONJMBUHJ.js → renovate-dispatch-2IHAZDVA.js} +13 -8
  123. package/dist/{renovate-dispatch-ONJMBUHJ.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
  124. package/dist/{report-RC5LGPU3.js → report-IAHXN5CW.js} +102 -58
  125. package/dist/report-IAHXN5CW.js.map +1 -0
  126. package/dist/{report-mirror-KKZZ6RMZ.js → report-mirror-4QM57LMS.js} +28 -10
  127. package/dist/report-mirror-4QM57LMS.js.map +1 -0
  128. package/dist/roster-GDNSIRAK.js +19 -0
  129. package/dist/roster-GDNSIRAK.js.map +1 -0
  130. package/dist/{self-updating-RKKIYNV4.js → self-updating-ZUWPGYUP.js} +7 -7
  131. package/dist/{selftest-AFLR7YV7.js → selftest-G4W3LIBQ.js} +21 -17
  132. package/dist/{selftest-AFLR7YV7.js.map → selftest-G4W3LIBQ.js.map} +1 -1
  133. package/dist/site-create-ZU3ACBTO.js +45 -0
  134. package/dist/site-create-ZU3ACBTO.js.map +1 -0
  135. package/dist/{site-mirror-H2FYMN55.js → site-mirror-WR7ACXOV.js} +10 -10
  136. package/dist/site-mirror-WR7ACXOV.js.map +1 -0
  137. package/dist/{smoke-suite-BWHR3XX5.js → smoke-suite-WAUPI6EJ.js} +7 -7
  138. package/dist/{submissions-II5J35QM.js → submissions-2SHCFOKD.js} +11 -6
  139. package/dist/submissions-2SHCFOKD.js.map +1 -0
  140. package/dist/{svelte-codemods-QWGIRNN6.js → svelte-codemods-VOLOKKFI.js} +6 -6
  141. package/dist/{sync-NKPDCMYW.js → sync-F4USL4K5.js} +6 -6
  142. package/dist/{sync-configs-HGLQV55H.js → sync-configs-OXSWKVZ2.js} +6 -6
  143. package/dist/turso-KQKBBQCK.js +24 -0
  144. package/dist/turso-KQKBBQCK.js.map +1 -0
  145. package/dist/{upgrade-WZ44IARG.js → upgrade-EVAL3JTO.js} +6 -6
  146. package/dist/util/git.d.ts +1 -1
  147. package/dist/util/git.js +1 -1
  148. package/dist/{websites-6DHIHJ2D.js → websites-Y6FN2XFD.js} +26 -21
  149. package/dist/{write-audits-to-airtable-BIK6LNVC.js → write-audits-to-airtable-TI3JMWSY.js} +6 -4
  150. package/package.json +1 -1
  151. package/dist/airtable-SJV4ZSMS.js +0 -9
  152. package/dist/announce-45HVP4J4.js.map +0 -1
  153. package/dist/attachments-NLZIJ5NU.js +0 -9
  154. package/dist/chunk-37B4LVB3.js.map +0 -1
  155. package/dist/chunk-72FJZ6LO.js.map +0 -1
  156. package/dist/chunk-74QRWE6G.js +0 -53
  157. package/dist/chunk-74QRWE6G.js.map +0 -1
  158. package/dist/chunk-CGQZFQAY.js.map +0 -1
  159. package/dist/chunk-F3JWOPTG.js.map +0 -1
  160. package/dist/chunk-GEUC5VGM.js.map +0 -1
  161. package/dist/chunk-H445EH33.js.map +0 -1
  162. package/dist/chunk-IHHAYXKY.js.map +0 -1
  163. package/dist/chunk-IY6256GK.js.map +0 -1
  164. package/dist/chunk-KN52PM63.js.map +0 -1
  165. package/dist/chunk-MZZDMBDT.js.map +0 -1
  166. package/dist/chunk-OTEVKRAH.js.map +0 -1
  167. package/dist/chunk-P4A6D6HO.js.map +0 -1
  168. package/dist/chunk-UIITN37J.js.map +0 -1
  169. package/dist/chunk-YFAXOD7Z.js +0 -42
  170. package/dist/chunk-YFAXOD7Z.js.map +0 -1
  171. package/dist/chunk-ZH5LOQET.js.map +0 -1
  172. package/dist/client-L7NAUG6J.js +0 -11
  173. package/dist/digest-5AJGWS33.js.map +0 -1
  174. package/dist/ensure-site-XESZHZEB.js.map +0 -1
  175. package/dist/fleet-events-cF2Om88w.d.ts +0 -24
  176. package/dist/forms-notify-target-4NPLX2UG.js.map +0 -1
  177. package/dist/github-signals-DMU74SAX.js.map +0 -1
  178. package/dist/header-image-BRTOLHJF.js.map +0 -1
  179. package/dist/launch-XTDMC6LD.js.map +0 -1
  180. package/dist/lighthouse-airtable-3NOFB42B.js +0 -12
  181. package/dist/match-harness-OW45CB5H.js +0 -77
  182. package/dist/match-harness-OW45CB5H.js.map +0 -1
  183. package/dist/migrate-NE3DUMTM.js +0 -7
  184. package/dist/parity-AEKYSLN6.js +0 -14
  185. package/dist/preflight-POZZI3GN.js.map +0 -1
  186. package/dist/prismic-ci-AHY5ULJR.js.map +0 -1
  187. package/dist/prismic-models-URYGODNY.js.map +0 -1
  188. package/dist/report-RC5LGPU3.js.map +0 -1
  189. package/dist/report-mirror-KKZZ6RMZ.js.map +0 -1
  190. package/dist/site-mirror-H2FYMN55.js.map +0 -1
  191. package/dist/submissions-II5J35QM.js.map +0 -1
  192. package/dist/write-audits-to-airtable-BIK6LNVC.js.map +0 -1
  193. /package/dist/{airtable-SJV4ZSMS.js.map → attachments-JFGFQRXQ.js.map} +0 -0
  194. /package/dist/{blux-RUFMNOUJ.js.map → blux-NXQLMS4W.js.map} +0 -0
  195. /package/dist/{bump-deps-EL3TKMIL.js.map → bump-deps-775VLDYB.js.map} +0 -0
  196. /package/dist/{chunk-IMYL67JV.js.map → chunk-3RLDX3Q2.js.map} +0 -0
  197. /package/dist/{chunk-CA6ADGOC.js.map → chunk-6PGXKN6F.js.map} +0 -0
  198. /package/dist/{chunk-Y5BVR4HW.js.map → chunk-6THEZQLA.js.map} +0 -0
  199. /package/dist/{chunk-DVJNYPFR.js.map → chunk-AYDQQBNG.js.map} +0 -0
  200. /package/dist/{chunk-SHGIDTYF.js.map → chunk-DYTYPKFN.js.map} +0 -0
  201. /package/dist/{chunk-NVLREB5R.js.map → chunk-EN6CTBUN.js.map} +0 -0
  202. /package/dist/{chunk-RRE74VWI.js.map → chunk-HSMN35LU.js.map} +0 -0
  203. /package/dist/{chunk-ECDJDHKT.js.map → chunk-JHJNNJLG.js.map} +0 -0
  204. /package/dist/{chunk-XY7YV2YQ.js.map → chunk-L3S3QZXL.js.map} +0 -0
  205. /package/dist/{chunk-XED2DNAI.js.map → chunk-LAG6IP7Y.js.map} +0 -0
  206. /package/dist/{chunk-SQ4FSNYL.js.map → chunk-LUF52EIY.js.map} +0 -0
  207. /package/dist/{chunk-6INOBNRO.js.map → chunk-MMSMZC7S.js.map} +0 -0
  208. /package/dist/{chunk-44SDGPGQ.js.map → chunk-PZZ6HM5T.js.map} +0 -0
  209. /package/dist/{chunk-4UTJ4G6P.js.map → chunk-TJLKWQOX.js.map} +0 -0
  210. /package/dist/{chunk-VEIIUCYL.js.map → chunk-TV76A6GN.js.map} +0 -0
  211. /package/dist/{chunk-D3CTOCUR.js.map → chunk-TVZ6QCOE.js.map} +0 -0
  212. /package/dist/{chunk-3EA43S3R.js.map → chunk-U2ZI6G7H.js.map} +0 -0
  213. /package/dist/{chunk-EQQ4H6L3.js.map → chunk-YXWBM26P.js.map} +0 -0
  214. /package/dist/{attachments-NLZIJ5NU.js.map → client-DGTO65WB.js.map} +0 -0
  215. /package/dist/{convert-to-pnpm-4DON7IFS.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
  216. /package/dist/{db-5K4CDEGJ.js.map → db-II5WAK4D.js.map} +0 -0
  217. /package/dist/{client-L7NAUG6J.js.map → digest-collectors-CYTN2EUX.js.map} +0 -0
  218. /package/dist/{digest-collectors-MC5NWEUA.js.map → fleet-state-RJROYXKO.js.map} +0 -0
  219. /package/dist/{health-endpoint-UCLMSSCN.js.map → health-endpoint-BJ6BKRHF.js.map} +0 -0
  220. /package/dist/{health-mirror-ONRT4DQ5.js.map → health-mirror-KU3G7V2Y.js.map} +0 -0
  221. /package/dist/{fleet-state-GJWGKTLD.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
  222. /package/dist/{init-3LMZTCVD.js.map → init-GOWDA5QS.js.map} +0 -0
  223. /package/dist/{import-airtable-IIK2QUUF.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
  224. /package/dist/{lighthouse-airtable-3NOFB42B.js.map → migrate-XZ7U5DFC.js.map} +0 -0
  225. /package/dist/{onboard-CZYBWVWH.js.map → onboard-JQQIMQW7.js.map} +0 -0
  226. /package/dist/{migrate-NE3DUMTM.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
  227. /package/dist/{orchestrate-TGDFGY52.js.map → parity-2JGVUCZD.js.map} +0 -0
  228. /package/dist/{prospect-audit-AJEE3Z4L.js.map → prospect-audit-FMPGQIRQ.js.map} +0 -0
  229. /package/dist/{parity-AEKYSLN6.js.map → render-from-row-75JTED2Z.js.map} +0 -0
  230. /package/dist/{self-updating-RKKIYNV4.js.map → self-updating-ZUWPGYUP.js.map} +0 -0
  231. /package/dist/{smoke-suite-BWHR3XX5.js.map → smoke-suite-WAUPI6EJ.js.map} +0 -0
  232. /package/dist/{svelte-codemods-QWGIRNN6.js.map → svelte-codemods-VOLOKKFI.js.map} +0 -0
  233. /package/dist/{sync-NKPDCMYW.js.map → sync-F4USL4K5.js.map} +0 -0
  234. /package/dist/{sync-configs-HGLQV55H.js.map → sync-configs-OXSWKVZ2.js.map} +0 -0
  235. /package/dist/{upgrade-WZ44IARG.js.map → upgrade-EVAL3JTO.js.map} +0 -0
  236. /package/dist/{render-from-row-65DC6ZP3.js.map → websites-Y6FN2XFD.js.map} +0 -0
  237. /package/dist/{websites-6DHIHJ2D.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
package/README.md CHANGED
@@ -158,7 +158,7 @@ Installs the matching harness used to rebuild a site against a live reference (a
158
158
  ```bash
159
159
  reddoor-maint match-harness --ref https://client.webflow.io
160
160
  reddoor-maint match-harness acme --ref https://acme.webflow.io --cand http://localhost:4173
161
- reddoor-maint match-harness --fleet airtable --ref https://client.webflow.io --matrix 1440,834,390
161
+ reddoor-maint match-harness --fleet turso --ref https://client.webflow.io --matrix 1440,834,390
162
162
  ```
163
163
 
164
164
  `--ref` is **required**. The harness gates against a live reference; with no reference it can only ever report an absence of errors, so the recipe refuses instead of installing something that goes green on nothing. `--cand` defaults to `http://localhost:5173` and `--matrix` to `1440,834,390`.
@@ -419,10 +419,10 @@ Nothing is written and nothing is sent.
419
419
  0. **Prereq: refresh Lighthouse scores on each Websites row.** From each site's checkout:
420
420
 
421
421
  ```bash
422
- reddoor-maint audit lighthouse --write-airtable
422
+ reddoor-maint audit lighthouse --write-back
423
423
  ```
424
424
 
425
- This runs Lighthouse and writes the 4 scores directly to the matching Websites row (slug auto-derived from `package.json#name`; pass `--write-airtable=<slug>` to override), along with a `Last lighthouse audit at` timestamp. The report orchestrator copies these into the new Reports row — drafting a report for a site missing scores fails with a clear error.
425
+ This runs Lighthouse and writes the 4 scores directly to the matching Websites row (slug auto-derived from `package.json#name`; pass `--write-back=<slug>` to override), along with a `Last lighthouse audit at` timestamp. The report orchestrator copies these into the new Reports row — drafting a report for a site missing scores fails with a clear error.
426
426
 
427
427
  1. **Draft overdue reports**
428
428
 
@@ -481,10 +481,9 @@ This repo's Netlify site hosts the whole dashboard + forms surface: the Resend d
481
481
  | `PROSPECT_EDIT_TOKEN` | Shared secret gating `POST /api/audit-report/:token/overrides`, where an operator's edits to a prospect report are saved — the SAME value is set on the marketing site, which proxies the save. Generate with `openssl rand -base64 32`. **Fails closed**: absent → every save 503s, which looks like a broken editor. Leading/trailing whitespace from a paste is tolerated, and warned about in the function logs |
482
482
  | `DASHBOARD_PASSWORD` | Basic-auth password gating the cockpit, per-site dashboards, and every action endpoint — the dashboard is unauthable without it |
483
483
  | `DASHBOARD_BASE_URL` | Public base URL of this deploy (e.g. `https://<your-site>.netlify.app`) — used to build absolute links such as report approve URLs |
484
- | `RENOVATE_TOKEN` | GitHub PAT with `actions:write` — powers the dashboard's "Trigger Renovate" button (the button 502s without it) |
485
- | `GH_TOKEN` | GitHub token for the dashboard's request-path REST calls (fleet-refresh dispatch + GitHub-sourced signals) |
484
+ | `GH_TOKEN` | GitHub token for the dashboard's request-path REST calls, with `actions:write` — the "Trigger Renovate" button, the fleet-refresh and prospect-audit dispatches (each answers 503 `not-configured` without it), plus GitHub-sourced signals. The only name these functions read: `RENOVATE_TOKEN` was retired with the operator PAT |
486
485
 
487
- > The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `PROSPECT_EDIT_TOKEN` is needed only to SAVE edits to a prospect report (serving one needs no secret beyond its URL token); `DASHBOARD_PASSWORD` / `DASHBOARD_BASE_URL` / `RENOVATE_TOKEN` / `GH_TOKEN` are additionally required by the operator **dashboard** this same deploy serves (cockpit at `/`, per-site at `/s/:slug`).
486
+ > The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `PROSPECT_EDIT_TOKEN` is needed only to SAVE edits to a prospect report (serving one needs no secret beyond its URL token); `DASHBOARD_PASSWORD` / `DASHBOARD_BASE_URL` / `GH_TOKEN` are additionally required by the operator **dashboard** this same deploy serves (cockpit at `/`, per-site at `/s/:slug`).
488
487
 
489
488
  **3. Trigger a deploy** (Deploys → Trigger deploy → Deploy site). When it goes green, curl the health endpoint:
490
489
 
@@ -1,24 +1,25 @@
1
1
  import {
2
2
  scoresFromRow
3
- } from "./chunk-44SDGPGQ.js";
3
+ } from "./chunk-PZZ6HM5T.js";
4
4
  import {
5
5
  fetchGaUsers,
6
6
  fetchSearch,
7
7
  readGaConfig,
8
8
  refreshHeaderImage
9
- } from "./chunk-72FJZ6LO.js";
9
+ } from "./chunk-DRNZVORX.js";
10
10
  import {
11
+ createReportDraft,
12
+ findReportForPeriod,
11
13
  queueDraft
12
- } from "./chunk-YFAXOD7Z.js";
14
+ } from "./chunk-UPE6D4AT.js";
13
15
  import {
14
- createDraft,
15
- findReportByPeriod,
16
16
  updateReportScores
17
- } from "./chunk-F3JWOPTG.js";
17
+ } from "./chunk-TEMQPSCM.js";
18
+ import "./chunk-ASLDCWBF.js";
18
19
  import "./chunk-VAD43MOR.js";
19
20
  import {
20
21
  uploadAttachment
21
- } from "./chunk-IY6256GK.js";
22
+ } from "./chunk-YJBSB4LP.js";
22
23
  import {
23
24
  openBase,
24
25
  readAirtableConfig
@@ -32,23 +33,26 @@ import {
32
33
  resolveCopy
33
34
  } from "./chunk-KJGKZLCT.js";
34
35
  import "./chunk-BGTYPVLT.js";
35
- import "./chunk-MZZDMBDT.js";
36
+ import "./chunk-LZMAG73U.js";
36
37
  import {
37
- listWebsites,
38
- siteSlug,
39
38
  updateAnalyticsHealth
40
- } from "./chunk-KN52PM63.js";
39
+ } from "./chunk-TC4GANMX.js";
40
+ import {
41
+ siteSlug
42
+ } from "./chunk-ICSRWW5Q.js";
43
+ import "./chunk-RQJFGK3A.js";
41
44
  import "./chunk-RARREDQE.js";
42
45
  import "./chunk-6VAL7XJT.js";
43
46
 
44
47
  // src/recipes/announce.ts
45
48
  var GA_WINDOW_DAYS = 30;
46
49
  async function announce(deps) {
47
- const base = deps?.base ?? openBase(readAirtableConfig());
48
- const now = deps?.now ?? /* @__PURE__ */ new Date();
49
- const websites = await listWebsites(base);
50
+ const base = deps.base ?? openBase(readAirtableConfig());
51
+ const now = deps.now ?? /* @__PURE__ */ new Date();
52
+ const writer = deps.reportMirror;
53
+ const websites = await deps.roster();
50
54
  let targets = websites.filter((w) => w.status === "maintained");
51
- if (deps?.site) {
55
+ if (deps.site) {
52
56
  const wanted = siteSlug(deps.site);
53
57
  targets = targets.filter((w) => siteSlug(w.name) === wanted);
54
58
  }
@@ -61,8 +65,8 @@ async function announce(deps) {
61
65
  results.push({ site: w.name, status: "skipped-no-scores" });
62
66
  continue;
63
67
  }
64
- if (deps?.refreshHeader !== false) {
65
- await refreshHeaderImage(w, deps?.refreshHeader ?? {});
68
+ if (deps.refreshHeader !== false) {
69
+ await refreshHeaderImage(w, deps.refreshHeader ?? {});
66
70
  }
67
71
  const periodEnd = now;
68
72
  const periodStart = new Date(now.getTime() - GA_WINDOW_DAYS * 24 * 60 * 60 * 1e3);
@@ -82,17 +86,17 @@ async function announce(deps) {
82
86
  w.id,
83
87
  gaResult.softFailed || searchResult.softFailed ? now.toISOString() : null
84
88
  );
85
- await deps?.siteMirror?.health(w.id, fields);
89
+ await deps.siteMirror?.health(w.id, fields);
86
90
  } catch (e) {
87
91
  console.warn(`\u26A0 analytics-health write skipped for ${w.name}: ${e.message}`);
88
92
  }
89
93
  }
90
94
  let report;
91
95
  let statusKind;
92
- const existing = await findReportByPeriod(base, w.id, "Announcement", period);
96
+ const existing = await findReportForPeriod(writer, w.id, "Announcement", period);
93
97
  if (existing) {
94
98
  await updateReportScores(base, existing.id, scores, now, enrichment);
95
- await deps?.reportMirror?.patch(existing.id, {
99
+ await writer.patch(existing.id, {
96
100
  lighthouse_performance: scores.performance,
97
101
  lighthouse_accessibility: scores.accessibility,
98
102
  lighthouse_best_practices: scores.bestPractices,
@@ -106,11 +110,9 @@ async function announce(deps) {
106
110
  report = existing;
107
111
  statusKind = "reused";
108
112
  } else {
109
- report = await createDraft(
110
- base,
111
- draftInputFor(w, scores, now, period, enrichment),
112
- deps?.reportMirror?.created
113
- );
113
+ report = await createReportDraft(draftInputFor(w, scores, now, period, enrichment), {
114
+ create: writer.create
115
+ });
114
116
  statusKind = "drafted";
115
117
  }
116
118
  const slug = siteSlug(w.name);
@@ -141,7 +143,7 @@ async function announce(deps) {
141
143
  `${slug}-${now.toISOString().slice(0, 10)}.html`,
142
144
  "text/html"
143
145
  );
144
- await deps?.reportMirror?.body(report.id, html);
146
+ await writer.body(report.id, html);
145
147
  } catch (uploadErr) {
146
148
  console.warn(
147
149
  `\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
@@ -150,7 +152,7 @@ async function announce(deps) {
150
152
  const queue = await queueDraft(
151
153
  base,
152
154
  { id: report.id, siteId: w.id, reportType: "Announcement" },
153
- deps?.reportMirror
155
+ writer
154
156
  );
155
157
  const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
156
158
  results.push({
@@ -203,11 +205,17 @@ function formatAnnounceResult(result) {
203
205
  return result.results.map(formatSiteResult).join("\n");
204
206
  }
205
207
  async function runAnnounceCommand(site, _opts) {
206
- const { makeReportMirror } = await import("./report-mirror-KKZZ6RMZ.js");
207
- const { makeSiteMirror } = await import("./site-mirror-H2FYMN55.js");
208
+ const { makeReportMirror } = await import("./report-mirror-4QM57LMS.js");
209
+ const { makeSiteMirror } = await import("./site-mirror-WR7ACXOV.js");
208
210
  const reportMirror = await makeReportMirror();
209
211
  const siteMirror = await makeSiteMirror();
210
- const result = await announce({ ...site ? { site } : {}, reportMirror, siteMirror });
212
+ const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
213
+ const result = await announce({
214
+ ...site ? { site } : {},
215
+ roster: () => readFleetRoster(),
216
+ reportMirror,
217
+ siteMirror
218
+ });
211
219
  const hadError = result.results.some((r) => r.status === "error");
212
220
  return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };
213
221
  }
@@ -215,4 +223,4 @@ export {
215
223
  formatAnnounceResult,
216
224
  runAnnounceCommand
217
225
  };
218
- //# sourceMappingURL=announce-45HVP4J4.js.map
226
+ //# sourceMappingURL=announce-5ZD2SBGD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recipes/announce.ts","../src/cli/commands/announce.ts"],"sourcesContent":["import { openBase, readAirtableConfig } from \"../reports/airtable/client.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { siteSlug, updateAnalyticsHealth } from \"../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { readGaConfig } from \"../reports/ga/config.js\";\nimport { updateReportScores, type ReportEnrichment } from \"../reports/airtable/reports.js\";\nimport { createReportDraft, findReportForPeriod } from \"../reports/create-report.js\";\nimport type { DraftInput } from \"../reports/draft-fields.js\";\nimport type { ReportMirror } from \"../reports/report-mirror.js\";\nimport type { SiteMirror } from \"../db/site-mirror.js\";\nimport { queueDraft } from \"../reports/queue.js\";\nimport { uploadAttachment } from \"../reports/airtable/attachments.js\";\nimport { renderReportHtml } from \"../reports/render.js\";\nimport { resolveCopy } from \"../reports/copy.js\";\nimport { fetchGaUsers, fetchSearch, refreshHeaderImage } from \"../reports/draft.js\";\nimport type { RefreshHeaderDeps } from \"../reports/draft.js\";\nimport { announcementSiteExtras } from \"../reports/announcement-email/template.js\";\nimport type { LighthouseScores } from \"../reports/types.js\";\nimport { defaultReportSubject } from \"../reports/subject.js\";\nimport { scoresFromRow } from \"../reports/report-data.js\";\n\nexport type AnnounceSiteResult =\n | {\n site: string;\n status: \"drafted\" | \"reused\";\n reportId: string;\n recipientMissing: boolean;\n /** False when a higher-or-equal-tier report was already queued (single-queue rule). */\n queued: boolean;\n }\n | { site: string; status: \"skipped-no-scores\" }\n | { site: string; status: \"error\"; message: string };\n\nexport type AnnounceResult = { results: AnnounceSiteResult[] };\n\n/** The traffic/search lookback window (days) the announcement reports on. The trend compares it\n * against the equal-length prior window, and the email labels it \"vs the previous N days\". */\nconst GA_WINDOW_DAYS = 30;\n\nexport type AnnounceDeps = {\n /** Airtable handle. Defaults to opening the live base from credentials. */\n base?: AirtableBase;\n /** When set, restrict to the single site whose slug matches. Default: all maintenance sites. */\n site?: string;\n /** Single timestamp driving the period key, render, draft, and preview filename. */\n now?: Date;\n /**\n * Header-image refresh, matching `draftReportForSite`'s opt-out shape: UNSET means\n * refresh (the production default), `false` skips it so unit suites don't launch a\n * browser. Announce previously never refreshed at all, so a site whose stored header\n * predated a plate change kept announcing with the old one until its next\n * Maintenance/Testing draft happened to heal it.\n */\n refreshHeader?: RefreshHeaderDeps | false;\n /** Every site in the fleet, read from TURSO (#646 step 4) — the Airtable\n * roster this replaces could not see a `site_<ULID>` site, so a site created\n * since step 3 was never announced. Required, not defaulted: every unit test\n * calls `announce` with a fake base, and a default would open a real libSQL\n * handle from inside the suite. The CLI composition root wires\n * `readFleetRoster`. */\n roster: () => Promise<WebsiteRow[]>;\n /** #539 Phase 5, and since #646 step 4 the store that MINTS and holds the\n * report row: the created row (or a reused row's refreshed scores), the\n * rendered body, and the queue flag. Required for the same reason `roster`\n * is — a default would open a real libSQL handle from inside the suite. */\n reportMirror: ReportMirror;\n /** #539 Phase 5: the Websites-row twin — announce stamps `Analytics soft-fail\n * at` on the SITE row, a different Turso table from the report. */\n siteMirror?: SiteMirror;\n};\n\n/**\n * Draft the monthly-report ANNOUNCEMENT email for every `maintenance` site (or one,\n * via `deps.site`). Airtable-driven and fleet-wide: unlike `launch`, it runs no audits\n * and takes no `Site`/inventory object — it reads the Lighthouse scores already stored\n * on each Websites row. DRAFTS ONLY; the M3 approve loop sends.\n *\n * One bad site must never abort the run: each site is wrapped in its own try/catch that\n * records an `error` result and continues.\n */\nexport async function announce(deps: AnnounceDeps): Promise<AnnounceResult> {\n const base = deps.base ?? openBase(readAirtableConfig());\n const now = deps.now ?? new Date();\n const writer = deps.reportMirror;\n\n const websites = await deps.roster();\n let targets = websites.filter((w) => w.status === \"maintained\");\n if (deps.site) {\n const wanted = siteSlug(deps.site);\n targets = targets.filter((w) => siteSlug(w.name) === wanted);\n }\n\n const period = now.toISOString().slice(0, 7);\n const results: AnnounceSiteResult[] = [];\n\n for (const w of targets) {\n try {\n const scores = scoresFromRow(w);\n if (scores === null) {\n results.push({ site: w.name, status: \"skipped-no-scores\" });\n continue;\n }\n\n // Refresh BEFORE the render so a queued announcement always has a current header\n // stored — the send re-reads the attachment, so a stale one here reaches the\n // client. Best-effort by construction: refreshHeaderImage returns false and never\n // throws, so a capture failure leaves the stored image and the draft continues.\n if (deps.refreshHeader !== false) {\n await refreshHeaderImage(w, deps.refreshHeader ?? {});\n }\n\n // Traffic + search snapshot over a ~30-day window ending now (fetchPeriodUsers derives\n // the equal-length previous window for the trend). Reuses the report pipeline's\n // soft-failing enrichment: GA/search unconfigured or an API error leaves the numbers\n // null and the email simply omits the traffic section — it never blocks the draft.\n const periodEnd = now;\n const periodStart = new Date(now.getTime() - GA_WINDOW_DAYS * 24 * 60 * 60 * 1000);\n const gaResult = await fetchGaUsers(w, periodStart, periodEnd);\n const searchResult = await fetchSearch(w, periodStart, periodEnd);\n const gaUsers = gaResult.value;\n const search = searchResult.value;\n const enrichment: ReportEnrichment = {\n ...(gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {}),\n ...(search ? { searchFoundPage1: search.foundOnPage1 } : {}),\n ...(search?.foundOnPage1 && search.position !== null\n ? { searchPosition: search.position }\n : {}),\n };\n\n // Record this site's GA/Search enrichment health for the per-site analytics-failure\n // signal (cockpit/digest) — exactly as the `--due` draft path does. Without this, an\n // announcement-time GA outage hides the traffic block with NO operator signal (it reads\n // identically to \"site has no GA configured\"). Set the timestamp on a soft-fail, clear\n // it on a clean enrichment so the signal self-heals. Best-effort: the column is\n // operator-added, so until it exists the write throws — which must not break the draft.\n if (readGaConfig() !== null && Boolean(w.ga4PropertyId || w.searchQuery)) {\n try {\n const fields = await updateAnalyticsHealth(\n base,\n w.id,\n gaResult.softFailed || searchResult.softFailed ? now.toISOString() : null,\n );\n // Mirror the EXACT FieldSet Airtable got — see draft.ts.\n await deps.siteMirror?.health(w.id, fields);\n } catch (e) {\n console.warn(`⚠ analytics-health write skipped for ${w.name}: ${(e as Error).message}`);\n }\n }\n\n // Dedupe: reuse an existing Announcement row for this (site, period) rather than\n // stacking a second draft. The reuse path refreshes the stored scores + traffic/search\n // (and Completed on) so the eventually-sent email — which reads the row — isn't stale.\n // The create path writes them via createDraft.\n let report;\n let statusKind: \"drafted\" | \"reused\";\n const existing = await findReportForPeriod(writer, w.id, \"Announcement\", period);\n if (existing) {\n await updateReportScores(base, existing.id, scores, now, enrichment);\n // Mirror the SAME refresh: the reuse path exists so the eventually-sent\n // email is not stale, and the console reads those numbers too.\n await writer.patch(existing.id, {\n lighthouse_performance: scores.performance,\n lighthouse_accessibility: scores.accessibility,\n lighthouse_best_practices: scores.bestPractices,\n lighthouse_seo: scores.seo,\n completed_on: now.toISOString().slice(0, 10),\n ...(enrichment.gaUsersCurrent !== undefined\n ? { ga_users_current: enrichment.gaUsersCurrent }\n : {}),\n ...(enrichment.gaUsersPrevious !== undefined\n ? { ga_users_previous: enrichment.gaUsersPrevious }\n : {}),\n ...(enrichment.searchFoundPage1 !== undefined\n ? { search_found_page1: enrichment.searchFoundPage1 ? 1 : 0 }\n : {}),\n ...(enrichment.searchPosition !== undefined\n ? { search_position: enrichment.searchPosition }\n : {}),\n });\n report = existing;\n statusKind = \"reused\";\n } else {\n // #646 step 4: minted and written in Turso (`report_<ULID>`). The\n // Airtable Reports row this replaces is skipped, not written — see\n // `createReportDraft`.\n report = await createReportDraft(draftInputFor(w, scores, now, period, enrichment), {\n create: writer.create,\n });\n statusKind = \"drafted\";\n }\n\n const slug = siteSlug(w.name);\n const { html } = await renderReportHtml({\n siteName: w.name,\n siteUrl: w.url,\n reportType: \"Announcement\",\n completedOn: now,\n lighthouse: scores,\n gaUsersCurrent: gaUsers?.current,\n gaUsersPrevious: gaUsers?.previous,\n gaPeriodDays: GA_WINDOW_DAYS,\n searchPosition: search?.foundOnPage1 ? (search.position ?? undefined) : undefined,\n lastTestedDate: null,\n commentary: null,\n copy: resolveCopy(w),\n headerImageCid: `${slug}-header`,\n // cadence (the client's go-forward pace, \"None\" omitted) + default-on improvement\n // callouts. Shared with the send re-render via announcementSiteExtras so the sent\n // email matches this reviewed preview.\n ...announcementSiteExtras(w),\n });\n\n // A preview-upload hiccup must NOT fail the site — log and continue.\n try {\n await uploadAttachment(\n report.id,\n \"Rendered HTML\",\n html,\n `${slug}-${now.toISOString().slice(0, 10)}.html`,\n \"text/html\",\n );\n // Store the same body in Turso — the console's preview route reads it\n // there, not from the Airtable attachment (whose URL expires).\n await writer.body(report.id, html);\n } catch (uploadErr) {\n console.warn(\n `⚠ Announcement preview upload skipped for ${w.name}: ${\n uploadErr instanceof Error ? uploadErr.message : String(uploadErr)\n }`,\n );\n }\n\n // Critical: NOT wrapped — without queueing, the draft never enters the approve queue,\n // so a failure here must surface as an error result for the site. queueDraft also\n // supersedes any lower-tier (Maintenance/Testing) drafts queued for this site, and\n // stands down if an equal-or-higher report is already queued (single-queue rule).\n const queue = await queueDraft(\n base,\n { id: report.id, siteId: w.id, reportType: \"Announcement\" },\n writer,\n );\n\n const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());\n results.push({\n site: w.name,\n status: statusKind,\n reportId: report.id,\n recipientMissing,\n queued: queue.queued,\n });\n } catch (err) {\n results.push({\n site: w.name,\n status: \"error\",\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return { results };\n}\n\n/** Build the Announcement `DraftInput`. Announcements have no period window and no prior\n * maintenance test, so periodStart/periodEnd/completedOn all collapse to `now` and\n * `lastTestedDate` is null. The subject override gives the email a purpose-built line. */\nfunction draftInputFor(\n w: WebsiteRow,\n scores: LighthouseScores,\n now: Date,\n period: string,\n enrichment: ReportEnrichment,\n): DraftInput {\n return {\n reportId: `${w.name} — Announcement — ${now.toISOString().slice(0, 10)}`,\n siteId: w.id,\n reportType: \"Announcement\",\n period,\n periodStart: now,\n periodEnd: now,\n completedOn: now,\n lighthouse: scores,\n lastTestedDate: null,\n subjectOverride: defaultReportSubject({\n name: w.name,\n url: w.url,\n type: \"Announcement\",\n date: now,\n }),\n ...enrichment,\n };\n}\n","import { announce, type AnnounceResult, type AnnounceSiteResult } from \"../../recipes/announce.js\";\n\nexport type AnnounceCommandOptions = {\n cwd?: string;\n};\n\nfunction formatSiteResult(r: AnnounceSiteResult): string {\n if (r.status === \"skipped-no-scores\") return `[${r.site}] skipped-no-scores`;\n if (r.status === \"error\") return `[${r.site}] error: ${r.message}`;\n const note = r.recipientMissing ? \" ⚠ recipient missing\" : \"\";\n return `[${r.site}] ${r.status}${note}`;\n}\n\nexport function formatAnnounceResult(result: AnnounceResult): string {\n if (result.results.length === 0) return \"No maintenance sites to announce.\";\n return result.results.map(formatSiteResult).join(\"\\n\");\n}\n\n/**\n * `announce [site]` — Airtable-driven and fleet-wide. Draft the monthly-report\n * announcement email for every `maintenance` site (or one, when `site` is given) into\n * the M3 approve queue. Never sends; the operator approves each draft and the next send\n * run delivers it. Reads the Lighthouse scores already stored on each Websites row —\n * no audits are run.\n */\nexport async function runAnnounceCommand(\n site: string | undefined,\n _opts: AnnounceCommandOptions,\n): Promise<{ output: string; code: number }> {\n // #539 Phase 5: the create-side Turso dual-write is wired HERE rather than\n // inside `announce`, so a unit suite calling the recipe with a fake base can\n // never open a real libSQL handle (and, with TURSO_* exported locally, write\n // into production).\n const { makeReportMirror } = await import(\"../../reports/report-mirror.js\");\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n const reportMirror = await makeReportMirror();\n const siteMirror = await makeSiteMirror();\n // #646 step 4: the fleet roster comes from Turso — an Airtable roster cannot\n // see a `site_<ULID>` site, so one created since step 3 was never announced.\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n const result = await announce({\n ...(site ? { site } : {}),\n roster: () => readFleetRoster(),\n reportMirror,\n siteMirror,\n });\n const hadError = result.results.some((r) => r.status === \"error\");\n return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,10 @@
1
+ import {
2
+ fetchAttachmentBytes,
3
+ uploadAttachment
4
+ } from "./chunk-YJBSB4LP.js";
5
+ import "./chunk-RQJFGK3A.js";
6
+ export {
7
+ fetchAttachmentBytes,
8
+ uploadAttachment
9
+ };
10
+ //# sourceMappingURL=attachments-JFGFQRXQ.js.map
@@ -1,13 +1,13 @@
1
+ import {
2
+ assetRef,
3
+ richText
4
+ } from "./chunk-UWJZXL2D.js";
1
5
  import {
2
6
  FROZEN_PAGE_TYPE,
3
7
  extractMapConfig,
4
8
  frozenPageCustomType,
5
9
  makeIsMapMount
6
10
  } from "./chunk-DRDAEELJ.js";
7
- import {
8
- assetRef,
9
- richText
10
- } from "./chunk-UWJZXL2D.js";
11
11
  import {
12
12
  CDN_HOSTS,
13
13
  collectAssetUrls
@@ -4767,4 +4767,4 @@ ${lines.join("\n")}`,
4767
4767
  export {
4768
4768
  runBluxCommand
4769
4769
  };
4770
- //# sourceMappingURL=blux-RUFMNOUJ.js.map
4770
+ //# sourceMappingURL=blux-NXQLMS4W.js.map
@@ -5,19 +5,19 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-SHGIDTYF.js";
8
+ } from "./chunk-DYTYPKFN.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-H445EH33.js";
11
+ } from "./chunk-FP6HOW6J.js";
12
12
  import {
13
13
  bumpDeps
14
- } from "./chunk-VEIIUCYL.js";
15
- import "./chunk-SQ4FSNYL.js";
14
+ } from "./chunk-TV76A6GN.js";
15
+ import "./chunk-LUF52EIY.js";
16
16
  import "./chunk-Z3JXLULD.js";
17
17
  import "./chunk-LBYOLBW7.js";
18
18
  import "./chunk-RARREDQE.js";
19
19
  import "./chunk-XXTZBPUY.js";
20
- import "./chunk-EASYCD6F.js";
20
+ import "./chunk-WMF5S2MB.js";
21
21
 
22
22
  // src/cli/commands/bump-deps.ts
23
23
  import { resolve } from "path";
@@ -57,4 +57,4 @@ async function runBumpDepsCommand(site, opts) {
57
57
  export {
58
58
  runBumpDepsCommand
59
59
  };
60
- //# sourceMappingURL=bump-deps-EL3TKMIL.js.map
60
+ //# sourceMappingURL=bump-deps-775VLDYB.js.map
@@ -1,6 +1,3 @@
1
- import {
2
- withRecipe
3
- } from "./chunk-SQ4FSNYL.js";
4
1
  import {
5
2
  ALL_TEMPLATES,
6
3
  RENOVATE_ACTION_CONFIG,
@@ -8,10 +5,13 @@ import {
8
5
  templatesByName,
9
6
  withRenovatePinsFrom
10
7
  } from "./chunk-SLFG7Y76.js";
8
+ import {
9
+ withRecipe
10
+ } from "./chunk-LUF52EIY.js";
11
11
  import {
12
12
  listTrackedFiles,
13
13
  removeFromIndex
14
- } from "./chunk-EASYCD6F.js";
14
+ } from "./chunk-WMF5S2MB.js";
15
15
 
16
16
  // src/recipes/sync-configs.ts
17
17
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -229,4 +229,4 @@ export {
229
229
  planTemplateDiffs,
230
230
  syncConfigs
231
231
  };
232
- //# sourceMappingURL=chunk-IMYL67JV.js.map
232
+ //# sourceMappingURL=chunk-3RLDX3Q2.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-SQ4FSNYL.js";
3
+ } from "./chunk-LUF52EIY.js";
4
4
  import {
5
5
  bumpDep,
6
6
  readPackageJson,
@@ -150,4 +150,4 @@ export {
150
150
  selfCaretRange,
151
151
  onboard
152
152
  };
153
- //# sourceMappingURL=chunk-CA6ADGOC.js.map
153
+ //# sourceMappingURL=chunk-6PGXKN6F.js.map
@@ -9,10 +9,10 @@ import {
9
9
  } from "./chunk-KJGKZLCT.js";
10
10
  import {
11
11
  applyReportTypeHeadline
12
- } from "./chunk-MZZDMBDT.js";
12
+ } from "./chunk-LZMAG73U.js";
13
13
  import {
14
14
  siteSlug
15
- } from "./chunk-KN52PM63.js";
15
+ } from "./chunk-ICSRWW5Q.js";
16
16
 
17
17
  // src/reports/send/render-email.ts
18
18
  function toInlineAttachment(a) {
@@ -143,4 +143,4 @@ export {
143
143
  renderReportFromRow,
144
144
  windowDays
145
145
  };
146
- //# sourceMappingURL=chunk-Y5BVR4HW.js.map
146
+ //# sourceMappingURL=chunk-6THEZQLA.js.map
@@ -0,0 +1,41 @@
1
+ // src/reports/report-row.ts
2
+ var REPORT_TYPES = ["Maintenance", "Testing", "Launch", "Announcement"];
3
+ function toReportType(raw) {
4
+ if (raw && REPORT_TYPES.includes(raw)) return raw;
5
+ if (raw)
6
+ console.warn(`[reports] unknown Report type ${JSON.stringify(raw)} \u2014 treating as Maintenance`);
7
+ return "Maintenance";
8
+ }
9
+ function isPendingApproval(r) {
10
+ return r.draftReady && !r.approvedToSend && r.sentAt === null;
11
+ }
12
+ function parseAutoEvidence(raw) {
13
+ if (typeof raw !== "string" || !raw.trim()) return null;
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(raw);
17
+ } catch {
18
+ return null;
19
+ }
20
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
21
+ const out = {};
22
+ for (const [field, v] of Object.entries(parsed)) {
23
+ if (!v || typeof v !== "object") continue;
24
+ const o = v;
25
+ if (o.result !== "pass" && o.result !== "fail" && o.result !== "unknown" && o.result !== "n/a")
26
+ continue;
27
+ out[field] = {
28
+ result: o.result,
29
+ checkedAt: typeof o.checkedAt === "string" ? o.checkedAt : null,
30
+ note: typeof o.note === "string" ? o.note : ""
31
+ };
32
+ }
33
+ return Object.keys(out).length > 0 ? out : null;
34
+ }
35
+
36
+ export {
37
+ toReportType,
38
+ isPendingApproval,
39
+ parseAutoEvidence
40
+ };
41
+ //# sourceMappingURL=chunk-ASLDCWBF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reports/report-row.ts"],"sourcesContent":["/**\n * The report-row model and its pure, vendor-neutral coercers.\n *\n * `ReportRow` is what BOTH readers return — `mapRow` over an Airtable record\n * (`src/reports/airtable/reports.ts`) and the Turso report reader in\n * `src/db/fleet-state.ts`. Moved out of the Airtable module in #539 Phase 6\n * step 1 (#646) so the Turso read path does not depend on the directory Phase 6\n * deletes. `src/reports/airtable/reports.ts` re-exports every name.\n */\nimport type { ReportType, LighthouseScores } from \"./types.js\";\nimport type { EvidenceRecord } from \"./auto-tick.js\";\n\nconst REPORT_TYPES: readonly ReportType[] = [\"Maintenance\", \"Testing\", \"Launch\", \"Announcement\"];\n\n/** Coerce the Airtable `Report type` (a single-select string) to a known\n * ReportType. A bare `as ReportType` cast is a compile-time lie: if the\n * single-select gains an unexpected option, the bad value flows to render.ts,\n * where `reportType === \"Launch\"` silently falls through to the Maintenance\n * template. Validate at the boundary; warn + default to \"Maintenance\" so an\n * unknown type is VISIBLE in the logs rather than silently mis-templated. */\nexport function toReportType(raw: string | undefined): ReportType {\n if (raw && (REPORT_TYPES as readonly string[]).includes(raw)) return raw as ReportType;\n if (raw)\n console.warn(`[reports] unknown Report type ${JSON.stringify(raw)} — treating as Maintenance`);\n return \"Maintenance\";\n}\n\nexport type DeliveryStatus = \"pending\" | \"delivered\" | \"bounced\" | \"complained\";\n\nexport type ReportRow = {\n id: string;\n reportId: string;\n siteId: string;\n reportType: ReportType;\n /** UTC `YYYY-MM` recurrence key (idempotency for search-before-create). Null on legacy rows. */\n period: string | null;\n periodStart: string | null;\n periodEnd: string | null;\n completedOn: string | null;\n lighthouse: LighthouseScores | null;\n gaUsersCurrent: number | null;\n gaUsersPrevious: number | null;\n searchFoundPage1: boolean | null;\n searchPosition: number | null;\n lastTestedDate: string | null;\n commentary: string | null;\n subjectOverride: string | null;\n draftReady: boolean;\n approvedToSend: boolean;\n sentAt: string | null;\n approvedAt: string | null;\n approvedBy: string | null;\n deliveryStatus: DeliveryStatus;\n renderedHtmlAttachment: { url: string; filename: string } | null;\n /** Read out of the Resend response and stored in a hidden field; needed for webhook reconciliation. */\n resendMessageId: string | null;\n /** The 12 operator-checklist checkboxes, keyed by their Airtable column name (ALL_CHECKLIST_FIELDS);\n * missing/false cells read false. Maintenance/Testing reports gate approve+send on the relevant\n * subset (see src/reports/checklist.ts). */\n checklist: Record<string, boolean>;\n /** Snapshot of the auto-tick evidence at draft time, keyed by checklist field → evidence\n * record. Null when the report predates auto-tick or carried no auto-checked items. This is the\n * health gate's source of truth: `isHealthGateClear`/`gatingHealth` (src/reports/checklist.ts)\n * read these evidence records, NOT the checklist booleans — and also drive the dashboard badges. */\n autoEvidence: Record<string, EvidenceRecord> | null;\n /** Logged send-anyway override (Phase 10). When `sendOverride` is true AND `overrideReason` is\n * non-empty, the health gate is bypassed for THIS report; `overrideBy`/`overrideAt` are the\n * audit trail (parallel to approvedBy/approvedAt). Missing cells read false/null. */\n sendOverride: boolean;\n overrideReason: string | null;\n overrideBy: string | null;\n overrideAt: string | null;\n};\n\n/**\n * The \"Ready for your yes\" gate: Draft ready ∧ ¬Approved to send ∧ Sent at BLANK.\n * The single source of truth for \"pending the operator's approval\" — `listPendingApproval`,\n * `runDigest`'s ready-list, the per-site dashboard, and the fleet cockpit all key off this\n * one predicate so the surfaces can't drift.\n */\nexport function isPendingApproval(r: ReportRow): boolean {\n return r.draftReady && !r.approvedToSend && r.sentAt === null;\n}\n\n/**\n * Parse the `Checklist auto-evidence` JSON field → a field→EvidenceRecord map, or null when\n * absent/malformed. Permissive on inner shape (it's display-only — a bad blob just yields null,\n * never throws), mirroring `parseNotifyRouting`.\n */\nexport function parseAutoEvidence(raw: unknown): Record<string, EvidenceRecord> | null {\n if (typeof raw !== \"string\" || !raw.trim()) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n // Validate each entry's inner shape (it drives display) — drop any record whose `result` isn't\n // one of the four literals, and coerce `note`/`checkedAt` to safe types. A garbage blob yields\n // null rather than a record that would render \"auto: undefined\" on the dashboard.\n const out: Record<string, EvidenceRecord> = {};\n for (const [field, v] of Object.entries(parsed as Record<string, unknown>)) {\n if (!v || typeof v !== \"object\") continue;\n const o = v as Record<string, unknown>;\n if (o.result !== \"pass\" && o.result !== \"fail\" && o.result !== \"unknown\" && o.result !== \"n/a\")\n continue;\n out[field] = {\n result: o.result,\n checkedAt: typeof o.checkedAt === \"string\" ? o.checkedAt : null,\n note: typeof o.note === \"string\" ? o.note : \"\",\n };\n }\n return Object.keys(out).length > 0 ? out : null;\n}\n"],"mappings":";AAYA,IAAM,eAAsC,CAAC,eAAe,WAAW,UAAU,cAAc;AAQxF,SAAS,aAAa,KAAqC;AAChE,MAAI,OAAQ,aAAmC,SAAS,GAAG,EAAG,QAAO;AACrE,MAAI;AACF,YAAQ,KAAK,iCAAiC,KAAK,UAAU,GAAG,CAAC,iCAA4B;AAC/F,SAAO;AACT;AAuDO,SAAS,kBAAkB,GAAuB;AACvD,SAAO,EAAE,cAAc,CAAC,EAAE,kBAAkB,EAAE,WAAW;AAC3D;AAOO,SAAS,kBAAkB,KAAqD;AACrF,MAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,KAAK,EAAG,QAAO;AACnD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,QAAO;AAI3E,QAAM,MAAsC,CAAC;AAC7C,aAAW,CAAC,OAAO,CAAC,KAAK,OAAO,QAAQ,MAAiC,GAAG;AAC1E,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU;AACjC,UAAM,IAAI;AACV,QAAI,EAAE,WAAW,UAAU,EAAE,WAAW,UAAU,EAAE,WAAW,aAAa,EAAE,WAAW;AACvF;AACF,QAAI,KAAK,IAAI;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,MAC3D,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,IAC9C;AAAA,EACF;AACA,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM;AAC7C;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-SQ4FSNYL.js";
3
+ } from "./chunk-LUF52EIY.js";
4
4
  import {
5
5
  readPackageJson,
6
6
  writePackageJson
@@ -96,4 +96,4 @@ async function convertToPnpm(site, opts = {}) {
96
96
  export {
97
97
  convertToPnpm
98
98
  };
99
- //# sourceMappingURL=chunk-DVJNYPFR.js.map
99
+ //# sourceMappingURL=chunk-AYDQQBNG.js.map
@@ -5,11 +5,11 @@ import {
5
5
  mapWebsiteRecord,
6
6
  scheduleColumnFor,
7
7
  siteValueFor
8
- } from "./chunk-UIITN37J.js";
8
+ } from "./chunk-LF2TJN4X.js";
9
9
  import {
10
10
  parseAutoEvidence,
11
11
  toReportType
12
- } from "./chunk-F3JWOPTG.js";
12
+ } from "./chunk-ASLDCWBF.js";
13
13
  import {
14
14
  MAINTENANCE_CHECKLIST,
15
15
  TESTING_CHECKLIST
@@ -22,7 +22,7 @@ import {
22
22
  toPrismicModelsVerdict,
23
23
  toVerdict,
24
24
  trimToNull
25
- } from "./chunk-KN52PM63.js";
25
+ } from "./chunk-ICSRWW5Q.js";
26
26
 
27
27
  // src/db/fleet-state.ts
28
28
  import { sql } from "kysely";
@@ -199,6 +199,35 @@ async function mirrorHealthFields(db, siteId, fields) {
199
199
  const res = await db.updateTable("site_health").set(patch).where("site_id", "=", siteId).executeTakeFirst();
200
200
  return res.numUpdatedRows > 0n;
201
201
  }
202
+ async function insertSiteRows(db, site, computedAt) {
203
+ await db.insertInto("sites").values({
204
+ id: site.id,
205
+ slug: site.slug,
206
+ name: site.name,
207
+ status: site.status,
208
+ url: site.url,
209
+ point_of_contact: site.pointOfContact,
210
+ git_repo: site.gitRepo,
211
+ require_turnstile: 0
212
+ }).execute();
213
+ await db.insertInto("site_health").values({ site_id: site.id }).execute();
214
+ await db.insertInto("site_schedule").values({
215
+ site_id: site.id,
216
+ next_maintenance_at: null,
217
+ next_testing_at: null,
218
+ computed_at: computedAt
219
+ }).execute();
220
+ }
221
+ async function updateSiteIdentity(db, siteId, patch) {
222
+ const set = {};
223
+ if (patch.name !== void 0) set.name = patch.name;
224
+ if (patch.url !== void 0) set.url = patch.url;
225
+ if (patch.pointOfContact !== void 0) set.point_of_contact = patch.pointOfContact;
226
+ if (patch.gitRepo !== void 0) set.git_repo = patch.gitRepo;
227
+ if (Object.keys(set).length === 0) return true;
228
+ const res = await db.updateTable("sites").set(set).where("id", "=", siteId).executeTakeFirst();
229
+ return res.numUpdatedRows > 0n;
230
+ }
202
231
  async function mirrorScheduleFields(db, siteId, fields, computedAt) {
203
232
  const patch = {};
204
233
  for (const [field, value] of Object.entries(fields)) {
@@ -332,6 +361,10 @@ async function listReportsForSite(db, siteId) {
332
361
  const rows = await db.selectFrom("reports").select(REPORT_LIST_COLUMNS).select(HAS_RENDERED_HTML).where("site_id", "=", siteId).orderBy("period_start", "desc").orderBy("id").execute();
333
362
  return rows.map((r) => reportRowFromDb(r, r.has_rendered_html !== 0));
334
363
  }
364
+ async function listSendableReports(db) {
365
+ const rows = await db.selectFrom("reports").select(REPORT_LIST_COLUMNS).select(HAS_RENDERED_HTML).where("draft_ready", "=", 1).where("approved_to_send", "=", 1).where("sent_at", "is", null).orderBy("period_start", "desc").orderBy("id").execute();
366
+ return rows.map((r) => reportRowFromDb(r, r.has_rendered_html !== 0));
367
+ }
335
368
  async function mirrorReportPatch(db, reportId, patch) {
336
369
  if (Object.keys(patch).length === 0) return true;
337
370
  const res = await db.updateTable("reports").set(patch).where("id", "=", reportId).executeTakeFirst();
@@ -343,6 +376,9 @@ async function mirrorReportInsert(db, rec) {
343
376
  void _rh;
344
377
  await db.insertInto("reports").values(row).onConflict((oc) => oc.column("id").doUpdateSet(rowSansHtml)).execute();
345
378
  }
379
+ async function insertReportRow(db, rec) {
380
+ await db.insertInto("reports").values(mapReportRecord(rec, null)).execute();
381
+ }
346
382
  async function storeRenderedHtml(db, reportId, html) {
347
383
  await db.updateTable("reports").set({ rendered_html: html }).where("id", "=", reportId).execute();
348
384
  }
@@ -350,6 +386,10 @@ async function getReportById(db, id) {
350
386
  const r = await db.selectFrom("reports").selectAll().where("id", "=", id).executeTakeFirst();
351
387
  return r ? reportRowFromDb(r, r.rendered_html !== null) : null;
352
388
  }
389
+ async function findReportByMessageId(db, messageId) {
390
+ const r = await db.selectFrom("reports").select(REPORT_LIST_COLUMNS).select(HAS_RENDERED_HTML).where("resend_message_id", "=", messageId).limit(1).executeTakeFirst();
391
+ return r ? reportRowFromDb(r, r.has_rendered_html !== 0) : null;
392
+ }
353
393
  async function getReportHtml(db, id) {
354
394
  const r = await db.selectFrom("reports").select(["rendered_html", "report_id"]).where("id", "=", id).executeTakeFirst();
355
395
  if (!r || r.rendered_html === null) return null;
@@ -361,6 +401,8 @@ export {
361
401
  mirrorSiteFields,
362
402
  mirrorSiteInsert,
363
403
  mirrorHealthFields,
404
+ insertSiteRows,
405
+ updateSiteIdentity,
364
406
  mirrorScheduleFields,
365
407
  getSiteBySlug,
366
408
  siteRowExists,
@@ -369,10 +411,13 @@ export {
369
411
  REPORT_LIST_COLUMNS,
370
412
  listAllReports,
371
413
  listReportsForSite,
414
+ listSendableReports,
372
415
  mirrorReportPatch,
373
416
  mirrorReportInsert,
417
+ insertReportRow,
374
418
  storeRenderedHtml,
375
419
  getReportById,
420
+ findReportByMessageId,
376
421
  getReportHtml
377
422
  };
378
- //# sourceMappingURL=chunk-GEUC5VGM.js.map
423
+ //# sourceMappingURL=chunk-BINFZQ2T.js.map