@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
package/README.md CHANGED
@@ -66,6 +66,14 @@ reddoor-maint upgrade svelte-4-to-5 [site]
66
66
 
67
67
  `[site]` defaults to `process.cwd()`. Add `--fleet path/to/inventory.json` (or `.mjs` / `.js`) to run across every site in an inventory instead. `--cwd <path>` overrides the working directory for any command.
68
68
 
69
+ Standalone, outside the `[site]` loop above (it audits an external URL, not a fleet site):
70
+
71
+ - `prospect-audit <url>` — AEO/SEO audit an external prospect's site and publish
72
+ a shareable report. Flags: `--business <name>`, `--competitors <list>`,
73
+ `--no-probes`, `--out <file>`, `--json`. Needs `ANTHROPIC_API_KEY` (or the
74
+ standard Anthropic auth chain) and, for tier 3, `PERPLEXITY_API_KEY`;
75
+ persistence and the shareable link need the Turso vars.
76
+
69
77
  ### Common flags
70
78
 
71
79
  - `--only <names>` — comma-separated subset. Validates against the known set; typos exit with code 2.
@@ -0,0 +1,9 @@
1
+ import {
2
+ fromAirtableBase
3
+ } from "./chunk-C6CLRKPV.js";
4
+ import "./chunk-7NYN2P6I.js";
5
+ import "./chunk-EVBCK2KC.js";
6
+ export {
7
+ fromAirtableBase
8
+ };
9
+ //# sourceMappingURL=airtable-ZBUPQJSC.js.map
@@ -1,40 +1,44 @@
1
1
  import {
2
2
  scoresFromRow
3
- } from "./chunk-GZNBCHAZ.js";
3
+ } from "./chunk-HEWSRXYV.js";
4
4
  import {
5
5
  fetchGaUsers,
6
6
  fetchSearch,
7
- readGaConfig
8
- } from "./chunk-MGR2H7F4.js";
7
+ readGaConfig,
8
+ refreshHeaderImage
9
+ } from "./chunk-BBUSGEES.js";
9
10
  import {
10
11
  queueDraft
11
- } from "./chunk-62VF32IC.js";
12
- import "./chunk-37S6L6G4.js";
12
+ } from "./chunk-YFAXOD7Z.js";
13
13
  import {
14
- defaultReportSubject
15
- } from "./chunk-GECESMVS.js";
16
- import {
17
- announcementSiteExtras,
18
14
  createDraft,
19
15
  findReportByPeriod,
20
- renderReportHtml,
21
- resolveCopy,
22
16
  updateReportScores
23
- } from "./chunk-6QRLRT3P.js";
17
+ } from "./chunk-F3JWOPTG.js";
18
+ import "./chunk-VAD43MOR.js";
24
19
  import {
25
20
  uploadAttachment
26
- } from "./chunk-MPBXOJ3N.js";
27
- import "./chunk-SEEA6RXF.js";
28
- import "./chunk-U5SZ4FOP.js";
29
- import {
30
- listWebsites,
31
- siteSlug,
32
- updateAnalyticsHealth
33
- } from "./chunk-577HCGZO.js";
21
+ } from "./chunk-IY6256GK.js";
34
22
  import {
35
23
  openBase,
36
24
  readAirtableConfig
37
25
  } from "./chunk-HN6I2XWI.js";
26
+ import {
27
+ defaultReportSubject
28
+ } from "./chunk-GECESMVS.js";
29
+ import {
30
+ announcementSiteExtras,
31
+ renderReportHtml,
32
+ resolveCopy
33
+ } from "./chunk-MT2JINSS.js";
34
+ import "./chunk-DJZWYIPP.js";
35
+ import "./chunk-5UINJNAJ.js";
36
+ import {
37
+ listWebsites,
38
+ siteSlug,
39
+ updateAnalyticsHealth
40
+ } from "./chunk-7NYN2P6I.js";
41
+ import "./chunk-EVBCK2KC.js";
38
42
  import "./chunk-6VAL7XJT.js";
39
43
 
40
44
  // src/recipes/announce.ts
@@ -43,7 +47,7 @@ async function announce(deps) {
43
47
  const base = deps?.base ?? openBase(readAirtableConfig());
44
48
  const now = deps?.now ?? /* @__PURE__ */ new Date();
45
49
  const websites = await listWebsites(base);
46
- let targets = websites.filter((w) => w.status === "maintenance");
50
+ let targets = websites.filter((w) => w.status === "maintained");
47
51
  if (deps?.site) {
48
52
  const wanted = siteSlug(deps.site);
49
53
  targets = targets.filter((w) => siteSlug(w.name) === wanted);
@@ -57,6 +61,9 @@ async function announce(deps) {
57
61
  results.push({ site: w.name, status: "skipped-no-scores" });
58
62
  continue;
59
63
  }
64
+ if (deps?.refreshHeader !== false) {
65
+ await refreshHeaderImage(w, deps?.refreshHeader ?? {});
66
+ }
60
67
  const periodEnd = now;
61
68
  const periodStart = new Date(now.getTime() - GA_WINDOW_DAYS * 24 * 60 * 60 * 1e3);
62
69
  const gaResult = await fetchGaUsers(w, periodStart, periodEnd);
@@ -70,11 +77,12 @@ async function announce(deps) {
70
77
  };
71
78
  if (readGaConfig() !== null && Boolean(w.ga4PropertyId || w.searchQuery)) {
72
79
  try {
73
- await updateAnalyticsHealth(
80
+ const fields = await updateAnalyticsHealth(
74
81
  base,
75
82
  w.id,
76
83
  gaResult.softFailed || searchResult.softFailed ? now.toISOString() : null
77
84
  );
85
+ await deps?.siteMirror?.health(w.id, fields);
78
86
  } catch (e) {
79
87
  console.warn(`\u26A0 analytics-health write skipped for ${w.name}: ${e.message}`);
80
88
  }
@@ -84,10 +92,25 @@ async function announce(deps) {
84
92
  const existing = await findReportByPeriod(base, w.id, "Announcement", period);
85
93
  if (existing) {
86
94
  await updateReportScores(base, existing.id, scores, now, enrichment);
95
+ await deps?.reportMirror?.patch(existing.id, {
96
+ lighthouse_performance: scores.performance,
97
+ lighthouse_accessibility: scores.accessibility,
98
+ lighthouse_best_practices: scores.bestPractices,
99
+ lighthouse_seo: scores.seo,
100
+ completed_on: now.toISOString().slice(0, 10),
101
+ ...enrichment.gaUsersCurrent !== void 0 ? { ga_users_current: enrichment.gaUsersCurrent } : {},
102
+ ...enrichment.gaUsersPrevious !== void 0 ? { ga_users_previous: enrichment.gaUsersPrevious } : {},
103
+ ...enrichment.searchFoundPage1 !== void 0 ? { search_found_page1: enrichment.searchFoundPage1 ? 1 : 0 } : {},
104
+ ...enrichment.searchPosition !== void 0 ? { search_position: enrichment.searchPosition } : {}
105
+ });
87
106
  report = existing;
88
107
  statusKind = "reused";
89
108
  } else {
90
- report = await createDraft(base, draftInputFor(w, scores, now, period, enrichment));
109
+ report = await createDraft(
110
+ base,
111
+ draftInputFor(w, scores, now, period, enrichment),
112
+ deps?.reportMirror?.created
113
+ );
91
114
  statusKind = "drafted";
92
115
  }
93
116
  const slug = siteSlug(w.name);
@@ -118,16 +141,17 @@ async function announce(deps) {
118
141
  `${slug}-${now.toISOString().slice(0, 10)}.html`,
119
142
  "text/html"
120
143
  );
144
+ await deps?.reportMirror?.body(report.id, html);
121
145
  } catch (uploadErr) {
122
146
  console.warn(
123
147
  `\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
124
148
  );
125
149
  }
126
- const queue = await queueDraft(base, {
127
- id: report.id,
128
- siteId: w.id,
129
- reportType: "Announcement"
130
- });
150
+ const queue = await queueDraft(
151
+ base,
152
+ { id: report.id, siteId: w.id, reportType: "Announcement" },
153
+ deps?.reportMirror
154
+ );
131
155
  const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
132
156
  results.push({
133
157
  site: w.name,
@@ -179,7 +203,11 @@ function formatAnnounceResult(result) {
179
203
  return result.results.map(formatSiteResult).join("\n");
180
204
  }
181
205
  async function runAnnounceCommand(site, _opts) {
182
- const result = await announce(site ? { site } : {});
206
+ const { makeReportMirror } = await import("./report-mirror-4MNORYQ4.js");
207
+ const { makeSiteMirror } = await import("./site-mirror-NIREX5ME.js");
208
+ const reportMirror = await makeReportMirror();
209
+ const siteMirror = await makeSiteMirror();
210
+ const result = await announce({ ...site ? { site } : {}, reportMirror, siteMirror });
183
211
  const hadError = result.results.some((r) => r.status === "error");
184
212
  return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };
185
213
  }
@@ -187,4 +215,4 @@ export {
187
215
  formatAnnounceResult,
188
216
  runAnnounceCommand
189
217
  };
190
- //# sourceMappingURL=announce-GIAYN67S.js.map
218
+ //# sourceMappingURL=announce-I2Z4JN3I.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 { listWebsites, siteSlug, updateAnalyticsHealth } from \"../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { readGaConfig } from \"../reports/ga/config.js\";\nimport {\n createDraft,\n findReportByPeriod,\n updateReportScores,\n type ReportEnrichment,\n} from \"../reports/airtable/reports.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 /** #539 Phase 5: Turso write-through for everything this recipe writes — the\n * created row (or a reused row's refreshed scores), the rendered body, and\n * the queue flag. Not defaulted here — every unit test calls `announce` with\n * a fake base, and a default would open a real libSQL handle from inside the\n * suite. The CLI composition root wires it. */\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\n const websites = await listWebsites(base);\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 findReportByPeriod(base, 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 deps?.reportMirror?.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 report = await createDraft(\n base,\n draftInputFor(w, scores, now, period, enrichment),\n deps?.reportMirror?.created,\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 deps?.reportMirror?.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 deps?.reportMirror,\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): Parameters<typeof createDraft>[1] {\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 const result = await announce({ ...(site ? { site } : {}), reportMirror, siteMirror });\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,9 @@
1
+ import {
2
+ fetchAttachmentBytes,
3
+ uploadAttachment
4
+ } from "./chunk-IY6256GK.js";
5
+ export {
6
+ fetchAttachmentBytes,
7
+ uploadAttachment
8
+ };
9
+ //# sourceMappingURL=attachments-NLZIJ5NU.js.map
@@ -5,19 +5,19 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-ZMIPG6TR.js";
8
+ } from "./chunk-TVL5MVUF.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-7J53SUT6.js";
11
+ } from "./chunk-CROKUHJB.js";
12
12
  import {
13
13
  bumpDeps
14
- } from "./chunk-SDUJIANJ.js";
15
- import "./chunk-ZTPPCHDB.js";
16
- import "./chunk-FDIFTIXC.js";
17
- import "./chunk-4DHQCN5H.js";
18
- import "./chunk-XXTZBPUY.js";
14
+ } from "./chunk-2NJQLSEU.js";
15
+ import "./chunk-3G25KIWW.js";
16
+ import "./chunk-6TWWY7ZU.js";
17
+ import "./chunk-EVBCK2KC.js";
19
18
  import "./chunk-XTK5VIQB.js";
20
- import "./chunk-U5SZ4FOP.js";
19
+ import "./chunk-LBYOLBW7.js";
20
+ import "./chunk-XXTZBPUY.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-SWJB6H5V.js.map
60
+ //# sourceMappingURL=bump-deps-CGVELRLK.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-ZTPPCHDB.js";
3
+ } from "./chunk-3G25KIWW.js";
4
4
  import {
5
5
  defaultSpawn
6
- } from "./chunk-4DHQCN5H.js";
6
+ } from "./chunk-LBYOLBW7.js";
7
7
 
8
8
  // src/recipes/bump-deps.ts
9
9
  import { stat } from "fs/promises";
@@ -76,4 +76,4 @@ async function bumpDeps(site, opts = {}) {
76
76
  export {
77
77
  bumpDeps
78
78
  };
79
- //# sourceMappingURL=chunk-SDUJIANJ.js.map
79
+ //# sourceMappingURL=chunk-2NJQLSEU.js.map
@@ -0,0 +1,243 @@
1
+ import {
2
+ findFreePort,
3
+ withFreePort
4
+ } from "./chunk-PTDMVGKL.js";
5
+ import {
6
+ defaultSpawn
7
+ } from "./chunk-LBYOLBW7.js";
8
+ import {
9
+ siteLabel
10
+ } from "./chunk-XXTZBPUY.js";
11
+ import {
12
+ lighthouseConfig
13
+ } from "./chunk-NMAWXBJA.js";
14
+
15
+ // src/audits/lighthouse.ts
16
+ import { readFile as readFile2, writeFile, mkdtemp, rm, readdir } from "fs/promises";
17
+ import { tmpdir } from "os";
18
+ import { join as join2 } from "path";
19
+
20
+ // src/audits/util/site-config.ts
21
+ import { readFile } from "fs/promises";
22
+ import { join } from "path";
23
+ async function readSiteConfig(sitePath) {
24
+ let raw;
25
+ try {
26
+ raw = await readFile(join(sitePath, "package.json"), "utf-8");
27
+ } catch {
28
+ return {};
29
+ }
30
+ let pkg;
31
+ try {
32
+ pkg = JSON.parse(raw);
33
+ } catch {
34
+ return {};
35
+ }
36
+ if (!pkg || typeof pkg !== "object") return {};
37
+ const cfg = pkg.reddoor;
38
+ if (!cfg || typeof cfg !== "object") return {};
39
+ const out = {};
40
+ const url = cfg.lighthouseUrl;
41
+ if (typeof url === "string" && url.length > 0) {
42
+ out.lighthouseUrl = url;
43
+ }
44
+ const routes = cfg.a11yRoutes;
45
+ if (Array.isArray(routes)) {
46
+ const clean = routes.filter((r) => typeof r === "string").map((r) => r.trim()).filter((r) => r.length > 0);
47
+ if (clean.length > 0) out.a11yRoutes = clean;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // src/audits/lighthouse.ts
53
+ async function readJsonMaybe(path) {
54
+ try {
55
+ const raw = await readFile2(path, "utf-8");
56
+ return JSON.parse(raw);
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ async function readLhrEntries(resultsDir) {
62
+ const files = await readdir(resultsDir).catch(() => []);
63
+ const entries = [];
64
+ for (const f of files) {
65
+ if (!f.startsWith("lhr-") || !f.endsWith(".json")) continue;
66
+ const lhr = await readJsonMaybe(join2(resultsDir, f));
67
+ if (!lhr || !lhr.categories) continue;
68
+ const summary = {};
69
+ for (const [k, v] of Object.entries(lhr.categories)) {
70
+ if (typeof v?.score === "number") summary[k] = v.score;
71
+ }
72
+ entries.push({ url: lhr.requestedUrl, summary });
73
+ }
74
+ return entries;
75
+ }
76
+ function averageSummaries(entries) {
77
+ if (entries.length === 0) return {};
78
+ const sums = {};
79
+ const counts = {};
80
+ for (const e of entries) {
81
+ for (const [k, v] of Object.entries(e.summary ?? {})) {
82
+ if (typeof v !== "number") continue;
83
+ sums[k] = (sums[k] ?? 0) + v;
84
+ counts[k] = (counts[k] ?? 0) + 1;
85
+ }
86
+ }
87
+ const out = {};
88
+ for (const k of Object.keys(sums)) {
89
+ const total = sums[k] ?? 0;
90
+ const count = counts[k] ?? 1;
91
+ out[k] = total / count;
92
+ }
93
+ return out;
94
+ }
95
+ function categoryFromAssertion(a) {
96
+ const colonIdx = a.name.indexOf(":");
97
+ return colonIdx >= 0 ? a.name.slice(colonIdx + 1) : a.name;
98
+ }
99
+ function messageForAssertion(a) {
100
+ const actual = typeof a.actual === "number" ? a.actual.toFixed(2) : "n/a";
101
+ return `${a.name} ${a.operator} ${a.expected} (actual: ${actual})`;
102
+ }
103
+ async function parseLhciResults(resultsDir, label, raw) {
104
+ const manifest = await readLhrEntries(resultsDir);
105
+ if (manifest.length === 0) {
106
+ return {
107
+ audit: "lighthouse",
108
+ site: label,
109
+ status: "fail",
110
+ summary: `lighthouse: no lhr-*.json written (exit ${raw.code})${raw.stderr ? ` \u2014 ${raw.stderr.slice(0, 200)}` : ""}`
111
+ };
112
+ }
113
+ const assertionResults = await readJsonMaybe(join2(resultsDir, "assertion-results.json")) ?? [];
114
+ const failed = assertionResults.filter((a) => !a.passed);
115
+ const assertions = failed.map((a) => ({
116
+ category: categoryFromAssertion(a),
117
+ level: a.level,
118
+ message: messageForAssertion(a)
119
+ }));
120
+ const anyError = assertions.some((a) => a.level === "error");
121
+ const anyWarn = assertions.some((a) => a.level === "warn");
122
+ const status = anyError ? "fail" : anyWarn ? "warn" : "pass";
123
+ const normalized = {
124
+ summary: averageSummaries(manifest),
125
+ assertionsFailed: failed.length,
126
+ assertions
127
+ };
128
+ const summary = status === "pass" ? "lighthouse: all categories passing" : `lighthouse: ${failed.length} assertion(s) failed`;
129
+ return { audit: "lighthouse", site: label, status, summary, details: normalized };
130
+ }
131
+ async function checkoutLighthouse(spawn, site, label) {
132
+ const siteCfg = await readSiteConfig(site.path);
133
+ const port = await findFreePort();
134
+ const baseUrl = siteCfg.lighthouseUrl ?? lighthouseConfig.ci.collect.url[0];
135
+ const resolvedConfig = {
136
+ ...lighthouseConfig,
137
+ ci: {
138
+ ...lighthouseConfig.ci,
139
+ collect: {
140
+ ...lighthouseConfig.ci.collect,
141
+ url: [withFreePort(baseUrl, port)],
142
+ startServerCommand: `npm run vite:dev -- --port ${port} --strictPort`
143
+ }
144
+ }
145
+ };
146
+ const configDir = await mkdtemp(join2(tmpdir(), "reddoor-lhci-"));
147
+ const configPath = join2(configDir, "lighthouserc.json");
148
+ await writeFile(configPath, JSON.stringify(resolvedConfig), "utf-8");
149
+ const resultsDir = join2(site.path, ".lighthouseci");
150
+ await rm(resultsDir, { recursive: true, force: true });
151
+ let raw;
152
+ try {
153
+ raw = await spawn("npx", ["--yes", "@lhci/cli", "autorun", `--config=${configPath}`], {
154
+ cwd: site.path,
155
+ timeoutMs: 5 * 6e4
156
+ });
157
+ } catch (err) {
158
+ await rm(configDir, { recursive: true, force: true });
159
+ const e = err;
160
+ if (e.code === "ENOENT" || /ENOENT/.test(String(err))) {
161
+ return {
162
+ audit: "lighthouse",
163
+ site: label,
164
+ status: "skip",
165
+ summary: "npx/@lhci/cli not available"
166
+ };
167
+ }
168
+ throw err;
169
+ }
170
+ await rm(configDir, { recursive: true, force: true });
171
+ return parseLhciResults(resultsDir, label, raw);
172
+ }
173
+ async function deployedLighthouse(spawn, deployedUrl, label) {
174
+ const workDir = await mkdtemp(join2(tmpdir(), "reddoor-lh-deployed-"));
175
+ const resolvedConfig = {
176
+ ci: {
177
+ // Deliberately NOT spread from lighthouseConfig.ci.collect: deployed mode
178
+ // must omit startServerCommand and the dev-server settings entirely.
179
+ collect: {
180
+ url: [deployedUrl],
181
+ // 3 runs to damp Lighthouse's run-to-run variance; parseLhciResults
182
+ // averages the lhr files. (Median is a tracked future refinement.)
183
+ numberOfRuns: 3,
184
+ // Use devtools (applied) throttling instead of the desktop preset's
185
+ // default `simulate` (Lantern). Lantern estimates LCP from the
186
+ // dependency graph and can't model an LCP element revealed by a
187
+ // JS-load opacity fade (e.g. gallerysonder's GridImage): on a fast
188
+ // observed load it finds no LCP node and throws NO_LCP, nulling the
189
+ // perf score, which the cockpit then coerces to a misleading 0.
190
+ // devtools reads the real LCP paint event from the throttled trace
191
+ // instead. Trade-off: slightly noisier run-to-run, damped by the
192
+ // numberOfRuns:3 average above.
193
+ settings: { preset: "desktop", throttlingMethod: "devtools", skipAudits: ["uses-http2"] }
194
+ },
195
+ assert: lighthouseConfig.ci.assert,
196
+ upload: { target: "filesystem", outputDir: join2(workDir, "lhci-report") }
197
+ }
198
+ };
199
+ const configPath = join2(workDir, "lighthouserc.json");
200
+ await writeFile(configPath, JSON.stringify(resolvedConfig), "utf-8");
201
+ const resultsDir = join2(workDir, ".lighthouseci");
202
+ let raw;
203
+ try {
204
+ raw = await spawn("npx", ["--yes", "@lhci/cli", "autorun", `--config=${configPath}`], {
205
+ cwd: workDir,
206
+ // 3 serial cold runs of a slow deployed site (lhci's own maxWaitForLoad
207
+ // ~45-60s each) + first-use Chrome download can plausibly exceed 3 min →
208
+ // SIGTERM → no lhr-*.json → spurious "no scores". Match the 5-min budget
209
+ // the checkout path already gives (erp-industrials nightly flake,
210
+ // morning-brief 2026-06-10 MEDIUM-F).
211
+ timeoutMs: 5 * 6e4
212
+ });
213
+ } catch (err) {
214
+ await rm(workDir, { recursive: true, force: true });
215
+ const e = err;
216
+ if (e.code === "ENOENT" || /ENOENT/.test(String(err))) {
217
+ return {
218
+ audit: "lighthouse",
219
+ site: label,
220
+ status: "skip",
221
+ summary: "npx/@lhci/cli not available"
222
+ };
223
+ }
224
+ throw err;
225
+ }
226
+ try {
227
+ return await parseLhciResults(resultsDir, label, raw);
228
+ } finally {
229
+ await rm(workDir, { recursive: true, force: true });
230
+ }
231
+ }
232
+ async function lighthouseAudit(ctx) {
233
+ const spawn = ctx.spawn ?? defaultSpawn;
234
+ const site = ctx.site;
235
+ const label = siteLabel(site);
236
+ return site.deployedUrl ? deployedLighthouse(spawn, site.deployedUrl, label) : checkoutLighthouse(spawn, site, label);
237
+ }
238
+
239
+ export {
240
+ readSiteConfig,
241
+ lighthouseAudit
242
+ };
243
+ //# sourceMappingURL=chunk-2NQUCS7O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/audits/lighthouse.ts","../src/audits/util/site-config.ts"],"sourcesContent":["import { readFile, writeFile, mkdtemp, rm, readdir } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AuditResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { lighthouseConfig } from \"../configs/lighthouse.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { SpawnFn, SpawnResult } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { readSiteConfig } from \"./util/site-config.js\";\nimport { findFreePort, withFreePort } from \"../util/free-port.js\";\n\ntype ManifestEntry = {\n url: string;\n summary: Record<string, number>;\n htmlPath?: string;\n jsonPath?: string;\n};\n\ntype AssertionResult = {\n name: string;\n actual: number;\n expected: number;\n operator: string;\n passed: boolean;\n level: \"warn\" | \"error\";\n auditProperty?: string;\n auditId?: string;\n};\n\ntype NormalizedLhciResult = {\n summary: Record<string, number>;\n assertionsFailed: number;\n assertions: Array<{ category: string; level: \"warn\" | \"error\"; message: string }>;\n};\n\nasync function readJsonMaybe<T>(path: string): Promise<T | null> {\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\ntype LhrFile = {\n requestedUrl: string;\n finalUrl?: string;\n categories: Record<string, { score: number | null }>;\n};\n\n/**\n * Build manifest-equivalent entries by scanning the `.lighthouseci/` dir\n * for `lhr-*.json` files written by `lhci collect`. We used to read\n * `manifest.json` directly, but lhci 0.15+ no longer writes it — the\n * audit would silently return \"no manifest written\" against a perfectly\n * healthy run. Reproduced on caltex 2026-05-28 (0.10.5 dogfood).\n */\nasync function readLhrEntries(resultsDir: string): Promise<ManifestEntry[]> {\n const files = await readdir(resultsDir).catch(() => [] as string[]);\n const entries: ManifestEntry[] = [];\n for (const f of files) {\n if (!f.startsWith(\"lhr-\") || !f.endsWith(\".json\")) continue;\n const lhr = await readJsonMaybe<LhrFile>(join(resultsDir, f));\n if (!lhr || !lhr.categories) continue;\n const summary: Record<string, number> = {};\n for (const [k, v] of Object.entries(lhr.categories)) {\n if (typeof v?.score === \"number\") summary[k] = v.score;\n }\n entries.push({ url: lhr.requestedUrl, summary });\n }\n return entries;\n}\n\nfunction averageSummaries(entries: ManifestEntry[]): Record<string, number> {\n if (entries.length === 0) return {};\n const sums: Record<string, number> = {};\n const counts: Record<string, number> = {};\n for (const e of entries) {\n for (const [k, v] of Object.entries(e.summary ?? {})) {\n if (typeof v !== \"number\") continue;\n sums[k] = (sums[k] ?? 0) + v;\n counts[k] = (counts[k] ?? 0) + 1;\n }\n }\n const out: Record<string, number> = {};\n for (const k of Object.keys(sums)) {\n const total = sums[k] ?? 0;\n const count = counts[k] ?? 1;\n out[k] = total / count;\n }\n return out;\n}\n\nfunction categoryFromAssertion(a: AssertionResult): string {\n // `name` looks like \"categories:accessibility\" or \"audits:uses-http2\".\n const colonIdx = a.name.indexOf(\":\");\n return colonIdx >= 0 ? a.name.slice(colonIdx + 1) : a.name;\n}\n\nfunction messageForAssertion(a: AssertionResult): string {\n // `a.actual` is parsed from external lhci/Lighthouse JSON; a malformed or\n // missing value (not a number) would make `.toFixed` throw and crash the whole\n // audit. Guard it and fall back to a readable string instead.\n const actual = typeof a.actual === \"number\" ? a.actual.toFixed(2) : \"n/a\";\n return `${a.name} ${a.operator} ${a.expected} (actual: ${actual})`;\n}\n\n/** Shared tail: scan `.lighthouseci/` for lhr-*.json + assertion-results.json and\n * build the AuditResult. Identical for the checkout and deployed paths. */\nasync function parseLhciResults(\n resultsDir: string,\n label: string,\n raw: SpawnResult,\n): Promise<AuditResult> {\n const manifest = await readLhrEntries(resultsDir);\n\n if (manifest.length === 0) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"fail\",\n summary: `lighthouse: no lhr-*.json written (exit ${raw.code})${\n raw.stderr ? ` — ${raw.stderr.slice(0, 200)}` : \"\"\n }`,\n };\n }\n\n const assertionResults =\n (await readJsonMaybe<AssertionResult[]>(join(resultsDir, \"assertion-results.json\"))) ?? [];\n\n const failed = assertionResults.filter((a) => !a.passed);\n const assertions = failed.map((a) => ({\n category: categoryFromAssertion(a),\n level: a.level,\n message: messageForAssertion(a),\n }));\n\n const anyError = assertions.some((a) => a.level === \"error\");\n const anyWarn = assertions.some((a) => a.level === \"warn\");\n const status: AuditResult[\"status\"] = anyError ? \"fail\" : anyWarn ? \"warn\" : \"pass\";\n\n const normalized: NormalizedLhciResult = {\n summary: averageSummaries(manifest),\n assertionsFailed: failed.length,\n assertions,\n };\n\n const summary =\n status === \"pass\"\n ? \"lighthouse: all categories passing\"\n : `lighthouse: ${failed.length} assertion(s) failed`;\n\n return { audit: \"lighthouse\", site: label, status, summary, details: normalized };\n}\n\n/** Checkout mode (unchanged behavior): boot the site's vite dev server on a\n * pinned free port and audit the local fixtures/override URL. */\nasync function checkoutLighthouse(spawn: SpawnFn, site: Site, label: string): Promise<AuditResult> {\n const siteCfg = await readSiteConfig(site.path);\n // Allocate a free port + force vite to `--strictPort` so the spawned dev\n // server either binds the port we picked or fails loudly (caltex 2026-05-28\n // zombie-vite incident).\n const port = await findFreePort();\n const baseUrl = siteCfg.lighthouseUrl ?? lighthouseConfig.ci.collect.url[0];\n const resolvedConfig = {\n ...lighthouseConfig,\n ci: {\n ...lighthouseConfig.ci,\n collect: {\n ...lighthouseConfig.ci.collect,\n url: [withFreePort(baseUrl, port)],\n startServerCommand: `npm run vite:dev -- --port ${port} --strictPort`,\n },\n },\n };\n\n const configDir = await mkdtemp(join(tmpdir(), \"reddoor-lhci-\"));\n const configPath = join(configDir, \"lighthouserc.json\");\n await writeFile(configPath, JSON.stringify(resolvedConfig), \"utf-8\");\n\n const resultsDir = join(site.path, \".lighthouseci\");\n await rm(resultsDir, { recursive: true, force: true });\n\n let raw: SpawnResult;\n try {\n raw = await spawn(\"npx\", [\"--yes\", \"@lhci/cli\", \"autorun\", `--config=${configPath}`], {\n cwd: site.path,\n timeoutMs: 5 * 60_000,\n });\n } catch (err) {\n await rm(configDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"skip\",\n summary: \"npx/@lhci/cli not available\",\n };\n }\n throw err;\n }\n await rm(configDir, { recursive: true, force: true });\n\n return parseLhciResults(resultsDir, label, raw);\n}\n\n/** Deployed mode: audit a production URL directly — no checkout, no dev server.\n * Runs in a throwaway tmp cwd; uploads to the filesystem so fleet runs never\n * push 200 public reports to temporary-public-storage. */\nasync function deployedLighthouse(\n spawn: SpawnFn,\n deployedUrl: string,\n label: string,\n): Promise<AuditResult> {\n const workDir = await mkdtemp(join(tmpdir(), \"reddoor-lh-deployed-\"));\n const resolvedConfig = {\n ci: {\n // Deliberately NOT spread from lighthouseConfig.ci.collect: deployed mode\n // must omit startServerCommand and the dev-server settings entirely.\n collect: {\n url: [deployedUrl],\n // 3 runs to damp Lighthouse's run-to-run variance; parseLhciResults\n // averages the lhr files. (Median is a tracked future refinement.)\n numberOfRuns: 3,\n // Use devtools (applied) throttling instead of the desktop preset's\n // default `simulate` (Lantern). Lantern estimates LCP from the\n // dependency graph and can't model an LCP element revealed by a\n // JS-load opacity fade (e.g. gallerysonder's GridImage): on a fast\n // observed load it finds no LCP node and throws NO_LCP, nulling the\n // perf score, which the cockpit then coerces to a misleading 0.\n // devtools reads the real LCP paint event from the throttled trace\n // instead. Trade-off: slightly noisier run-to-run, damped by the\n // numberOfRuns:3 average above.\n settings: { preset: \"desktop\", throttlingMethod: \"devtools\", skipAudits: [\"uses-http2\"] },\n },\n assert: lighthouseConfig.ci.assert,\n upload: { target: \"filesystem\", outputDir: join(workDir, \"lhci-report\") },\n },\n };\n\n const configPath = join(workDir, \"lighthouserc.json\");\n await writeFile(configPath, JSON.stringify(resolvedConfig), \"utf-8\");\n\n const resultsDir = join(workDir, \".lighthouseci\");\n\n let raw: SpawnResult;\n try {\n raw = await spawn(\"npx\", [\"--yes\", \"@lhci/cli\", \"autorun\", `--config=${configPath}`], {\n cwd: workDir,\n // 3 serial cold runs of a slow deployed site (lhci's own maxWaitForLoad\n // ~45-60s each) + first-use Chrome download can plausibly exceed 3 min →\n // SIGTERM → no lhr-*.json → spurious \"no scores\". Match the 5-min budget\n // the checkout path already gives (erp-industrials nightly flake,\n // morning-brief 2026-06-10 MEDIUM-F).\n timeoutMs: 5 * 60_000,\n });\n } catch (err) {\n await rm(workDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"skip\",\n summary: \"npx/@lhci/cli not available\",\n };\n }\n throw err;\n }\n\n try {\n return await parseLhciResults(resultsDir, label, raw);\n } finally {\n await rm(workDir, { recursive: true, force: true });\n }\n}\n\nexport async function lighthouseAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n return site.deployedUrl\n ? deployedLighthouse(spawn, site.deployedUrl, label)\n : checkoutLighthouse(spawn, site, label);\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type SiteConfig = {\n /** Override URL the lighthouse audit hits. Sites without the default\n * `/dev/a11y-fixtures` dev route set this to their homepage. */\n lighthouseUrl?: string;\n /**\n * Real site routes the a11y audit should axe-scan, IN ADDITION to the two\n * synthetic fixture pages it always covers. Opt-in per site and absent by\n * default: the audit runs with `--fail-on-violations` in the shared CI\n * workflow, and most of the fleet carries pre-existing a11y debt, so scanning\n * real routes everywhere at once would red every repo. A site adopts this once\n * its routes are clean. Omitted (never `[]`) when unset or unusable.\n */\n a11yRoutes?: string[];\n};\n\n/**\n * Read per-site overrides from `package.json#reddoor`. Returns `{}` on any\n * failure (missing file, malformed JSON, missing key, wrong type) so every\n * caller can safely fall back to its built-in default. Never throws.\n */\nexport async function readSiteConfig(sitePath: string): Promise<SiteConfig> {\n let raw: string;\n try {\n raw = await readFile(join(sitePath, \"package.json\"), \"utf-8\");\n } catch {\n return {};\n }\n let pkg: unknown;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return {};\n }\n if (!pkg || typeof pkg !== \"object\") return {};\n const cfg = (pkg as { reddoor?: unknown }).reddoor;\n if (!cfg || typeof cfg !== \"object\") return {};\n\n const out: SiteConfig = {};\n const url = (cfg as { lighthouseUrl?: unknown }).lighthouseUrl;\n if (typeof url === \"string\" && url.length > 0) {\n out.lighthouseUrl = url;\n }\n const routes = (cfg as { a11yRoutes?: unknown }).a11yRoutes;\n if (Array.isArray(routes)) {\n // Junk entries are dropped rather than passed through — a `null` in the list\n // would become `page.goto(null)` inside the generated spec and fail the whole\n // audit for a typo. An all-junk list leaves the key omitted, so the caller's\n // \"no routes configured\" branch is the same shape as \"key absent\".\n const clean = routes\n .filter((r): r is string => typeof r === \"string\")\n .map((r) => r.trim())\n .filter((r) => r.length > 0);\n if (clean.length > 0) out.a11yRoutes = clean;\n }\n return out;\n}\n"],"mappings":"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","names":["readFile","join","readFile","join"]}
@@ -1,6 +1,3 @@
1
- import {
2
- siteLabel
3
- } from "./chunk-XXTZBPUY.js";
4
1
  import {
5
2
  branchName,
6
3
  checkoutBranch,
@@ -11,6 +8,9 @@ import {
11
8
  forceCheckoutBranch,
12
9
  isWorkingTreeClean
13
10
  } from "./chunk-XTK5VIQB.js";
11
+ import {
12
+ siteLabel
13
+ } from "./chunk-XXTZBPUY.js";
14
14
 
15
15
  // src/recipes/_with-recipe.ts
16
16
  async function withRecipe(body) {
@@ -118,4 +118,4 @@ async function withRecipe(body) {
118
118
  export {
119
119
  withRecipe
120
120
  };
121
- //# sourceMappingURL=chunk-ZTPPCHDB.js.map
121
+ //# sourceMappingURL=chunk-3G25KIWW.js.map