@reddoorla/maintenance 0.95.1 → 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 (316) hide show
  1. package/README.md +41 -7
  2. package/dist/{announce-CLQYX5I4.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-4KNXUP2K.js → blux-NXQLMS4W.js} +1 -1
  6. package/dist/{bump-deps-C5G2EFBB.js → bump-deps-775VLDYB.js} +6 -6
  7. package/dist/{chunk-FUWFLAWQ.js → chunk-3RLDX3Q2.js} +5 -5
  8. package/dist/{chunk-XHHMM5HX.js → chunk-43SW3FGW.js} +63 -1
  9. package/dist/chunk-43SW3FGW.js.map +1 -0
  10. package/dist/{chunk-R53VL3RI.js → chunk-5CJVUPJU.js} +1 -1
  11. package/dist/{chunk-R53VL3RI.js.map → chunk-5CJVUPJU.js.map} +1 -1
  12. package/dist/{chunk-S7RYFUIK.js → chunk-6PGXKN6F.js} +2 -2
  13. package/dist/{chunk-HTABUCF4.js → chunk-6THEZQLA.js} +3 -3
  14. package/dist/chunk-A5QKNOXW.js +72 -0
  15. package/dist/chunk-A5QKNOXW.js.map +1 -0
  16. package/dist/chunk-ASLDCWBF.js +41 -0
  17. package/dist/chunk-ASLDCWBF.js.map +1 -0
  18. package/dist/{chunk-CWXTNPNZ.js → chunk-AYDQQBNG.js} +3 -3
  19. package/dist/chunk-AYDQQBNG.js.map +1 -0
  20. package/dist/{chunk-WVNDEYXL.js → chunk-BINFZQ2T.js} +57 -6
  21. package/dist/chunk-BINFZQ2T.js.map +1 -0
  22. package/dist/{chunk-2NQUCS7O.js → chunk-BIYQN7FH.js} +3 -1
  23. package/dist/{chunk-2NQUCS7O.js.map → chunk-BIYQN7FH.js.map} +1 -1
  24. package/dist/{chunk-YLYXW5PH.js → chunk-CXICCLNM.js} +34 -4
  25. package/dist/chunk-CXICCLNM.js.map +1 -0
  26. package/dist/{chunk-BYGPUUCT.js → chunk-DRNZVORX.js} +66 -30
  27. package/dist/chunk-DRNZVORX.js.map +1 -0
  28. package/dist/{chunk-LM2NHRCJ.js → chunk-DYTYPKFN.js} +4 -4
  29. package/dist/chunk-DYTYPKFN.js.map +1 -0
  30. package/dist/{chunk-DFKBTBO3.js → chunk-EN6CTBUN.js} +35 -6
  31. package/dist/chunk-EN6CTBUN.js.map +1 -0
  32. package/dist/{chunk-5EEMTJMT.js → chunk-FHFXJ3EM.js} +8 -6
  33. package/dist/chunk-FHFXJ3EM.js.map +1 -0
  34. package/dist/{chunk-Z2K4JPQ3.js → chunk-FP6HOW6J.js} +10 -7
  35. package/dist/chunk-FP6HOW6J.js.map +1 -0
  36. package/dist/{chunk-2LYOAB2E.js → chunk-HSMN35LU.js} +2 -2
  37. package/dist/chunk-ICSRWW5Q.js +148 -0
  38. package/dist/chunk-ICSRWW5Q.js.map +1 -0
  39. package/dist/{chunk-FSGICFXB.js → chunk-IDBPII6G.js} +17 -5
  40. package/dist/chunk-IDBPII6G.js.map +1 -0
  41. package/dist/{chunk-C3IQQKXL.js → chunk-IOTOOTII.js} +121 -1
  42. package/dist/{chunk-C3IQQKXL.js.map → chunk-IOTOOTII.js.map} +1 -1
  43. package/dist/{chunk-BCKEP5S6.js → chunk-JHJNNJLG.js} +2 -2
  44. package/dist/{chunk-TO7DJYLZ.js → chunk-L3S3QZXL.js} +178 -39
  45. package/dist/chunk-L3S3QZXL.js.map +1 -0
  46. package/dist/{chunk-WSJ7RO54.js → chunk-LAG6IP7Y.js} +23 -5
  47. package/dist/chunk-LAG6IP7Y.js.map +1 -0
  48. package/dist/{chunk-WJMPCVOR.js → chunk-LAJ3FF54.js} +12 -4
  49. package/dist/chunk-LAJ3FF54.js.map +1 -0
  50. package/dist/{chunk-2ZAZ7ETT.js → chunk-LDHRIQUW.js} +32 -18
  51. package/dist/chunk-LDHRIQUW.js.map +1 -0
  52. package/dist/{chunk-6R56OKJZ.js → chunk-LF2TJN4X.js} +2 -2
  53. package/dist/chunk-LF2TJN4X.js.map +1 -0
  54. package/dist/{chunk-ZQV7UQNE.js → chunk-LUF52EIY.js} +2 -2
  55. package/dist/{chunk-5UINJNAJ.js → chunk-LZMAG73U.js} +34 -21
  56. package/dist/chunk-LZMAG73U.js.map +1 -0
  57. package/dist/{chunk-VSGMCCGQ.js → chunk-MMSMZC7S.js} +2 -2
  58. package/dist/chunk-MVKNU4QD.js +37 -0
  59. package/dist/chunk-MVKNU4QD.js.map +1 -0
  60. package/dist/{chunk-XPJQXSC4.js → chunk-P7323YVW.js} +27 -5
  61. package/dist/chunk-P7323YVW.js.map +1 -0
  62. package/dist/{chunk-KJRVMMM3.js → chunk-PARKXDXI.js} +36 -5
  63. package/dist/chunk-PARKXDXI.js.map +1 -0
  64. package/dist/{chunk-4763KQ6O.js → chunk-PXRN2MER.js} +2 -2
  65. package/dist/{chunk-WMBLR5VA.js → chunk-PZZ6HM5T.js} +3 -3
  66. package/dist/{chunk-TR3HFH6J.js → chunk-QFSL2L2B.js} +67 -10
  67. package/dist/chunk-QFSL2L2B.js.map +1 -0
  68. package/dist/chunk-RQJFGK3A.js +51 -0
  69. package/dist/chunk-RQJFGK3A.js.map +1 -0
  70. package/dist/{chunk-3Z6X3BHU.js → chunk-TC4GANMX.js} +53 -150
  71. package/dist/chunk-TC4GANMX.js.map +1 -0
  72. package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
  73. package/dist/chunk-TEMQPSCM.js.map +1 -0
  74. package/dist/chunk-TJLKWQOX.js +52 -0
  75. package/dist/chunk-TJLKWQOX.js.map +1 -0
  76. package/dist/{chunk-CJ2RMTT7.js → chunk-TV76A6GN.js} +2 -2
  77. package/dist/{chunk-KZRR4NFG.js → chunk-TVZ6QCOE.js} +3 -3
  78. package/dist/{chunk-BVEDKMNG.js → chunk-U2ZI6G7H.js} +29 -11
  79. package/dist/chunk-U2ZI6G7H.js.map +1 -0
  80. package/dist/chunk-UPE6D4AT.js +69 -0
  81. package/dist/chunk-UPE6D4AT.js.map +1 -0
  82. package/dist/{chunk-R5IMFAFR.js → chunk-VMGHQUTV.js} +7 -16
  83. package/dist/chunk-VMGHQUTV.js.map +1 -0
  84. package/dist/chunk-VQMAAQ5M.js +53 -0
  85. package/dist/chunk-VQMAAQ5M.js.map +1 -0
  86. package/dist/{chunk-AVEQUR44.js → chunk-WMF5S2MB.js} +60 -17
  87. package/dist/chunk-WMF5S2MB.js.map +1 -0
  88. package/dist/{chunk-SVAAONYB.js → chunk-WQLNUEZL.js} +12 -2
  89. package/dist/{chunk-SVAAONYB.js.map → chunk-WQLNUEZL.js.map} +1 -1
  90. package/dist/{chunk-XN37VTDB.js → chunk-X56LAKPR.js} +3 -3
  91. package/dist/chunk-X56LAKPR.js.map +1 -0
  92. package/dist/{chunk-ZCL7C5LH.js → chunk-Y6FCXRIC.js} +1661 -387
  93. package/dist/chunk-Y6FCXRIC.js.map +1 -0
  94. package/dist/{chunk-2SLED3NB.js → chunk-YDZ7XGPZ.js} +1 -1
  95. package/dist/{chunk-2SLED3NB.js.map → chunk-YDZ7XGPZ.js.map} +1 -1
  96. package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
  97. package/dist/chunk-YJBSB4LP.js.map +1 -0
  98. package/dist/{chunk-BI2VI6EM.js → chunk-YXWBM26P.js} +2 -2
  99. package/dist/{chunk-QF6IQQFP.js → chunk-Z7FDJ35L.js} +18 -12
  100. package/dist/chunk-Z7FDJ35L.js.map +1 -0
  101. package/dist/cli/bin.js +111 -51
  102. package/dist/cli/bin.js.map +1 -1
  103. package/dist/cli/commands/audit.d.ts +33 -3
  104. package/dist/cli/commands/audit.js +28 -25
  105. package/dist/cli/commands/audit.js.map +1 -1
  106. package/dist/client-DGTO65WB.js +11 -0
  107. package/dist/configs/playwright-a11y.d.ts +11 -1
  108. package/dist/configs/playwright-a11y.js +3 -1
  109. package/dist/{convert-to-pnpm-YTFREUJK.js → convert-to-pnpm-KLWA53N5.js} +6 -6
  110. package/dist/{db-XKZYDCKM.js → db-II5WAK4D.js} +45 -20
  111. package/dist/db-II5WAK4D.js.map +1 -0
  112. package/dist/deadletter-G4M3HSVM.js +17 -0
  113. package/dist/{digest-7UBGUBRK.js → digest-CWKK2DMR.js} +56 -32
  114. package/dist/digest-CWKK2DMR.js.map +1 -0
  115. package/dist/{digest-collectors-DJFN5ZDK.js → digest-collectors-CYTN2EUX.js} +17 -12
  116. package/dist/{digest-state-3KHZF7GD.js → digest-state-ZDPKR7MW.js} +18 -2
  117. package/dist/digest-state-ZDPKR7MW.js.map +1 -0
  118. package/dist/ensure-site-2QZHFVQ3.js +70 -0
  119. package/dist/ensure-site-2QZHFVQ3.js.map +1 -0
  120. package/dist/ensure-site-JEKV2ODE.js +105 -0
  121. package/dist/ensure-site-JEKV2ODE.js.map +1 -0
  122. package/dist/{ensure-site-OAAE5YE2.js → ensure-site-OO4UPWNQ.js} +49 -44
  123. package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
  124. package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
  125. package/dist/{fleet-state-UEVZ3X3J.js → fleet-state-RJROYXKO.js} +19 -7
  126. package/dist/{forms-notify-target-UC6PF3AE.js → forms-notify-target-X6UEOI2M.js} +19 -15
  127. package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
  128. package/dist/{freeze-A6MUCZU4.js → freeze-GUQ43EAU.js} +2 -2
  129. package/dist/{github-signals-TX5FPYP6.js → github-signals-QIRTHHET.js} +22 -17
  130. package/dist/github-signals-QIRTHHET.js.map +1 -0
  131. package/dist/{header-image-PSPT7CUM.js → header-image-H7ZJAV26.js} +15 -18
  132. package/dist/header-image-H7ZJAV26.js.map +1 -0
  133. package/dist/{health-endpoint-KVCJYWET.js → health-endpoint-BJ6BKRHF.js} +8 -7
  134. package/dist/{health-endpoint-KVCJYWET.js.map → health-endpoint-BJ6BKRHF.js.map} +1 -1
  135. package/dist/{health-mirror-XJAKA3EY.js → health-mirror-KU3G7V2Y.js} +10 -10
  136. package/dist/{import-airtable-TAKSN4NV.js → import-airtable-UBF5GYAL.js} +3 -3
  137. package/dist/index.d.ts +158 -419
  138. package/dist/index.js +91 -46
  139. package/dist/index.js.map +1 -1
  140. package/dist/{init-FFE4FCO2.js → init-GOWDA5QS.js} +19 -18
  141. package/dist/{init-FFE4FCO2.js.map → init-GOWDA5QS.js.map} +1 -1
  142. package/dist/{launch-UD36AHUN.js → launch-7N3X2MXB.js} +229 -79
  143. package/dist/launch-7N3X2MXB.js.map +1 -0
  144. package/dist/{lighthouse-VHI77PFB.js → lighthouse-52WSPZ5D.js} +2 -2
  145. package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
  146. package/dist/match-harness-GBCDSVPS.js +417 -0
  147. package/dist/match-harness-GBCDSVPS.js.map +1 -0
  148. package/dist/migrate-XZ7U5DFC.js +7 -0
  149. package/dist/{notify-IISTHNBR.js → notify-K3WVH3DM.js} +4 -4
  150. package/dist/{onboard-TM2KESGF.js → onboard-JQQIMQW7.js} +6 -6
  151. package/dist/{orchestrate-HYVQPRCE.js → orchestrate-L2A5BZFY.js} +13 -10
  152. package/dist/parity-2JGVUCZD.js +14 -0
  153. package/dist/{pipeline-YROAY6DT.js → pipeline-UUPXOS2J.js} +3 -3
  154. package/dist/{preflight-RYIP54NY.js → preflight-O7Q6FWHU.js} +35 -11
  155. package/dist/preflight-O7Q6FWHU.js.map +1 -0
  156. package/dist/{prismic-ci-6GKKZGB5.js → prismic-ci-NAPWOXXD.js} +39 -9
  157. package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
  158. package/dist/{prismic-models-P6FWWVRS.js → prismic-models-LL6ORTQ2.js} +20 -17
  159. package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
  160. package/dist/prospect/types.d.ts +72 -0
  161. package/dist/prospect/types.js +1 -1
  162. package/dist/{prospect-audit-PRHU53HS.js → prospect-audit-FMPGQIRQ.js} +16 -7
  163. package/dist/prospect-audit-FMPGQIRQ.js.map +1 -0
  164. package/dist/{prospect-audits-R3LWF6U7.js → prospect-audits-IZVQY3OQ.js} +4 -2
  165. package/dist/protection-audit-YXJKFU6O.js +413 -0
  166. package/dist/protection-audit-YXJKFU6O.js.map +1 -0
  167. package/dist/recipes/sync-configs.js +3 -3
  168. package/dist/{render-YGNGTX3W.js → render-MHUUHPLN.js} +36 -9
  169. package/dist/render-MHUUHPLN.js.map +1 -0
  170. package/dist/{render-from-row-IFSBHWDH.js → render-from-row-75JTED2Z.js} +6 -4
  171. package/dist/{renovate-dispatch-U5Q52SKT.js → renovate-dispatch-2IHAZDVA.js} +14 -9
  172. package/dist/{renovate-dispatch-U5Q52SKT.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
  173. package/dist/{replay-PCSQOGCZ.js → replay-4FVFOMHH.js} +10 -23
  174. package/dist/replay-4FVFOMHH.js.map +1 -0
  175. package/dist/{report-LQS4ECTM.js → report-IAHXN5CW.js} +103 -57
  176. package/dist/report-IAHXN5CW.js.map +1 -0
  177. package/dist/{report-mirror-HDTOJE64.js → report-mirror-4QM57LMS.js} +34 -12
  178. package/dist/report-mirror-4QM57LMS.js.map +1 -0
  179. package/dist/roster-GDNSIRAK.js +19 -0
  180. package/dist/roster-GDNSIRAK.js.map +1 -0
  181. package/dist/{schema-A4WCPIF4.js → schema-KWUQEZ5O.js} +1 -1
  182. package/dist/schema-KWUQEZ5O.js.map +1 -0
  183. package/dist/{self-updating-MWXIZLL7.js → self-updating-ZUWPGYUP.js} +8 -7
  184. package/dist/{self-updating-MWXIZLL7.js.map → self-updating-ZUWPGYUP.js.map} +1 -1
  185. package/dist/{selftest-VXTHRPRN.js → selftest-G4W3LIBQ.js} +21 -17
  186. package/dist/{selftest-VXTHRPRN.js.map → selftest-G4W3LIBQ.js.map} +1 -1
  187. package/dist/site-create-ZU3ACBTO.js +45 -0
  188. package/dist/site-create-ZU3ACBTO.js.map +1 -0
  189. package/dist/site-lookup-DTOLJ4EA.js +27 -0
  190. package/dist/site-lookup-DTOLJ4EA.js.map +1 -0
  191. package/dist/{site-mirror-WON4FTJ2.js → site-mirror-WR7ACXOV.js} +26 -12
  192. package/dist/site-mirror-WR7ACXOV.js.map +1 -0
  193. package/dist/{smoke-suite-7DGITCTZ.js → smoke-suite-WAUPI6EJ.js} +8 -7
  194. package/dist/{smoke-suite-7DGITCTZ.js.map → smoke-suite-WAUPI6EJ.js.map} +1 -1
  195. package/dist/{submissions-OVZPVBYG.js → submissions-2SHCFOKD.js} +12 -7
  196. package/dist/submissions-2SHCFOKD.js.map +1 -0
  197. package/dist/{submissions-3I3GAQAX.js → submissions-JDZP35JM.js} +49 -9
  198. package/dist/submissions-JDZP35JM.js.map +1 -0
  199. package/dist/{svelte-codemods-WSV2FGNG.js → svelte-codemods-VOLOKKFI.js} +6 -6
  200. package/dist/{sync-K3PKHVLH.js → sync-F4USL4K5.js} +6 -6
  201. package/dist/{sync-configs-RRIHNLIJ.js → sync-configs-OXSWKVZ2.js} +6 -6
  202. package/dist/turso-KQKBBQCK.js +24 -0
  203. package/dist/turso-KQKBBQCK.js.map +1 -0
  204. package/dist/{upgrade-VTD6V4HS.js → upgrade-EVAL3JTO.js} +6 -6
  205. package/dist/util/git.d.ts +49 -10
  206. package/dist/util/git.js +1 -1
  207. package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
  208. package/dist/{websites-TB4TCAWM.js → websites-Y6FN2XFD.js} +27 -20
  209. package/dist/{write-audits-to-airtable-53EVXKMU.js → write-audits-to-airtable-TI3JMWSY.js} +7 -5
  210. package/package.json +3 -2
  211. package/dist/airtable-JGCPS7JH.js +0 -9
  212. package/dist/announce-CLQYX5I4.js.map +0 -1
  213. package/dist/attachments-NLZIJ5NU.js +0 -9
  214. package/dist/chunk-2ZAZ7ETT.js.map +0 -1
  215. package/dist/chunk-3Z6X3BHU.js.map +0 -1
  216. package/dist/chunk-5EEMTJMT.js.map +0 -1
  217. package/dist/chunk-5GUJ36TL.js +0 -53
  218. package/dist/chunk-5GUJ36TL.js.map +0 -1
  219. package/dist/chunk-5UINJNAJ.js.map +0 -1
  220. package/dist/chunk-6R56OKJZ.js.map +0 -1
  221. package/dist/chunk-AVEQUR44.js.map +0 -1
  222. package/dist/chunk-BVEDKMNG.js.map +0 -1
  223. package/dist/chunk-BYGPUUCT.js.map +0 -1
  224. package/dist/chunk-CWXTNPNZ.js.map +0 -1
  225. package/dist/chunk-DFKBTBO3.js.map +0 -1
  226. package/dist/chunk-F3JWOPTG.js.map +0 -1
  227. package/dist/chunk-FSGICFXB.js.map +0 -1
  228. package/dist/chunk-GASBX52O.js +0 -12
  229. package/dist/chunk-GASBX52O.js.map +0 -1
  230. package/dist/chunk-IY6256GK.js.map +0 -1
  231. package/dist/chunk-KJRVMMM3.js.map +0 -1
  232. package/dist/chunk-LM2NHRCJ.js.map +0 -1
  233. package/dist/chunk-QF6IQQFP.js.map +0 -1
  234. package/dist/chunk-R5IMFAFR.js.map +0 -1
  235. package/dist/chunk-TO7DJYLZ.js.map +0 -1
  236. package/dist/chunk-TR3HFH6J.js.map +0 -1
  237. package/dist/chunk-WJMPCVOR.js.map +0 -1
  238. package/dist/chunk-WSJ7RO54.js.map +0 -1
  239. package/dist/chunk-WVNDEYXL.js.map +0 -1
  240. package/dist/chunk-XHHMM5HX.js.map +0 -1
  241. package/dist/chunk-XN37VTDB.js.map +0 -1
  242. package/dist/chunk-XPJQXSC4.js.map +0 -1
  243. package/dist/chunk-YFAXOD7Z.js +0 -42
  244. package/dist/chunk-YFAXOD7Z.js.map +0 -1
  245. package/dist/chunk-YLYXW5PH.js.map +0 -1
  246. package/dist/chunk-Z2K4JPQ3.js.map +0 -1
  247. package/dist/chunk-ZCL7C5LH.js.map +0 -1
  248. package/dist/client-G2WGWPCA.js +0 -11
  249. package/dist/db-XKZYDCKM.js.map +0 -1
  250. package/dist/digest-7UBGUBRK.js.map +0 -1
  251. package/dist/digest-state-3KHZF7GD.js.map +0 -1
  252. package/dist/ensure-site-OAAE5YE2.js.map +0 -1
  253. package/dist/fleet-events-cF2Om88w.d.ts +0 -24
  254. package/dist/forms-notify-target-UC6PF3AE.js.map +0 -1
  255. package/dist/github-signals-TX5FPYP6.js.map +0 -1
  256. package/dist/header-image-PSPT7CUM.js.map +0 -1
  257. package/dist/launch-UD36AHUN.js.map +0 -1
  258. package/dist/lighthouse-airtable-YMH4UA7R.js +0 -12
  259. package/dist/match-harness-QRG6XZAR.js +0 -70
  260. package/dist/match-harness-QRG6XZAR.js.map +0 -1
  261. package/dist/migrate-Y7R4CESL.js +0 -7
  262. package/dist/parity-CAJ477PS.js +0 -14
  263. package/dist/preflight-RYIP54NY.js.map +0 -1
  264. package/dist/prismic-ci-6GKKZGB5.js.map +0 -1
  265. package/dist/prismic-models-P6FWWVRS.js.map +0 -1
  266. package/dist/prospect-audit-PRHU53HS.js.map +0 -1
  267. package/dist/protection-audit-SJO53MEQ.js +0 -168
  268. package/dist/protection-audit-SJO53MEQ.js.map +0 -1
  269. package/dist/render-YGNGTX3W.js.map +0 -1
  270. package/dist/replay-PCSQOGCZ.js.map +0 -1
  271. package/dist/report-LQS4ECTM.js.map +0 -1
  272. package/dist/report-mirror-HDTOJE64.js.map +0 -1
  273. package/dist/schema-A4WCPIF4.js.map +0 -1
  274. package/dist/site-mirror-WON4FTJ2.js.map +0 -1
  275. package/dist/submissions-3I3GAQAX.js.map +0 -1
  276. package/dist/submissions-OVZPVBYG.js.map +0 -1
  277. /package/dist/{airtable-JGCPS7JH.js.map → attachments-JFGFQRXQ.js.map} +0 -0
  278. /package/dist/{blux-4KNXUP2K.js.map → blux-NXQLMS4W.js.map} +0 -0
  279. /package/dist/{bump-deps-C5G2EFBB.js.map → bump-deps-775VLDYB.js.map} +0 -0
  280. /package/dist/{chunk-FUWFLAWQ.js.map → chunk-3RLDX3Q2.js.map} +0 -0
  281. /package/dist/{chunk-S7RYFUIK.js.map → chunk-6PGXKN6F.js.map} +0 -0
  282. /package/dist/{chunk-HTABUCF4.js.map → chunk-6THEZQLA.js.map} +0 -0
  283. /package/dist/{chunk-2LYOAB2E.js.map → chunk-HSMN35LU.js.map} +0 -0
  284. /package/dist/{chunk-BCKEP5S6.js.map → chunk-JHJNNJLG.js.map} +0 -0
  285. /package/dist/{chunk-ZQV7UQNE.js.map → chunk-LUF52EIY.js.map} +0 -0
  286. /package/dist/{chunk-VSGMCCGQ.js.map → chunk-MMSMZC7S.js.map} +0 -0
  287. /package/dist/{chunk-4763KQ6O.js.map → chunk-PXRN2MER.js.map} +0 -0
  288. /package/dist/{chunk-WMBLR5VA.js.map → chunk-PZZ6HM5T.js.map} +0 -0
  289. /package/dist/{chunk-CJ2RMTT7.js.map → chunk-TV76A6GN.js.map} +0 -0
  290. /package/dist/{chunk-KZRR4NFG.js.map → chunk-TVZ6QCOE.js.map} +0 -0
  291. /package/dist/{chunk-BI2VI6EM.js.map → chunk-YXWBM26P.js.map} +0 -0
  292. /package/dist/{attachments-NLZIJ5NU.js.map → client-DGTO65WB.js.map} +0 -0
  293. /package/dist/{convert-to-pnpm-YTFREUJK.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
  294. /package/dist/{client-G2WGWPCA.js.map → deadletter-G4M3HSVM.js.map} +0 -0
  295. /package/dist/{digest-collectors-DJFN5ZDK.js.map → digest-collectors-CYTN2EUX.js.map} +0 -0
  296. /package/dist/{fleet-state-UEVZ3X3J.js.map → fleet-state-RJROYXKO.js.map} +0 -0
  297. /package/dist/{freeze-A6MUCZU4.js.map → freeze-GUQ43EAU.js.map} +0 -0
  298. /package/dist/{health-mirror-XJAKA3EY.js.map → health-mirror-KU3G7V2Y.js.map} +0 -0
  299. /package/dist/{import-airtable-TAKSN4NV.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
  300. /package/dist/{lighthouse-VHI77PFB.js.map → lighthouse-52WSPZ5D.js.map} +0 -0
  301. /package/dist/{lighthouse-airtable-YMH4UA7R.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
  302. /package/dist/{migrate-Y7R4CESL.js.map → migrate-XZ7U5DFC.js.map} +0 -0
  303. /package/dist/{notify-IISTHNBR.js.map → notify-K3WVH3DM.js.map} +0 -0
  304. /package/dist/{onboard-TM2KESGF.js.map → onboard-JQQIMQW7.js.map} +0 -0
  305. /package/dist/{orchestrate-HYVQPRCE.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
  306. /package/dist/{parity-CAJ477PS.js.map → parity-2JGVUCZD.js.map} +0 -0
  307. /package/dist/{pipeline-YROAY6DT.js.map → pipeline-UUPXOS2J.js.map} +0 -0
  308. /package/dist/{prospect-audits-R3LWF6U7.js.map → prospect-audits-IZVQY3OQ.js.map} +0 -0
  309. /package/dist/{render-from-row-IFSBHWDH.js.map → render-from-row-75JTED2Z.js.map} +0 -0
  310. /package/dist/{svelte-codemods-WSV2FGNG.js.map → svelte-codemods-VOLOKKFI.js.map} +0 -0
  311. /package/dist/{sync-K3PKHVLH.js.map → sync-F4USL4K5.js.map} +0 -0
  312. /package/dist/{sync-configs-RRIHNLIJ.js.map → sync-configs-OXSWKVZ2.js.map} +0 -0
  313. /package/dist/{upgrade-VTD6V4HS.js.map → upgrade-EVAL3JTO.js.map} +0 -0
  314. /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
  315. /package/dist/{websites-TB4TCAWM.js.map → websites-Y6FN2XFD.js.map} +0 -0
  316. /package/dist/{write-audits-to-airtable-53EVXKMU.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/match-harness.ts","../src/recipes/match-harness/index.ts","../src/recipes/match-harness/previous.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { matchHarness } from \"../../recipes/match-harness/index.js\";\nimport type { RecipeResult } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport { prepareFleetSites, appendSkipNotice, type SkippedSite } from \"../fleet/prepare-sites.js\";\nimport { runRecipeOverSites } from \"../fleet/run-recipe-over-sites.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\n\nexport type MatchHarnessCommandOptions = {\n ref?: string;\n cand?: string;\n /** cac type-coerces a numeric option value, so this arrives as a NUMBER for\n * `--matrix 1440` and a string only when it contains a comma or a letter. */\n matrix?: string | number;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n};\n\nfunction formatResult(r: RecipeResult): string {\n if (r.status === \"noop\") return `[${r.site}] noop: ${r.notes ?? \"\"}`;\n if (r.status === \"failed\") return `[${r.site}] failed: ${r.notes ?? \"\"}`;\n return `[${r.site}] applied: ${r.commits.length} commit(s)\\n${r.notes ?? \"\"}`;\n}\n\nexport async function runMatchHarnessCommand(\n site: string | undefined,\n opts: MatchHarnessCommandOptions,\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n // NOTHING RESOLVED IS NOT AN INSTALLED FLEET — the same refusal\n // `prismic-ci` opens with, for the same reason. `runRecipeOverSites` over an\n // empty list joins zero rows into the empty string and this command returned\n // it with code 0: a blank line and a success exit, which is indistinguishable\n // from \"every site already had the harness\". Measured on this branch before\n // the fix, `match-harness --ref … --fleet <inventory naming nobody>` printed\n // one empty line and exited 0.\n //\n // An Airtable view filter, an empty JSON file, or a dynamic inventory\n // returning [] all produce exactly that, and none of them is a run.\n if (sites.length === 0) {\n return {\n output:\n `the inventory resolved NO SITES, so the matching harness was installed nowhere.` +\n ` This is not an installed fleet — check the inventory (an Airtable view filter, an` +\n ` empty JSON file, a dynamic inventory returning []). Do NOT read this exit as a` +\n ` rollout.`,\n code: 1,\n };\n }\n\n let skipped: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, { workdir });\n sites = prep.prepared;\n skipped = prep.skipped;\n }\n\n // A bad --matrix must not silently become [NaN]: the matrix is the gate's\n // denominator, and a NaN viewport would produce a score nobody could read.\n //\n // Two things force the shape below. cac COERCES a numeric option value, so\n // `--matrix 1440` arrives as the number 1440 (`.split` would throw) and\n // `--matrix 0` as the number 0 — which is FALSY, so a truthiness test drops it\n // and the recipe silently applies the default [1440,834,390] and exits 0. So:\n // normalise to a string, and branch on PRESENCE, never on truthiness.\n const rawMatrix = opts.matrix === undefined ? undefined : String(opts.matrix);\n const matrix =\n rawMatrix === undefined ? undefined : rawMatrix.split(\",\").map((v) => Number(v.trim()));\n if (matrix !== undefined && matrix.some((v) => !Number.isFinite(v) || v <= 0)) {\n return {\n output: `match-harness: --matrix \"${rawMatrix}\" is not a list of viewports`,\n code: 1,\n };\n }\n\n const results = await runRecipeOverSites(\"match-harness\", sites, (s) =>\n matchHarness(s, {\n ref: opts.ref ?? \"\",\n ...(opts.cand !== undefined ? { cand: opts.cand } : {}),\n ...(matrix !== undefined ? { matrix } : {}),\n }),\n );\n\n const output = results.map(formatResult).join(\"\\n\");\n const code = results.some((r) => r.status === \"failed\") ? 1 : 0;\n return { output: appendSkipNotice(output, skipped), code };\n}\n","import { mkdir, readFile, rm, rmdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { ignoreRulesFor, pathsMissingFromHead } from \"../../util/git.js\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { formatWithPrettier, resolveTargetPrettier, PRETTIER_FLAG_NOTE } from \"../_prettier.js\";\nimport {\n MATCH_HARNESS_FILES,\n MATCH_HARNESS_PREVIOUS,\n MATCH_HARNESS_COUPLED,\n HARNESS_JSON_RELATIVE,\n GITIGNORE_MARKER,\n GITIGNORE_BLOCK,\n PRETTIERIGNORE_MARKER,\n PRETTIERIGNORE_BLOCK,\n CLAUDE_MD_MARKER,\n CLAUDE_MD_BLOCK,\n} from \"./template.js\";\nimport { MATCH_HARNESS_BLOCK_PREVIOUS } from \"./previous.js\";\n\n/** How long the target's own prettier gets. Also what makes the fleet's default\n * spawn detach the child, so the kill reaches prettier and not just a wrapper.\n * Same budget as `prismic-ci` and the Prismic pull-down path. */\nconst PRETTIER_TIMEOUT_MS = 60_000;\n\nexport type MatchHarnessOptions = {\n /** The live reference URL the harness gates against. Required — a harness\n * with no reference cannot produce evidence, only an absence of errors. */\n ref: string;\n /** Candidate origin. Defaults to the SvelteKit dev server. */\n cand?: string;\n /** Breakpoint matrix. Defaults to the fleet's 1440 / 834 / 390. */\n matrix?: number[];\n};\n\nexport type MatchHarnessDeps = {\n spawn: SpawnFn;\n /** Previously shipped renders per relative path; injectable so the\n * safe-replace path is testable before a second version exists. */\n previous?: Readonly<Record<string, readonly string[]>>;\n /** Previously shipped BLOCK bodies per target file; injectable for the same\n * reason, and empty in the shipped table until a body is superseded. */\n blockPrevious?: Readonly<Record<string, readonly string[]>>;\n /** Resolve the TARGET repo's own prettier. Injected so a test can assert the\n * absolute-path spawn without a populated `node_modules`. */\n resolvePrettier?: (repoRoot: string) => Promise<string | null>;\n};\n\ntype Plan = { ref: string; cand: string; matrix: number[] };\n\nasync function readIfExists(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** CRLF and trailing whitespace are the only differences a site's formatter is\n * allowed to introduce before a file stops counting as \"ours\". */\nfunction normalize(s: string): string {\n return s\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n}\n\n/** What to do with one marked block region. `flag` and `skip` never write.\n * One member per action, so `content` narrows on the discriminant alone. */\nexport type BlockPlan =\n | { action: \"write\"; content: string }\n | { action: \"replace\"; content: string }\n | { action: \"terminate\"; content: string }\n | { action: \"skip\" }\n | { action: \"flag\" };\n\n/**\n * Plan one marked block region. The only append-to-existing-file idiom in this\n * repo is mergeGitignore, and it cannot express a negated whitelist\n * (`matching/*` then `!matching/*.sh`) as one unit: it compares entries by\n * normalized presence, so the ordering that makes the block work would be lost.\n * So the block is kept whole — and, being whole, it needs an END as well as a\n * start if it is ever to be CORRECTED rather than only appended.\n *\n * That was #739. The predecessor returned \"already done\" the instant the marker\n * was present, which is right for \"never append twice\" and wrong for everything\n * else: the block's CONTENTS could then never change on a site that had already\n * installed. On `.gitignore` that is not staleness but a brick — the block is a\n * negated whitelist, so a harness file at a path it does not re-include is\n * absent from the commit, `pathsMissingFromHead` refuses the install, and the\n * remedy (widen the whitelist) is the one edit that cannot land.\n *\n * Four states, and the third is the one that made this worth doing properly:\n *\n * - No marker: append the region, terminator included.\n * - Marker AND terminator: the extent is KNOWN. Body still current -> skip.\n * Body byte-matches something we shipped before -> replace IN PLACE, keeping\n * everything before and after the region. Anything else -> flag, write\n * nothing.\n * - Marker, NO terminator — every site installed from 0.95.0: the extent is not\n * known, so it is never guessed. A candidate qualifies only by matching\n * EXACTLY, byte-for-byte, at the exact offset the body starts; what follows\n * is the site's own and is preserved verbatim. Guessing end-of-file instead\n * would eat whatever mergeGitignore appended after us.\n * - Anything unrecognised: flag. Anchoring is on the FIRST occurrence of the\n * marker, so a marker quoted in the site's own prose mis-anchors into text\n * that matches no candidate — which degrades to a flag, never to a write.\n */\nexport function planBlockWrite(\n existing: string | null,\n marker: string,\n endMarker: string,\n block: string,\n previous: readonly string[],\n): BlockPlan {\n const region = `${marker}\\n${block}\\n${endMarker}`;\n if (existing === null) return { action: \"write\", content: `${region}\\n` };\n\n const start = existing.indexOf(marker);\n if (start === -1) {\n const base = existing.endsWith(\"\\n\") ? existing : `${existing}\\n`;\n return { action: \"write\", content: `${base}\\n${region}\\n` };\n }\n\n const head = existing.slice(0, start);\n const bodyStart = start + marker.length + 1;\n\n const endAt = existing.indexOf(endMarker, bodyStart);\n if (endAt !== -1) {\n const body = existing.slice(bodyStart, endAt);\n if (normalize(body) === normalize(block)) return { action: \"skip\" };\n if (previous.some((p) => normalize(body) === normalize(p)))\n return {\n action: \"replace\",\n content: head + region + existing.slice(endAt + endMarker.length),\n };\n return { action: \"flag\" };\n }\n\n for (const candidate of [block, ...previous]) {\n if (!existing.startsWith(candidate, bodyStart)) continue;\n const content = `${head}${region}\\n${existing.slice(bodyStart + candidate.length)}`;\n // Matching the CURRENT block means the bytes do not change at all and only\n // the terminator is added — the migration write is content-neutral.\n return candidate === block ? { action: \"terminate\", content } : { action: \"replace\", content };\n }\n return { action: \"flag\" };\n}\n\n/**\n * The commit message says what the run DID. Every run used to commit as\n * `feat: install …`, including one that installed nothing — measured on 29-navy:\n * three scripts upgraded, three block regions terminated, 6 files and 436\n * lines, labelled a first install (#760). The notes distinguished the two the\n * whole time; the message is what a reader bisecting `git log` sees.\n *\n * `write` anywhere is an install; only replace/terminate is an upgrade; both is\n * both. A pristine install keeps the historical one-line message byte-for-byte.\n * An upgrade names its paths in the body, so `git log` answers \"when did this\n * site's gate.sh change\" without a diff.\n */\nexport function commitMessage(installed: readonly string[], upgraded: readonly string[]): string {\n const what = \"(/dev/match route + matching/ gate scripts)\";\n if (upgraded.length === 0) return `feat: install the matching harness ${what}`;\n const subject =\n installed.length > 0\n ? `feat: install and upgrade the matching harness ${what}`\n : `chore: upgrade the matching harness (${upgraded.length} file${upgraded.length === 1 ? \"\" : \"s\"} from a previously shipped version)`;\n const body = [\n installed.length > 0 ? `installed: ${installed.join(\", \")}` : \"\",\n `upgraded from a previously shipped version: ${upgraded.join(\", \")}`,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n return `${subject}\\n\\n${body}`;\n}\n\n/** What to do with one installed file. `flag` never writes. */\nexport function planFileWrite(\n existing: string | null,\n template: string,\n owner: \"recipe\" | \"site\",\n previous: readonly string[],\n): \"write\" | \"replace\" | \"skip\" | \"flag\" {\n if (existing === null) return \"write\";\n if (normalize(existing) === normalize(template)) return \"skip\";\n if (owner === \"site\") return \"skip\"; // records are never touched again\n if (previous.some((p) => normalize(existing) === normalize(p))) return \"replace\";\n return \"flag\";\n}\n\n/**\n * Where each block region ENDS. Deliberately NOT in template.ts beside the\n * start markers, and the split is a real smell being paid for on purpose:\n * template.ts is GENERATED, and regenerating it today drags in undeclared drift\n * from the source site and would flag three already-installed scripts forever.\n * Reuniting them belongs to #753, which makes the generator's\n * `MATCH_HARNESS_PREVIOUS` emission trustworthy enough to regenerate against.\n * That table stays in template.ts and stays POPULATED in the meantime: it is\n * what upgrades an installed site's gate.sh past #744, so pointing this recipe\n * at an empty one un-ships that fix to every site already carrying the v1 gate.\n *\n * None of the three contains its own start marker as a substring — `# end\n * reddoor-maint …` never yields `# reddoor-maint …` — which is what keeps\n * `indexOf(marker)` from anchoring the region on its own terminator. A test\n * asserts it in both directions.\n */\nexport const GITIGNORE_END_MARKER = \"# end reddoor-maint match-harness\";\nexport const PRETTIERIGNORE_END_MARKER = \"# end reddoor-maint match-harness\";\nexport const CLAUDE_MD_END_MARKER = \"<!-- end reddoor-maint match-harness -->\";\n\n/** The three files the recipe APPENDS a marked block to rather than writing whole. */\nconst APPENDED_BLOCKS = [\n [\".gitignore\", GITIGNORE_MARKER, GITIGNORE_END_MARKER, GITIGNORE_BLOCK],\n [\".prettierignore\", PRETTIERIGNORE_MARKER, PRETTIERIGNORE_END_MARKER, PRETTIERIGNORE_BLOCK],\n [\"CLAUDE.md\", CLAUDE_MD_MARKER, CLAUDE_MD_END_MARKER, CLAUDE_MD_BLOCK],\n] as const;\n\n/**\n * Every path the installed harness requires to be IN THE COMMIT — deliberately\n * the whole manifest, NOT the paths written on this run. A re-run skips every\n * file already byte-correct on disk, so a check over the written delta would\n * find nothing missing and report a second hollow \"applied\" over the same\n * half-install.\n */\nexport const MATCH_HARNESS_INSTALLED_PATHS: readonly string[] = [\n ...MATCH_HARNESS_FILES.map((f) => f.rel),\n ...APPENDED_BLOCKS.map(([rel]) => rel),\n];\n\n/**\n * Installs the matching harness into a site: the dev-guarded `/dev/match/[uid]`\n * route, a `site-pages.js` scaffold and its fixture-vs-model test,\n * `matching/harness.json` + the read layer, the round scripts, the site records,\n * and the `.gitignore` / `.prettierignore` / `CLAUDE.md` blocks.\n *\n * Install-if-absent throughout. Files a site edits are never rewritten. A\n * recipe-owned script that matches a previously shipped render is safe-replaced;\n * one that has been hand-edited is FLAGGED in the notes and left alone.\n *\n * ONE EXCEPTION TO PER-FILE INDEPENDENCE: the scripts in MATCH_HARNESS_COUPLED\n * upgrade as a SET. If any one of them is hand-edited, none of them is written,\n * because gate.sh calls `harness.mjs --check-run` and next.mjs imports from\n * harness.mjs — upgrading the others around a pinned one leaves a harness that\n * cannot run at all. See the measurement on MATCH_HARNESS_COUPLED.\n */\nexport async function matchHarness(\n site: Site,\n opts: MatchHarnessOptions,\n deps: MatchHarnessDeps = { spawn: defaultSpawn },\n): Promise<RecipeResult> {\n return withRecipe<Plan>({\n name: \"match-harness\",\n site,\n plan: async () => {\n if (!opts.ref || !/^https?:\\/\\//.test(opts.ref)) {\n return {\n kind: \"failed\",\n notes:\n \"--ref <url> is required: the harness gates against a live reference, and a harness with no reference can only ever report an absence of errors\",\n };\n }\n return {\n kind: \"apply\",\n plan: {\n ref: opts.ref.replace(/\\/$/, \"\"),\n cand: opts.cand ?? \"http://localhost:5173\",\n matrix: opts.matrix ?? [1440, 834, 390],\n },\n };\n },\n apply: async (planned, { commit, cwd }) => {\n const notes: string[] = [];\n const written: string[] = [];\n /** What this run did, for the commit message: paths written for the\n * FIRST time, and paths replaced or terminated from a previously shipped\n * render. The notes already tell them apart; the message is what\n * survives into `git log` (#760). */\n const installed: string[] = [];\n const upgraded: string[] = [];\n /** For every path this run writes: what was there BEFORE (null = the file\n * did not exist). The refusal below restores from this, so a refused run\n * leaves the checkout byte-identical to how it found it. */\n const before = new Map<string, string | null>();\n const previousAll = deps.previous ?? MATCH_HARNESS_PREVIOUS;\n const blockPreviousAll = deps.blockPrevious ?? MATCH_HARNESS_BLOCK_PREVIOUS;\n\n // PASS 1 — decide everything, write nothing. The coupled-set demotion\n // below needs the whole picture before the first byte is written.\n type Planned = {\n f: (typeof MATCH_HARNESS_FILES)[number];\n template: string;\n existing: string | null;\n action: ReturnType<typeof planFileWrite>;\n };\n const plans: Planned[] = [];\n for (const f of MATCH_HARNESS_FILES) {\n const target = join(cwd, f.rel);\n let template = f.template;\n if (f.rel === HARNESS_JSON_RELATIVE) {\n // Patch the PARSED object, never the text. The seed is pretty-printed\n // two-space JSON, so `matrix` renders as a four-line exploded array\n // and the literal \"[1440, 834, 390]\" does not occur — a string\n // replace on it is a no-op that drops --matrix with no error.\n const seed = JSON.parse(template) as Record<string, unknown>;\n seed.ref = planned.ref;\n seed.cand = planned.cand;\n seed.matrix = planned.matrix;\n template = JSON.stringify(seed, null, 2) + \"\\n\";\n }\n const existing = await readIfExists(target);\n plans.push({\n f,\n template,\n existing,\n action: planFileWrite(existing, template, f.owner, previousAll[f.rel] ?? []),\n });\n }\n\n // The coupled set moves together or not at all. Two deliberate limits,\n // both of them the difference between a guard and an outage:\n //\n // ONLY `flag` demotes. `skip` means the file is already byte-correct, and\n // treating that as a flag would make every re-run refuse itself and the\n // recipe permanently inert — the demotion's own failure mode is\n // OVER-refusal, so its test has to GRANT an upgrade, not merely deny one.\n //\n // ONLY `replace` is demoted. A file that is ABSENT cannot be \"left\n // alone\": there is nothing to preserve, and skipping the write leaves the\n // site with no harness at all rather than an old one. The hazard being\n // guarded is upgrading HALF of an installed set past a pinned member;\n // writing a missing file is the only action that makes the set exist.\n // (Measured: demoting `write` too broke a fresh install alongside one\n // hand-edited script — 16 files silently unwritten, then a failed run.)\n const coupled = new Set(MATCH_HARNESS_COUPLED);\n const blockedBy = plans\n .filter((p) => coupled.has(p.f.rel) && p.action === \"flag\")\n .map((p) => p.f.rel);\n const demoted = plans.filter(\n (p) => coupled.has(p.f.rel) && p.action === \"replace\" && !blockedBy.includes(p.f.rel),\n );\n if (blockedBy.length > 0 && demoted.length > 0) {\n for (const p of demoted) p.action = \"flag\";\n notes.push(\n `${blockedBy.join(\", \")} differs from the shipped template, so the whole coupled set ` +\n `was left alone: ${MATCH_HARNESS_COUPLED.join(\", \")} upgrade together. ` +\n `matching/gate.sh calls \\`harness.mjs --check-run\\` and matching/next.mjs imports from ` +\n `harness.mjs, so upgrading one without the others leaves a harness that cannot run.`,\n );\n }\n\n // PASS 2 — write.\n const demotedRels = new Set(demoted.map((p) => p.f.rel));\n for (const { f, template, existing, action } of plans) {\n if (action === \"flag\") {\n if (demotedRels.has(f.rel)) continue; // named in the set note above\n notes.push(\n `${f.rel} differs from the shipped template and was left alone (hand-edited?)`,\n );\n continue;\n }\n if (action === \"skip\") continue;\n const target = join(cwd, f.rel);\n await mkdir(dirname(target), { recursive: true });\n before.set(f.rel, existing);\n await writeFile(target, template, \"utf-8\");\n written.push(f.rel);\n (action === \"write\" ? installed : upgraded).push(f.rel);\n if (action === \"replace\") notes.push(`${f.rel} upgraded from a previous version`);\n }\n\n // Three appended blocks, each a delimited region that is replaced in\n // place when its body is one this recipe shipped, and flagged — never\n // overwritten — when it is anything else.\n for (const [rel, marker, endMarker, block] of APPENDED_BLOCKS) {\n const path = join(cwd, rel);\n const existing = await readIfExists(path);\n const plan = planBlockWrite(\n existing,\n marker,\n endMarker,\n block,\n blockPreviousAll[rel] ?? [],\n );\n if (plan.action === \"skip\") continue;\n if (plan.action === \"flag\") {\n // Never enters `written`: not formatted, and not restored by the\n // refusal path below, because this run did not touch it.\n notes.push(\n `${rel} carries a match-harness block that differs from every block this recipe has shipped, and was left alone (hand-edited?)`,\n );\n continue;\n }\n before.set(rel, existing);\n await writeFile(path, plan.content, \"utf-8\");\n written.push(rel);\n (plan.action === \"write\" ? installed : upgraded).push(`${rel} (match-harness block)`);\n if (plan.action === \"replace\")\n notes.push(`${rel}'s match-harness block upgraded from a previous version`);\n if (plan.action === \"terminate\")\n notes.push(\n `${rel}'s match-harness block region was terminated so a future version can update it`,\n );\n }\n\n // Format only what the SITE owns, decided by OWNERSHIP and never by path.\n // A recipe-owned file that a site's prettier reformats is indistinguishable\n // from a hand edit on the next install, so planFileWrite flags it and the\n // recipe can never upgrade it again. A path prefix cannot express that:\n // three recipe-owned files live under src/. This is an ALLOW-list on\n // purpose — a file added to MATCH_HARNESS_FILES is not formatted until its\n // owner says \"site\", because the unsafe default is silent and permanent.\n // The same files are in the .prettierignore block written above, which\n // stops the site's OWN `prettier --write .` from doing the identical\n // damage; that block and this filter must agree, and a test asserts it.\n // CLAUDE.md is not in the file table — it is an append into a file the\n // site owns — so it is named here.\n const siteOwned = new Set(\n MATCH_HARNESS_FILES.filter((f) => f.owner === \"site\").map((f) => f.rel),\n );\n const toFormat = written.filter((p) => siteOwned.has(p) || p === \"CLAUDE.md\");\n\n // Run the SITE's own prettier, resolved POSITIVELY and invoked by absolute\n // path. `pnpm exec prettier` here would first run a full, unbounded\n // `pnpm install` in the client's checkout — which on `--fleet` is the\n // NORMAL path, because `prepareFleetSites` clones and never installs — and\n // would then, in a repo whose install left no prettier of its own, fall\n // through to the CALLING repo's binary and exit 0, reporting success for a\n // format the target never did. A `true` from here must mean the target's\n // own prettier ran, so nothing about which binary runs is left to\n // resolution.\n if (toFormat.length > 0) {\n const bin = await (deps.resolvePrettier ?? resolveTargetPrettier)(cwd);\n if (bin === null) {\n notes.push(PRETTIER_FLAG_NOTE);\n } else if (\n !(await formatWithPrettier(deps.spawn, cwd, toFormat, {\n bin,\n timeoutMs: PRETTIER_TIMEOUT_MS,\n }))\n ) {\n notes.push(PRETTIER_FLAG_NOTE);\n }\n }\n\n await commit(commitMessage(installed, upgraded));\n\n // POSITIVE EVIDENCE that the install is real: every installed path is\n // FOUND in HEAD's tree. `git add -A` honours the site's .gitignore and\n // exits 0 either way, and git cannot re-include a file whose PARENT\n // DIRECTORY is excluded — so a site that already ignores `matching/`\n // (or `**/matching/`, `/matching`, `src/lib/site-pages.js`, …) gets the\n // files on disk, nothing in the commit, and — before this check — a\n // result of \"applied\", plus 83 lines of CLAUDE.md rules naming scripts\n // that exist on no other machine.\n const missing = await pathsMissingFromHead(cwd, MATCH_HARNESS_INSTALLED_PATHS);\n if (missing.length > 0) {\n const rules = await ignoreRulesFor(cwd, missing);\n const sources = [...new Set(rules.map((r) => r.split(\"\\t\")[0]!))];\n const explained = new Set(rules.map((r) => r.split(\"\\t\")[1] ?? \"\"));\n const unexplained = missing.filter((p) => !explained.has(p));\n\n // Put the checkout back exactly as we found it. Only paths THIS RUN\n // wrote and git then refused are touched: one that reached the commit\n // is left to withRecipe's force-checkout, and one we never wrote is\n // never ours. `git checkout -f` cannot do this job — these paths are\n // ignored, so git does not know they exist.\n const removed: string[] = [];\n for (const rel of written.filter((p) => missing.includes(p))) {\n const prev = before.get(rel);\n if (prev === undefined) continue;\n if (prev === null) {\n await rm(join(cwd, rel), { force: true });\n removed.push(rel);\n } else {\n await writeFile(join(cwd, rel), prev, \"utf-8\");\n }\n }\n // Deepest-first, so `matching/spec-sections` goes before `matching`.\n // rmdir refuses a non-empty directory, which is exactly the guard wanted.\n for (const d of [...new Set(removed.map(dirname))].sort((a, b) => b.length - a.length)) {\n if (d !== \".\") await rmdir(join(cwd, d)).catch(() => undefined);\n }\n\n return {\n kind: \"failed\",\n notes:\n `the harness was NOT installed: ${missing.length} of ${MATCH_HARNESS_INSTALLED_PATHS.length} paths git refused to track, so a fresh clone, CI and the next agent get a harness that does not exist. ` +\n `Absent from the commit: ${missing.join(\", \")}. ` +\n (sources.length > 0\n ? `Excluded by ${sources.join(\", \")} — narrow or remove those rules and re-run. `\n : \"\") +\n (unexplained.length > 0\n ? `No ignore rule matched ${unexplained.join(\", \")} — check .git/info/exclude, core.excludesFile, and whether the directory is a nested repository. `\n : \"\") +\n \"Everything this run wrote to those paths has been put back as it was.\",\n };\n }\n\n return notes.length > 0 ? { kind: \"ok\", notes: notes.join(\"; \") } : { kind: \"ok\" };\n },\n });\n}\n","/**\n * Block BODIES this recipe has previously shipped — the block half of what lets\n * it tell \"stale, but ours\" from \"hand-edited by the site\". `planBlockWrite`\n * flags every region it cannot recognise, so without a record of what was\n * shipped, install-once is install-forever for the three marked regions.\n *\n * The whole-FILE table is NOT here. It lives in the generated `template.ts` as\n * `MATCH_HARNESS_PREVIOUS`, it is POPULATED, and it is what upgrades an\n * installed site's `matching/gate.sh` to the one that stopped saying ALL DONE\n * over failed runs (#744). Re-pointing that import at an empty table here would\n * silently un-ship that fix to every site already carrying the v1 gate — which\n * is exactly what this file did on its first draft, and what\n * `tests/recipes/match-harness.test.ts`'s \"UPGRADES a site running the shipped\n * v1 gate.sh rather than flagging it\" caught. The deprecation note and the\n * import switch were written together; the data migration they both depend on\n * is #753, and had not happened. Whatever replaces the generated table has to\n * carry the bodies across in the same commit that moves the import.\n *\n * Two rules for what goes below, and they are why this table is HAND-AUTHORED:\n *\n * 1. An entry is added ONLY when a block body has been SUPERSEDED — at the\n * moment the shipped body changes, in the same commit that changes it.\n * Recording a body that is still current makes the table lie in the one\n * direction that matters: it would let a stale render be replaced by itself.\n *\n * 2. A body's source is a PUBLISHED GIT TAG — `git show v0.95.0:<path>` — never\n * the working tree. Reading the tree is how a mutant gets banked as a\n * legitimate prior release: mutate, regenerate, revert, and the mutation is\n * now a body this recipe will happily install over a real site. Measured on\n * this branch: one mutate/regenerate/revert cycle added 693 lines and two\n * copies of the same predicate.\n *\n * EMPTY at 0.95.1 on purpose. 0.95.1 changes no block body at all: its whole\n * job is to install the region terminators, which is what makes the first\n * exercise of the block replace path provably content-neutral.\n *\n * First entry after 0.95.1: the CLAUDE.md block, superseded when rule 4 and the\n * round protocol gained `census.sh` (#736). Sourced from `git show v0.95.1`,\n * per rule 2, and identical at v0.95.0.\n */\n\n/** The CLAUDE.md block as shipped at v0.95.0 and v0.95.1 — `git show\n * v0.95.1:src/recipes/match-harness/template.ts` → `CLAUDE_MD_BLOCK`, byte-\n * identical at both tags. Superseded by the block that names `census.sh`\n * (#736): the recipe installed the Phase 3 style gate and the rules that would\n * make anyone run it did not mention it. Exported so a test can seed exactly\n * this body and prove the shipped table upgrades it. */\nexport const CLAUDE_MD_BLOCK_0_95_1 = `\nThe \\`matching-a-page\\` skill governs a live-reference rebuild. These five rules\nexist because the skill alone did not hold on the project this harness came\nfrom — each one is a drift that actually happened, with the mechanical check\nthat now catches it. \\`matching/harness.json\\` is this site's configuration;\n\\`matching/LEDGER.md\\` is the dated record of every deviation, floor and mask.\n\n### 1. Source prescribes, rects only verify\n\nEvery geometry fix must cite the rule it came from: a line in the captured\nreference stylesheet under \\`matching/spec/\\`, or the reference's HTML. \"The probe\nsays the gap is 40px\" is not a source. If you cannot name the line you are\nguessing — go read the stylesheet first.\n\n**Check:** the commit body must name the file:line for each fix.\n**Operator's challenge:** _\"which line of the reference stylesheet says that?\"_\n\n### 2. Phase 1 before Phase 4\n\nA page gets its section census and per-section spec in \\`matching/SPEC.md\\` BEFORE\nits geometry is touched. No SPEC section, no geometry round. The census is the\ncoverage denominator; skipping it is how a reference's root-font ladder and its\nper-component height ladders get discovered reactively, after the region has\nalready failed several rounds.\n\n**Check:** \\`matching/gate.sh\\` refuses to run a page with no \\`SPEC.md\\` section.\n**Operator's challenge:** _\"show me the SPEC section for that region.\"_\n\n### 3. Three strikes, then stop\n\nA failing region that has not improved across 3+ gate runs does not get a fourth\nattempt. Present the attempts. Never widen the threshold, add a mask, or\nreclassify it as a floor to make it go away.\n\n**Check:** \\`node matching/strikes.mjs <page>\\` — exits 1 while any region is\nstalled, and is the first thing a geometry round runs.\n**Operator's challenge:** _\"how many runs has that region been flat?\"_\n\n### 4. A gate closes an item, nothing else\n\nNo fix is \"done\" because the code changed. Paste the gate header — it is\nself-describing, so a nonstandard threshold or an undisclosed mask is visible.\nThe threshold and the matrix are whatever \\`matching/harness.json\\` says, on every\npage, never a subset.\n\n**Operator's challenge:** _\"paste the gate header.\"_\n\n### 5. A commit is a checkpoint, not a stopping point\n\nDo not hand control back between rounds. After committing, run\n\\`node matching/next.mjs\\`: while it exits 1 there is a named next action, and the\nround continues. Report when the backlog is empty, when a decision is genuinely\nthe operator's (three strikes, a novel floor, a threshold change), or when\nasked — not because a commit felt like a natural place to summarise.\n\nThis exists because the failure was habitual, not deliberate: a turn ends when\nuser-facing prose gets written, and \"I just committed something good\" is the\nmoment that invites writing it. The gate stops incorrect work; this stops\npremature stopping.\n\n**Pausing:** create \\`matching/PAUSED\\` holding a note that says what the pause\nmeans; \\`next.mjs\\` and \\`strikes.mjs\\` then exit 0 while it exists. This rule says\n\"do not stop while work is named\"; it never said \"find work to name\". An empty\nagenda and a paused one end the round the same way. Deleting the switch is the\noperator's call.\n\n**Check:** \\`node matching/next.mjs\\` — exits 1 while any non-floor region fails.\n**Operator's challenge:** _\"what does next.mjs say?\"_\n\n### Round protocol\n\n1. \\`node matching/strikes.mjs <page>\\` — if it exits 1, the stalled regions are\n the agenda, and stalled ones get escalated rather than re-attempted.\n2. Confirm the page has a \\`matching/SPEC.md\\` section; write\n \\`matching/spec-sections/<page>.md\\` and run \\`node matching/build-spec.mjs\\` if\n not.\n3. Fix, each change citing its source line.\n4. \\`bash matching/gate.sh <tag> <page>\\` — paste the header.\n5. Append to \\`matching/LEDGER.md\\` at the moment a deviation, floor or mask is\n decided, not reconstructed at the end.\n6. \\`pnpm verify\\`, then commit and push.\n`;\n\n/** Block BODIES previously shipped per target file, for the three marked\n * regions in `.gitignore`, `.prettierignore` and `CLAUDE.md`. */\nexport const MATCH_HARNESS_BLOCK_PREVIOUS: Readonly<Record<string, readonly string[]>> = {\n \"CLAUDE.md\": [CLAUDE_MD_BLOCK_0_95_1],\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,7 @@
1
+ import {
2
+ runMigrations
3
+ } from "./chunk-IOTOOTII.js";
4
+ export {
5
+ runMigrations
6
+ };
7
+ //# sourceMappingURL=migrate-XZ7U5DFC.js.map
@@ -5,10 +5,10 @@ import {
5
5
  makeNotify,
6
6
  notifySubmission,
7
7
  resolveRecipients
8
- } from "./chunk-4763KQ6O.js";
9
- import "./chunk-QF6IQQFP.js";
10
- import "./chunk-5ML7FMBD.js";
8
+ } from "./chunk-PXRN2MER.js";
9
+ import "./chunk-Z7FDJ35L.js";
11
10
  import "./chunk-NO74DXDP.js";
11
+ import "./chunk-5ML7FMBD.js";
12
12
  import "./chunk-I7NHLVVI.js";
13
13
  import "./chunk-BGTYPVLT.js";
14
14
  import "./chunk-2OP4JKIQ.js";
@@ -20,4 +20,4 @@ export {
20
20
  notifySubmission,
21
21
  resolveRecipients
22
22
  };
23
- //# sourceMappingURL=notify-IISTHNBR.js.map
23
+ //# sourceMappingURL=notify-K3WVH3DM.js.map
@@ -5,21 +5,21 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-LM2NHRCJ.js";
8
+ } from "./chunk-DYTYPKFN.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-Z2K4JPQ3.js";
11
+ } from "./chunk-FP6HOW6J.js";
12
12
  import {
13
13
  onboard
14
- } from "./chunk-S7RYFUIK.js";
15
- import "./chunk-ZQV7UQNE.js";
14
+ } from "./chunk-6PGXKN6F.js";
15
+ import "./chunk-LUF52EIY.js";
16
16
  import "./chunk-OTGB6YQO.js";
17
17
  import "./chunk-LKGVSM2O.js";
18
18
  import "./chunk-Z3JXLULD.js";
19
19
  import "./chunk-LBYOLBW7.js";
20
20
  import "./chunk-RARREDQE.js";
21
21
  import "./chunk-XXTZBPUY.js";
22
- import "./chunk-AVEQUR44.js";
22
+ import "./chunk-WMF5S2MB.js";
23
23
 
24
24
  // src/cli/commands/onboard.ts
25
25
  import { resolve } from "path";
@@ -70,4 +70,4 @@ async function runOnboardCommand(site, opts) {
70
70
  export {
71
71
  runOnboardCommand
72
72
  };
73
- //# sourceMappingURL=onboard-TM2KESGF.js.map
73
+ //# sourceMappingURL=onboard-JQQIMQW7.js.map
@@ -4,21 +4,24 @@ import {
4
4
  parseAddresses,
5
5
  sendApprovedReports,
6
6
  withGlobalCc
7
- } from "./chunk-2ZAZ7ETT.js";
8
- import "./chunk-F3JWOPTG.js";
7
+ } from "./chunk-LDHRIQUW.js";
8
+ import "./chunk-TEMQPSCM.js";
9
+ import "./chunk-ASLDCWBF.js";
9
10
  import "./chunk-VAD43MOR.js";
10
- import "./chunk-IY6256GK.js";
11
+ import "./chunk-YJBSB4LP.js";
11
12
  import "./chunk-HN6I2XWI.js";
12
- import "./chunk-HTABUCF4.js";
13
+ import "./chunk-6THEZQLA.js";
13
14
  import "./chunk-GECESMVS.js";
14
15
  import "./chunk-KJGKZLCT.js";
15
16
  import "./chunk-BGTYPVLT.js";
16
- import "./chunk-5UINJNAJ.js";
17
- import "./chunk-3Z6X3BHU.js";
17
+ import "./chunk-LZMAG73U.js";
18
+ import "./chunk-TC4GANMX.js";
19
+ import "./chunk-ICSRWW5Q.js";
20
+ import "./chunk-RQJFGK3A.js";
18
21
  import "./chunk-WEKL2HYJ.js";
19
- import "./chunk-BI2VI6EM.js";
20
- import "./chunk-VSGMCCGQ.js";
21
- import "./chunk-C3IQQKXL.js";
22
+ import "./chunk-YXWBM26P.js";
23
+ import "./chunk-MMSMZC7S.js";
24
+ import "./chunk-IOTOOTII.js";
22
25
  import "./chunk-RARREDQE.js";
23
26
  import "./chunk-6VAL7XJT.js";
24
27
  export {
@@ -28,4 +31,4 @@ export {
28
31
  sendApprovedReports,
29
32
  withGlobalCc
30
33
  };
31
- //# sourceMappingURL=orchestrate-HYVQPRCE.js.map
34
+ //# sourceMappingURL=orchestrate-L2A5BZFY.js.map
@@ -0,0 +1,14 @@
1
+ import {
2
+ checkFleetParity,
3
+ formatParityResult
4
+ } from "./chunk-TVZ6QCOE.js";
5
+ import "./chunk-BINFZQ2T.js";
6
+ import "./chunk-LF2TJN4X.js";
7
+ import "./chunk-ASLDCWBF.js";
8
+ import "./chunk-VAD43MOR.js";
9
+ import "./chunk-ICSRWW5Q.js";
10
+ export {
11
+ checkFleetParity,
12
+ formatParityResult
13
+ };
14
+ //# sourceMappingURL=parity-2JGVUCZD.js.map
@@ -11,8 +11,8 @@ import {
11
11
  envEngines,
12
12
  mergeFixes,
13
13
  runProspectAudit
14
- } from "./chunk-TR3HFH6J.js";
15
- import "./chunk-WJMPCVOR.js";
14
+ } from "./chunk-QFSL2L2B.js";
15
+ import "./chunk-LAJ3FF54.js";
16
16
  import "./chunk-T7ZUUOZ2.js";
17
17
  import "./chunk-RARREDQE.js";
18
18
  export {
@@ -29,4 +29,4 @@ export {
29
29
  mergeFixes,
30
30
  runProspectAudit
31
31
  };
32
- //# sourceMappingURL=pipeline-YROAY6DT.js.map
32
+ //# sourceMappingURL=pipeline-UUPXOS2J.js.map
@@ -1,22 +1,25 @@
1
1
  import {
2
2
  preflight
3
- } from "./chunk-R5IMFAFR.js";
3
+ } from "./chunk-VMGHQUTV.js";
4
4
  import "./chunk-PZR4EBPY.js";
5
- import "./chunk-2ZAZ7ETT.js";
6
- import "./chunk-F3JWOPTG.js";
5
+ import "./chunk-LDHRIQUW.js";
6
+ import "./chunk-TEMQPSCM.js";
7
+ import "./chunk-ASLDCWBF.js";
7
8
  import "./chunk-VAD43MOR.js";
8
- import "./chunk-IY6256GK.js";
9
+ import "./chunk-YJBSB4LP.js";
9
10
  import "./chunk-HN6I2XWI.js";
10
- import "./chunk-HTABUCF4.js";
11
+ import "./chunk-6THEZQLA.js";
11
12
  import "./chunk-GECESMVS.js";
12
13
  import "./chunk-KJGKZLCT.js";
13
14
  import "./chunk-BGTYPVLT.js";
14
- import "./chunk-5UINJNAJ.js";
15
- import "./chunk-3Z6X3BHU.js";
15
+ import "./chunk-LZMAG73U.js";
16
+ import "./chunk-TC4GANMX.js";
17
+ import "./chunk-ICSRWW5Q.js";
18
+ import "./chunk-RQJFGK3A.js";
16
19
  import "./chunk-WEKL2HYJ.js";
17
- import "./chunk-BI2VI6EM.js";
18
- import "./chunk-VSGMCCGQ.js";
19
- import "./chunk-C3IQQKXL.js";
20
+ import "./chunk-YXWBM26P.js";
21
+ import "./chunk-MMSMZC7S.js";
22
+ import "./chunk-IOTOOTII.js";
20
23
  import "./chunk-RARREDQE.js";
21
24
  import "./chunk-6VAL7XJT.js";
22
25
 
@@ -31,6 +34,21 @@ function formatSite(r) {
31
34
  if (r.findings.length === 0) return `[${r.site}] \u2713 clean`;
32
35
  return r.findings.map((f) => `[${r.site}] ${MARK[f.level]} ${f.check}: ${f.message}`).join("\n");
33
36
  }
37
+ function lazyFleetDb() {
38
+ let db = null;
39
+ return {
40
+ get: async () => {
41
+ if (!db) {
42
+ const { openDb, readDbConfig } = await import("./client-DGTO65WB.js");
43
+ db = await openDb(readDbConfig());
44
+ }
45
+ return db;
46
+ },
47
+ close: async () => {
48
+ if (db) await db.destroy();
49
+ }
50
+ };
51
+ }
34
52
  async function runPreflightCommand(site, opts) {
35
53
  if (Boolean(site) === Boolean(opts.all)) {
36
54
  return { output: "Provide exactly one of <site> or --all.", code: 2 };
@@ -43,8 +61,12 @@ async function runPreflightCommand(site, opts) {
43
61
  code: 2
44
62
  };
45
63
  }
64
+ const { listSites, listAllReports } = await import("./fleet-state-RJROYXKO.js");
65
+ const fleetDb = lazyFleetDb();
46
66
  try {
47
67
  const { results, fleet } = await preflight({
68
+ roster: async () => listSites(await fleetDb.get()),
69
+ allReports: async () => listAllReports(await fleetDb.get()),
48
70
  ...site ? { site } : {},
49
71
  ...opts.all ? { all: true } : {},
50
72
  type
@@ -62,9 +84,11 @@ async function runPreflightCommand(site, opts) {
62
84
  } catch (err) {
63
85
  const e = err;
64
86
  return { output: e.message ?? String(err), code: e.exitCode ?? 1 };
87
+ } finally {
88
+ await fleetDb.close();
65
89
  }
66
90
  }
67
91
  export {
68
92
  runPreflightCommand
69
93
  };
70
- //# sourceMappingURL=preflight-RYIP54NY.js.map
94
+ //# sourceMappingURL=preflight-O7Q6FWHU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/preflight.ts"],"sourcesContent":["import {\n preflight,\n type PreflightSiteResult,\n type PreflightFinding,\n} from \"../../reports/preflight.js\";\nimport type { ReportType } from \"../../reports/types.js\";\nimport type { Db } from \"../../db/client.js\";\n\nexport type PreflightCommandOptions = {\n type?: string;\n all?: boolean;\n cwd?: string;\n};\n\nconst TYPES: Record<string, ReportType> = {\n announcement: \"Announcement\",\n maintenance: \"Maintenance\",\n testing: \"Testing\",\n};\n\nconst MARK: Record<PreflightFinding[\"level\"], string> = { fail: \"✗\", warn: \"⚠\", info: \"ℹ\" };\n\nfunction formatSite(r: PreflightSiteResult): string {\n if (r.findings.length === 0) return `[${r.site}] ✓ clean`;\n return r.findings.map((f) => `[${r.site}] ${MARK[f.level]} ${f.check}: ${f.message}`).join(\"\\n\");\n}\n\n/**\n * `preflight [site]` — read-only pre-send checks over the live fleet rows: everything\n * that would make a report send fail, reach the wrong inbox, or surprise the operator.\n * Exit 0 = safe (warnings allowed, printed), 1 = at least one hard failure, 2 = bad args.\n */\n/** One lazily-opened, once-closed libSQL connection for this command's reads\n * (#646 step 4). Lazy for the same reason as `report --digest`'s: a run that\n * returns before any read must not need `TURSO_DATABASE_URL`. */\nfunction lazyFleetDb(): { get: () => Promise<Db>; close: () => Promise<void> } {\n let db: Db | null = null;\n return {\n get: async () => {\n if (!db) {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n db = await openDb(readDbConfig());\n }\n return db;\n },\n close: async () => {\n if (db) await db.destroy();\n },\n };\n}\n\nexport async function runPreflightCommand(\n site: string | undefined,\n opts: PreflightCommandOptions,\n): Promise<{ output: string; code: number }> {\n if (Boolean(site) === Boolean(opts.all)) {\n return { output: \"Provide exactly one of <site> or --all.\", code: 2 };\n }\n const typeKey = (opts.type ?? \"announcement\").toLowerCase();\n const type = TYPES[typeKey];\n if (!type) {\n return {\n output: `Unknown --type '${opts.type}'. Supported: ${Object.keys(TYPES).join(\", \")}`,\n code: 2,\n };\n }\n\n // #646 step 4: the rows come from Turso. ONE connection for both reads, opened\n // lazily on the first read and closed when the checks are done — a run that\n // returns before reading (bad args, an injected preflight) needs no database.\n const { listSites, listAllReports } = await import(\"../../db/fleet-state.js\");\n const fleetDb = lazyFleetDb();\n try {\n const { results, fleet } = await preflight({\n roster: async () => listSites(await fleetDb.get()),\n allReports: async () => listAllReports(await fleetDb.get()),\n ...(site ? { site } : {}),\n ...(opts.all ? { all: true } : {}),\n type,\n });\n if (results.length === 0) return { output: \"No matching sites.\", code: 1 };\n\n const lines = results.map(formatSite);\n for (const f of fleet) lines.push(`[fleet] ${MARK[f.level]} ${f.check}: ${f.message}`);\n\n const all = [...results.flatMap((r) => r.findings), ...fleet];\n const fails = all.filter((f) => f.level === \"fail\").length;\n const warns = all.filter((f) => f.level === \"warn\").length;\n lines.push(\n `Preflight: ${results.length} site(s) — ${fails} fail, ${warns} warn. ${\n fails > 0 ? \"NOT safe to send.\" : \"Safe to send (review warnings).\"\n }`,\n );\n return { output: lines.join(\"\\n\"), code: fails > 0 ? 1 : 0 };\n } catch (err) {\n const e = err as { message?: string; exitCode?: number };\n return { output: e.message ?? String(err), code: e.exitCode ?? 1 };\n } finally {\n await fleetDb.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,IAAM,QAAoC;AAAA,EACxC,cAAc;AAAA,EACd,aAAa;AAAA,EACb,SAAS;AACX;AAEA,IAAM,OAAkD,EAAE,MAAM,UAAK,MAAM,UAAK,MAAM,SAAI;AAE1F,SAAS,WAAW,GAAgC;AAClD,MAAI,EAAE,SAAS,WAAW,EAAG,QAAO,IAAI,EAAE,IAAI;AAC9C,SAAO,EAAE,SAAS,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,KAAK,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AACjG;AAUA,SAAS,cAAsE;AAC7E,MAAI,KAAgB;AACpB,SAAO;AAAA,IACL,KAAK,YAAY;AACf,UAAI,CAAC,IAAI;AACP,cAAM,EAAE,QAAQ,aAAa,IAAI,MAAM,OAAO,sBAAoB;AAClE,aAAK,MAAM,OAAO,aAAa,CAAC;AAAA,MAClC;AACA,aAAO;AAAA,IACT;AAAA,IACA,OAAO,YAAY;AACjB,UAAI,GAAI,OAAM,GAAG,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,oBACpB,MACA,MAC2C;AAC3C,MAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK,GAAG,GAAG;AACvC,WAAO,EAAE,QAAQ,2CAA2C,MAAM,EAAE;AAAA,EACtE;AACA,QAAM,WAAW,KAAK,QAAQ,gBAAgB,YAAY;AAC1D,QAAM,OAAO,MAAM,OAAO;AAC1B,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,MACL,QAAQ,mBAAmB,KAAK,IAAI,iBAAiB,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,MAClF,MAAM;AAAA,IACR;AAAA,EACF;AAKA,QAAM,EAAE,WAAW,eAAe,IAAI,MAAM,OAAO,2BAAyB;AAC5E,QAAM,UAAU,YAAY;AAC5B,MAAI;AACF,UAAM,EAAE,SAAS,MAAM,IAAI,MAAM,UAAU;AAAA,MACzC,QAAQ,YAAY,UAAU,MAAM,QAAQ,IAAI,CAAC;AAAA,MACjD,YAAY,YAAY,eAAe,MAAM,QAAQ,IAAI,CAAC;AAAA,MAC1D,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;AAAA,MAChC;AAAA,IACF,CAAC;AACD,QAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,QAAQ,sBAAsB,MAAM,EAAE;AAEzE,UAAM,QAAQ,QAAQ,IAAI,UAAU;AACpC,eAAW,KAAK,MAAO,OAAM,KAAK,WAAW,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE;AAErF,UAAM,MAAM,CAAC,GAAG,QAAQ,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,GAAG,KAAK;AAC5D,UAAM,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE;AACpD,UAAM,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE;AACpD,UAAM;AAAA,MACJ,cAAc,QAAQ,MAAM,mBAAc,KAAK,UAAU,KAAK,UAC5D,QAAQ,IAAI,sBAAsB,iCACpC;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,GAAG,MAAM,QAAQ,IAAI,IAAI,EAAE;AAAA,EAC7D,SAAS,KAAK;AACZ,UAAM,IAAI;AACV,WAAO,EAAE,QAAQ,EAAE,WAAW,OAAO,GAAG,GAAG,MAAM,EAAE,YAAY,EAAE;AAAA,EACnE,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;","names":[]}
@@ -9,21 +9,26 @@ import {
9
9
  appendSkipNotice,
10
10
  fleetWorkdir,
11
11
  prepareFleetSites
12
- } from "./chunk-LM2NHRCJ.js";
12
+ } from "./chunk-DYTYPKFN.js";
13
13
  import {
14
14
  resolveSites
15
- } from "./chunk-Z2K4JPQ3.js";
15
+ } from "./chunk-FP6HOW6J.js";
16
+ import {
17
+ RESTORED_NOTE,
18
+ refusedByGit,
19
+ undoRefusedWrites
20
+ } from "./chunk-TJLKWQOX.js";
16
21
  import {
17
22
  PRETTIER_FLAG_NOTE,
18
23
  formatWithPrettier,
19
24
  resolveTargetPrettier
20
- } from "./chunk-R53VL3RI.js";
25
+ } from "./chunk-5CJVUPJU.js";
21
26
  import {
22
27
  readGitHubConfig
23
- } from "./chunk-GASBX52O.js";
28
+ } from "./chunk-MVKNU4QD.js";
24
29
  import {
25
30
  makeGitHub
26
- } from "./chunk-XHHMM5HX.js";
31
+ } from "./chunk-43SW3FGW.js";
27
32
  import "./chunk-Z3JXLULD.js";
28
33
  import {
29
34
  defaultSpawn
@@ -39,10 +44,12 @@ import {
39
44
  createBranch,
40
45
  currentBranch,
41
46
  deleteBranch,
47
+ getRemoteUrl,
42
48
  isWorkingTreeClean,
43
49
  push,
44
- resolveOwnerRepo
45
- } from "./chunk-AVEQUR44.js";
50
+ resolveOwnerRepo,
51
+ sameOwnerRepo
52
+ } from "./chunk-WMF5S2MB.js";
46
53
 
47
54
  // src/cli/commands/prismic-ci.ts
48
55
  import { readdir } from "fs/promises";
@@ -209,6 +216,21 @@ async function prismicCi(site, deps = {}) {
209
216
  "could not determine a GitHub repo for this site \u2014 set Airtable 'Git repo', or give the checkout an origin remote whose URL is a GitHub owner/repo"
210
217
  );
211
218
  }
219
+ if (site.gitRepo) {
220
+ let originUrl;
221
+ try {
222
+ originUrl = await getRemoteUrl(site.path);
223
+ } catch {
224
+ originUrl = null;
225
+ }
226
+ if (originUrl !== null && !sameOwnerRepo(repo, originUrl)) {
227
+ return resultOf(
228
+ site,
229
+ "failed",
230
+ `'Git repo' ${repo} does not match the checkout's origin ${originUrl} \u2014 refusing to push into one repo and open the PR in another; fix the Airtable cell or the remote`
231
+ );
232
+ }
233
+ }
212
234
  let isPrismic;
213
235
  try {
214
236
  isPrismic = await readPrismicConfig(site.path) !== null;
@@ -300,6 +322,9 @@ async function prismicCi(site, deps = {}) {
300
322
  try {
301
323
  await createBranch(site.path, branch);
302
324
  const dest = join2(site.path, WORKFLOW_PATH);
325
+ const before = /* @__PURE__ */ new Map([
326
+ [WORKFLOW_PATH, await readFile2(dest, "utf-8").catch(() => null)]
327
+ ]);
303
328
  await mkdir(dirname(dest), { recursive: true });
304
329
  await writeFile(dest, workflow, "utf-8");
305
330
  const notes = [];
@@ -317,6 +342,11 @@ async function prismicCi(site, deps = {}) {
317
342
  );
318
343
  }
319
344
  const sha = await commit(site.path, "ci: deliver Prismic model changes from merged PRs");
345
+ const refusal = await refusedByGit(site.path, [WORKFLOW_PATH], "the Prismic delivery workflow");
346
+ if (refusal) {
347
+ await undoRefusedWrites(site.path, before, refusal.missing);
348
+ return resultOf(site, "failed", refusal.notes + RESTORED_NOTE, sha ? [sha] : []);
349
+ }
320
350
  if (!sha) {
321
351
  return resultOf(
322
352
  site,
@@ -392,7 +422,7 @@ async function runPrismicCiCommand(site, opts, deps = {}) {
392
422
  let sites = await resolveSites({
393
423
  ...site !== void 0 ? { site } : {},
394
424
  ...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
395
- // Passed through because `--fleet airtable` derives each site's path from
425
+ // Passed through because `--fleet turso` derives each site's path from
396
426
  // it; without it the keyword inventory would resolve paths under a different
397
427
  // workdir than the one this run then prepares and commits in.
398
428
  ...opts.workdir !== void 0 ? { workdir: opts.workdir } : {},
@@ -446,4 +476,4 @@ export {
446
476
  formatPrismicCiResults,
447
477
  runPrismicCiCommand
448
478
  };
449
- //# sourceMappingURL=prismic-ci-6GKKZGB5.js.map
479
+ //# sourceMappingURL=prismic-ci-NAPWOXXD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/prismic-ci.ts","../src/recipes/prismic-ci/index.ts","../src/recipes/prismic-ci/template.ts","../src/recipes/prismic-ci/cli-version.ts"],"sourcesContent":["// The fleet face of the `prismic-ci` recipe: land the Prismic model delivery\n// workflow across the fleet, as ONE PULL REQUEST PER REPOSITORY.\n//\n// The blast radius is fifteen live client repositories, and the failure this\n// file is written against is not a bad PR — the recipe's gates handle that, and\n// nothing here can push to a client's main. It is the QUIET one: a run that\n// touched nothing and exited 0, read as a finished rollout. Three facts only the\n// fleet layer can get wrong produce it, and each has a refusal below:\n//\n// - an inventory that resolved NOBODY prints \"0 applied, 0 noop, 0 failed.\"\n// and exits 0, which is indistinguishable from a fleet with nothing left to\n// do. The Airtable inventory is view-filtered; one filter change empties it\n// with no error anywhere.\n// - a site that could not be PREPARED is isolated into `skipped` by\n// `prepareFleetSites` (correctly — one bad row must not abort the fleet) and\n// then disappears from a summary built out of `prepared` alone. It gets a\n// `failed` ROW, because the count is the only thing a machine reads.\n// - a checkout holding `.git` and nothing else answers \"not a Prismic site\" to\n// every read inside it, so the site noops out of the rollout for good — a\n// fleet workdir reuses any non-empty directory forever. Guarded BEFORE the\n// recipe runs: a tree nobody established is not a tree to branch from.\n//\n// SEQUENTIAL, one site at a time, like every other fleet recipe command — see\n// `runRecipeOverSites`. Each site does git work in its own checkout plus a\n// handful of GitHub calls; there is no Airtable write on this path (so the\n// fleet's ≤4.5 req/s throttle does not apply) and nothing parallelism would buy\n// except a burst of writes against fifteen repositories at once.\nimport { readdir } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { prismicCi } from \"../../recipes/prismic-ci/index.js\";\nimport {\n REUSABLE_WORKFLOW_PIN,\n isPinResolved,\n type ReusableWorkflowPin,\n} from \"../../recipes/prismic-ci/template.js\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\nimport { siteLabel } from \"../../util/site.js\";\nimport { noWorkingTreeFailure } from \"../../util/working-tree.js\";\nimport { appendSkipNotice, prepareFleetSites, type SkippedSite } from \"../fleet/prepare-sites.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport { runRecipeOverSites } from \"../fleet/run-recipe-over-sites.js\";\n\nexport type PrismicCiCommandOptions = {\n fleet?: string;\n workdir?: string;\n dry?: boolean;\n cwd?: string;\n};\n\n/** Injected only by tests, so the fleet layer's aggregation — counts, exit code,\n * refusals — is exercised without git, GitHub or a network. Production runs the\n * real recipe. */\nexport type PrismicCiCommandDeps = {\n runRecipe?: (site: Site) => Promise<RecipeResult>;\n /**\n * Which `reddoorla/.github` commit this run would pin. Injected only by tests;\n * production uses the shipped {@link REUSABLE_WORKFLOW_PIN}.\n *\n * It is injectable because the unresolved-pin refusal is a SAFETY behaviour\n * that must be tested in both directions at all times, and a test that reads\n * the shipped constant can only ever exercise whichever direction the constant\n * currently happens to be in. Two tests here did exactly that, and the release\n * that resolved the pin turned them from \"the refusal works\" into a red suite\n * with the refusal itself no longer covered by anything.\n */\n pin?: ReusableWorkflowPin;\n};\n\nconst messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\nconst failedRow = (site: string, notes: string): RecipeResult => ({\n recipe: \"prismic-ci\",\n site,\n status: \"failed\",\n commits: [],\n notes,\n});\n\n/**\n * One line per site plus a counts summary — the shape every fleet rollout\n * prints, so a sweep over fifteen repos is scannable.\n *\n * The banner above the counts is not decoration. A rollout in which every site\n * failed prints fifteen `[site] failed:` lines and then an arithmetic exercise;\n * the one fact an operator needs from that run — NOT ONE repository got the\n * workflow — has to be stated. It is keyed on \"sites were attempted and none was\n * applied\", not on \"nothing was applied\": a fleet that is already fully\n * delivered applies nothing every time it runs, and a banner that shouts at that\n * run is a banner nobody reads at the run above.\n */\nexport function formatPrismicCiResults(results: RecipeResult[]): string {\n const lines = results.map((r) => `[${r.site}] ${r.status}: ${r.notes ?? \"\"}`.trimEnd());\n const n = (s: RecipeResult[\"status\"]) => results.filter((r) => r.status === s).length;\n const failed = n(\"failed\");\n lines.push(\"\");\n if (failed > 0 && n(\"applied\") === 0) {\n lines.push(\n `⛔ NO SITE GOT THE DELIVERY WORKFLOW. ${failed} of ${results.length} site(s) failed and` +\n ` not one pull request was opened, so no site's model changes reach Prismic on merge.` +\n ` Do NOT read this run as a rollout.`,\n );\n lines.push(\"\");\n }\n lines.push(`${n(\"applied\")} applied, ${n(\"noop\")} noop, ${failed} failed.`);\n return lines.join(\"\\n\");\n}\n\n/**\n * Is there a checkout here at all, or the named reason there is not?\n *\n * Runs BEFORE the recipe, for both the reason in {@link noWorkingTreeFailure}\n * and one this command adds: the recipe's next steps create a branch, write a\n * file and commit it. A directory nobody managed to check out is not a tree to\n * do that in, and the answer the recipe would otherwise reach — \"not a Prismic\n * site\" — is the one answer that quietly removes the site from the rollout.\n *\n * A `readdir` that THROWS is its own failure and never the skip: a path that\n * does not exist, or one this process cannot read, is not a repo without\n * Prismic in it.\n */\nasync function checkoutFailure(repoRoot: string): Promise<string | null> {\n let entries: string[];\n try {\n entries = await readdir(repoRoot);\n } catch (e) {\n return `cannot read this checkout at ${repoRoot}: ${messageOf(e)}`;\n }\n return noWorkingTreeFailure(\n repoRoot,\n entries,\n \"No delivery workflow was proposed for it and no pull request was opened\",\n );\n}\n\n/**\n * Roll the delivery workflow out to one site or to the fleet.\n *\n * `resolveSites` is allowed to THROW (a positional site alongside `--fleet`, an\n * unsupported inventory extension, an Airtable read that failed). Those are \"the\n * fleet itself could not be established\", which has no per-site row to live in\n * and must not be reported as a rollout across zero sites; `bin.ts` prints the\n * message and exits with the error's own `exitCode`.\n */\nexport async function runPrismicCiCommand(\n site: string | undefined,\n opts: PrismicCiCommandOptions,\n deps: PrismicCiCommandDeps = {},\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n // Passed through because `--fleet turso` derives each site's path from\n // it; without it the keyword inventory would resolve paths under a different\n // workdir than the one this run then prepares and commits in.\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n // NOTHING RESOLVED IS NOT A DELIVERED FLEET — the refusal `runFleetSweep` and\n // the token doctor both open with, for the same reason: the summary below\n // would print \"0 applied, 0 noop, 0 failed.\" over no rows at all and exit 0,\n // which is exactly what a finished rollout looks like.\n if (sites.length === 0) {\n return {\n output:\n `the inventory resolved NO SITES, so no site was offered the delivery workflow.` +\n ` This is not a delivered fleet — check the inventory (an Airtable view filter, an` +\n ` empty JSON file, a dynamic inventory returning []). Do NOT read this exit as a` +\n ` rollout.`,\n code: 1,\n };\n }\n\n let skipped: SkippedSite[] = [];\n if (opts.fleet) {\n const prep = await prepareFleetSites(sites, { workdir: opts.workdir ?? fleetWorkdir() });\n sites = prep.prepared;\n skipped = prep.skipped;\n }\n\n // THE PIN, checked here as well as in the recipe — not a second copy of the\n // gate but the same exported predicate on the same pin, asked at the one point\n // the recipe cannot answer for: a `--dry` run never reaches the recipe at all,\n // and a preview that promised fifteen pull requests the real run would refuse\n // is a preview of something that cannot happen.\n //\n // ONE pin object serves both this preview and the recipe below, so the preview\n // can never be answering about a different pin than the run it previews.\n const pin = deps.pin ?? REUSABLE_WORKFLOW_PIN;\n const pinUnresolved = !isPinResolved(pin)\n ? `⛔ the reusable-workflow pin is unresolved (${pin.sha}) — every site` +\n ` below would be REFUSED, not delivered. Publish and tag the workflow in` +\n ` reddoorla/.github, then set REUSABLE_WORKFLOW_PIN.`\n : \"\";\n\n if (opts.dry) {\n const lines = sites.map(\n (s) =>\n `[${siteLabel(s)}] would be offered the delivery workflow (no gate was evaluated:` +\n ` the secret, default-branch and already-delivered checks run only on a real run)`,\n );\n return {\n output: appendSkipNotice(\n [pinUnresolved, lines.join(\"\\n\")].filter((s) => s !== \"\").join(\"\\n\\n\"),\n skipped,\n ),\n code: pinUnresolved === \"\" ? 0 : 1,\n };\n }\n\n const run = deps.runRecipe ?? ((s: Site) => prismicCi(s, { pin }));\n const results = await runRecipeOverSites(\"prismic-ci\", sites, async (s) => {\n const failure = await checkoutFailure(s.path);\n return failure ? failedRow(siteLabel(s), failure) : run(s);\n });\n\n // A SITE THAT COULD NOT BE PREPARED IS A SITE THAT DID NOT GET THE WORKFLOW,\n // and it gets a row. `prepareFleetSites` isolates the clone failure into\n // `skipped`, which is right and is also exactly how a site disappears: a\n // summary walking `prepared` alone counts a fleet-wide clone outage — an\n // expired App token, GitHub down, a full disk — as \"0 applied, 0 failed\",\n // exit 0. The notice below stays; the row is what makes the site countable.\n for (const s of skipped) {\n results.push(\n failedRow(\n s.site,\n `could not prepare this checkout: ${s.reason}. No delivery workflow was proposed` +\n ` for it — this is NOT \"this site already has one\".`,\n ),\n );\n }\n\n return {\n output: appendSkipNotice(formatPrismicCiResults(results), skipped),\n code: results.some((r) => r.status === \"failed\") ? 1 : 0,\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { readGitHubConfig } from \"../../github/config.js\";\nimport { makeGitHub, type GitHub } from \"../../github/gh.js\";\nimport { readPrismicConfig } from \"../../prismic/models/index.js\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport {\n branchName,\n checkoutBranch,\n commit as gitCommit,\n createBranch,\n currentBranch,\n deleteBranch,\n getRemoteUrl,\n isWorkingTreeClean,\n push as gitPush,\n resolveOwnerRepo,\n sameOwnerRepo,\n} from \"../../util/git.js\";\nimport { siteLabel } from \"../../util/site.js\";\nimport { formatWithPrettier, resolveTargetPrettier, PRETTIER_FLAG_NOTE } from \"../_prettier.js\";\nimport { refusedByGit, undoRefusedWrites, RESTORED_NOTE } from \"../_head-guard.js\";\nimport {\n APPLY_BRANCH,\n PRISMIC_CI_WORKFLOW,\n REUSABLE_WORKFLOW_PIN,\n SECRET,\n WORKFLOW_PATH,\n isPinResolved,\n prismicCiWorkflow,\n type ReusableWorkflowPin,\n} from \"./template.js\";\nimport { MIN_CLI_VERSION, atLeast, readLockedCliVersion } from \"./cli-version.js\";\n\n/** Head-branch prefix of every PR this recipe opens. `branchName(\"prismic-ci\")`\n * produces `maint/prismic-ci-<timestamp>`, and the re-run guard matches on this\n * prefix — the same idiom as `findOpenSelfUpdatingPR`. */\nconst BRANCH_PREFIX = \"maint/prismic-ci-\";\n\n/** How long the target's prettier gets. Also what makes the fleet's default\n * spawn detach the child, so the kill reaches prettier and not just a wrapper.\n * Matches the pull-down path's budget in `src/prismic/models/write.ts`. */\nconst PRETTIER_TIMEOUT_MS = 60_000;\n\n/** Just the GitHub surface this recipe touches, so a test fake is five methods\n * rather than thirty. `makeGitHub()` satisfies it structurally. */\nexport type PrismicCiGitHub = Pick<\n GitHub,\n \"defaultBranch\" | \"secretExists\" | \"fileContentsOnBranch\" | \"openPullRequest\" | \"openPullRequests\"\n>;\n\nexport type PrismicCiDeps = {\n github?: PrismicCiGitHub;\n pushBranch?: (cwd: string, branch: string) => Promise<void>;\n spawn?: SpawnFn;\n /** Resolve the TARGET repo's own prettier. Injected so a test can assert the\n * absolute-path spawn without a populated `node_modules`. */\n resolvePrettier?: (repoRoot: string) => Promise<string | null>;\n /** Which `reddoorla/.github` commit the written workflow pins. Injected only\n * by tests; production uses the shipped pin. */\n pin?: ReusableWorkflowPin;\n};\n\nconst resultOf = (\n site: Site,\n status: RecipeResult[\"status\"],\n notes: string,\n commits: string[] = [],\n): RecipeResult => ({\n recipe: \"prismic-ci\",\n site: siteLabel(site),\n status,\n commits,\n notes,\n});\n\nconst messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\n/** Same normalization as `self-updating`'s config comparison: CRLF and a stray\n * trailing newline are not drift, and treating them as drift opens a needless\n * PR on every run. Any real content difference still differs. */\nfunction sameWorkflow(current: string, canonical: string): boolean {\n const norm = (s: string) => s.replace(/\\r\\n/g, \"\\n\").replace(/\\s+$/, \"\");\n return norm(current) === norm(canonical);\n}\n\n/**\n * Land the Prismic model delivery workflow in one repo, as a pull request.\n *\n * Everything before the first mutation is a gate, and the gates exist because\n * this recipe's output is the only path in the project that writes to a live\n * client's Prismic repository. Two of them are worth naming:\n *\n * - THE SECRET. A workflow whose `PRISMIC_WRITE_TOKEN` does not exist yet goes\n * red on the repo's first model PR. Landing 15 of those turns a rollout into\n * 15 red repos, so a definitively-absent secret FAILS the site rather than\n * nooping it: \"this site was skipped and a human must act\" is not the same\n * report as \"this site was already done\", and a rollout summary where those\n * two look alike is how a site silently never gets model delivery. A secret\n * whose existence could not be DETERMINED fails too, with different wording —\n * they are different facts and the operator's next move differs.\n *\n * - THE DEFAULT BRANCH. The reusable workflow's apply job hard-codes\n * `refs/heads/main`. On a repo whose default branch is anything else, every\n * model PR would merge green and never reach Prismic. Refuse instead.\n *\n * Nothing here can delete a model: the workflow it installs calls a module that\n * exports no delete path.\n */\nexport async function prismicCi(site: Site, deps: PrismicCiDeps = {}): Promise<RecipeResult> {\n // 1. Repo identity — from Airtable's 'Git repo' or, on a positional run, the\n // checkout's origin. `failed`, not `noop`: a rollout that silently skips a site\n // is the failure this whole recipe is guarding against one level up. The\n // strict shape check runs BEFORE the first `gh` call, so a typo'd or\n // attacker-controlled value can never be interpolated into an API path.\n let repo: string | null;\n try {\n repo = await resolveOwnerRepo(site);\n } catch (err) {\n // A malformed identity aborts before any `gh` write — surfaced as a recipe\n // failure rather than being interpolated into an API path.\n return resultOf(site, \"failed\", `could not resolve the repo identity: ${messageOf(err)}`);\n }\n if (!repo) {\n return resultOf(\n site,\n \"failed\",\n \"could not determine a GitHub repo for this site — set Airtable 'Git repo', or give \" +\n \"the checkout an origin remote whose URL is a GitHub owner/repo\",\n );\n }\n\n // 1b. The two identities must agree (#713). `gitPush` goes to the CHECKOUT's\n // origin; `openPullRequest` goes to `repo`, which Airtable's 'Git repo'\n // wins when set. When they name different repositories the branch lands\n // in one and the PR is filed in the other with a head that does not\n // exist there — a 422, but only AFTER a real push into a client repo,\n // which the `finally` restore below does not undo. A stale cell after a\n // rename, a fork as origin, a row copy-pasted from another client: all\n // operator-data problems, so refuse before the first write rather than\n // guess which side is right. A checkout with NO origin is left alone —\n // the push itself will fail there, before anything reaches GitHub.\n if (site.gitRepo) {\n let originUrl: string | null;\n try {\n originUrl = await getRemoteUrl(site.path);\n } catch {\n originUrl = null;\n }\n if (originUrl !== null && !sameOwnerRepo(repo, originUrl)) {\n return resultOf(\n site,\n \"failed\",\n `'Git repo' ${repo} does not match the checkout's origin ${originUrl} — refusing to ` +\n \"push into one repo and open the PR in another; fix the Airtable cell or the remote\",\n );\n }\n }\n\n // 2. Is this a Prismic site? `readPrismicConfig` returns null for \"no Prismic\n // here\" and THROWS for a present-but-unreadable config — the distinction is\n // the point, so the throw becomes `failed` rather than being caught into\n // the skip branch.\n let isPrismic: boolean;\n try {\n isPrismic = (await readPrismicConfig(site.path)) !== null;\n } catch (err) {\n return resultOf(site, \"failed\", `could not read the Prismic config: ${messageOf(err)}`);\n }\n if (!isPrismic) return resultOf(site, \"noop\", \"not a Prismic site (no repositoryName) — skipped\");\n\n // 3. The pin. Checked before any network call: while it is unresolved NOTHING\n // can roll out, and 15 pointless API round-trips are not worth spending to\n // find that out per site.\n const pin = deps.pin ?? REUSABLE_WORKFLOW_PIN;\n if (!isPinResolved(pin)) {\n return resultOf(\n site,\n \"failed\",\n `reusable-workflow pin is unresolved (${pin.sha}) — publish + tag the workflow in ` +\n `reddoorla/.github and set REUSABLE_WORKFLOW_PIN before rolling out`,\n );\n }\n const workflow = pin === REUSABLE_WORKFLOW_PIN ? PRISMIC_CI_WORKFLOW : prismicCiWorkflow(pin);\n\n // 3b. THE BINARY THAT WILL ACTUALLY RUN. The reusable workflow does not\n // install this CLI — it runs `pnpm install --frozen-lockfile` and then the\n // site's OWN installed bin, so the version that executes is whatever this\n // repo's lockfile pins. Installing the caller next to a binary with no\n // `prismic-models` command yields a workflow that fails on the first model\n // PR, in a client repo, with an error naming an unknown command rather\n // than a rollout that ran too early.\n //\n // Local and cheap, so it sits with the pin gate ahead of the first network\n // call. And it is a REFUSAL on \"could not establish\", not a pass: the\n // asymmetry is deliberate, because the cost of waiting is a re-run and the\n // cost of being wrong is broken CI on someone else's repository.\n const locked = await readLockedCliVersion(site.path);\n if (!locked.ok) {\n return resultOf(\n site,\n \"failed\",\n `cannot establish which @reddoorla/maintenance version this repo's CI would run ` +\n `(${locked.reason}) — refusing to install a workflow that may not run`,\n );\n }\n if (!atLeast(locked.version, MIN_CLI_VERSION)) {\n return resultOf(\n site,\n \"failed\",\n `this repo's lockfile pins @reddoorla/maintenance ${locked.version}, which has no ` +\n `\\`prismic-models\\` command (first shipped in ${MIN_CLI_VERSION}) — bump the dependency ` +\n `and commit the lockfile first, or the workflow fails on its first model PR`,\n );\n }\n\n const ghConfig = readGitHubConfig();\n if (!deps.github && !ghConfig) return resultOf(site, \"failed\", \"GITHUB_TOKEN not set\");\n const gh = deps.github ?? makeGitHub({ token: ghConfig!.token });\n const spawn = deps.spawn ?? defaultSpawn;\n\n // 4. The secret. Absent and unreadable are separate answers with separate\n // wording; neither proceeds.\n let hasSecret: boolean;\n try {\n hasSecret = await gh.secretExists(repo, SECRET);\n } catch (err) {\n return resultOf(\n site,\n \"failed\",\n `could not determine whether ${repo} has the ${SECRET} secret (${messageOf(err)}) — ` +\n `refusing to install a workflow that may have no token`,\n );\n }\n if (!hasSecret) {\n return resultOf(\n site,\n \"failed\",\n `${repo} has no ${SECRET} Actions secret — the workflow reds the repo on its first ` +\n `model PR without it. Mint one, then: gh secret set ${SECRET} --repo ${repo}`,\n );\n }\n\n // 5. The default branch. NOT `.catch(() => \"main\")`: \"I could not read the\n // default branch\" and \"the default branch is main\" are opposite facts, and\n // guessing the second targets a PR (and an apply gate) at a branch nobody\n // confirmed.\n let base: string;\n try {\n base = await gh.defaultBranch(repo);\n } catch (err) {\n return resultOf(\n site,\n \"failed\",\n `could not read the default branch of ${repo}: ${messageOf(err)}`,\n );\n }\n if (base !== APPLY_BRANCH) {\n return resultOf(\n site,\n \"failed\",\n `default branch of ${repo} is ${base}, not ${APPLY_BRANCH} — the reusable workflow's ` +\n `apply job guards refs/heads/${APPLY_BRANCH}, so merged model changes would never reach Prismic`,\n );\n }\n\n // 6. Already delivered? Content-compared, not existence-compared, so a\n // present-but-STALE workflow (an older pin) is corrected rather than left\n // forever.\n const existing = await gh.fileContentsOnBranch(repo, base, WORKFLOW_PATH);\n if (existing !== null && sameWorkflow(existing, workflow)) {\n return resultOf(site, \"noop\", `delivery workflow already current on ${base}`);\n }\n\n // 7. Already proposed? Without this, every run before the PR merges opens\n // another one. `fileContentsOnBranch` also answers null for a read it could\n // not perform (gh exits non-zero on both a 404 and an auth failure), which\n // makes this the backstop for that collapse as well.\n const open = (await gh.openPullRequests(repo)).find((pr) => pr.headRef.startsWith(BRANCH_PREFIX));\n if (open) {\n return resultOf(site, \"noop\", `delivery workflow PR already open: ${open.url}`);\n }\n\n if (!(await isWorkingTreeClean(site.path))) {\n return resultOf(site, \"failed\", \"working tree not clean — commit or stash first\");\n }\n\n // Capture the operator's branch BEFORE creating ours so the `finally` can put\n // them back. Best-effort: if we cannot read it we skip the restore rather than\n // guess at a branch to check out.\n let original: string | null = null;\n try {\n original = await currentBranch(site.path);\n } catch {\n // Stays null (detached HEAD, git error) — the restore below is then skipped\n // rather than aiming a checkout at a branch name we had to invent.\n }\n const branch = branchName(\"prismic-ci\");\n const commits: string[] = [];\n let pushedOrOpened = false;\n\n try {\n await createBranch(site.path, branch);\n const dest = join(site.path, WORKFLOW_PATH);\n // What the path held before this run, so a write git then refuses can be put\n // back exactly as it was (null = the file did not exist).\n const before = new Map<string, string | null>([\n [WORKFLOW_PATH, await readFile(dest, \"utf-8\").catch(() => null)],\n ]);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, workflow, \"utf-8\");\n\n // Format with the SITE's own prettier, resolved POSITIVELY and run by\n // absolute path. `pnpm exec prettier` here would run a full `pnpm install`\n // in the client's repo first (an unrequested mutation of a live client repo\n // by a rollout that is supposed to add one file), and in a repo without\n // prettier would then format with the CALLING repo's binary and exit 0.\n const notes: string[] = [];\n const bin = await (deps.resolvePrettier ?? resolveTargetPrettier)(site.path);\n if (bin === null) {\n notes.push(PRETTIER_FLAG_NOTE);\n } else if (\n !(await formatWithPrettier(spawn, site.path, [WORKFLOW_PATH], {\n bin,\n timeoutMs: PRETTIER_TIMEOUT_MS,\n }))\n ) {\n notes.push(PRETTIER_FLAG_NOTE);\n } else if (!sameWorkflow(await readFile(dest, \"utf-8\"), workflow)) {\n // The site's prettier config disagrees with the template's shape (single\n // quotes, a different tab width). Harmless for the workflow — but the\n // step-6 comparison is byte-based, so this site will not recognise its own\n // delivered workflow as current and will propose it again after each\n // merge. Say so rather than let it be discovered as a mystery PR.\n notes.push(\n \"this site's prettier reformatted the workflow — re-runs will not match it as current\",\n );\n }\n\n const sha = await gitCommit(site.path, \"ci: deliver Prismic model changes from merged PRs\");\n\n // Did git TAKE the workflow? Here the drop does not even read as a failure:\n // with the path ignored, `commit()` stages nothing, returns null, and the\n // branch below reports \"already present and identical in the checkout\" — a\n // statement about a file that is in no commit at all. Checked BEFORE the\n // push, so nothing reaches the client repo on a refusal (#741).\n const refusal = await refusedByGit(site.path, [WORKFLOW_PATH], \"the Prismic delivery workflow\");\n if (refusal) {\n await undoRefusedWrites(site.path, before, refusal.missing);\n return resultOf(site, \"failed\", refusal.notes + RESTORED_NOTE, sha ? [sha] : []);\n }\n\n if (!sha) {\n // Git saw no change: the checkout already holds this exact workflow even\n // though the default branch does not. Nothing to push and nothing to\n // review — and pushing a commit-less branch would fail at PR creation.\n return resultOf(\n site,\n \"noop\",\n `${WORKFLOW_PATH} already present and identical in the checkout`,\n );\n }\n commits.push(sha);\n pushedOrOpened = true;\n await (deps.pushBranch ?? gitPush)(site.path, branch);\n const pr = await gh.openPullRequest(repo, {\n head: branch,\n base,\n title: \"Deliver Prismic model changes from merged PRs\",\n body:\n \"Adds the `prismic-models` workflow. On a PR touching `customtypes/**` or \" +\n \"`src/lib/slices/**/model.json` it comments the model delta and writes nothing; \" +\n \"on merge to main it pushes those models to Prismic. It can create and update \" +\n \"models but never delete — a model present only in Prismic is reported, not touched.\",\n });\n notes.unshift(`opened PR ${pr.url}`);\n return resultOf(site, \"applied\", notes.join(\"; \"), commits);\n } catch (err) {\n return resultOf(site, \"failed\", messageOf(err), commits);\n } finally {\n // Restore the operator's branch on success AND on failure — otherwise a push\n // error strands the checkout on the recipe branch with an unpushed commit,\n // and the retry dies at createBranch. Best-effort; never masks the result.\n if (original !== null && original !== branch) {\n try {\n await checkoutBranch(site.path, original);\n // Nothing was pushed and no PR exists, so the local branch is litter and\n // a re-run would leave another. Only ever the branch WE created.\n if (!pushedOrOpened) await deleteBranch(site.path, branch);\n } catch (err) {\n console.warn(\n `warning: could not restore branch ${original} after prismic-ci: ${messageOf(err)}`,\n );\n }\n }\n }\n}\n","/**\n * The caller workflow every Prismic site gets.\n *\n * Fully generic — no per-site values — because the token is the site's own\n * `PRISMIC_WRITE_TOKEN` secret, the name every site's code already reads.\n * Contrast `ci.yml`, which is deliberately NOT templated anywhere in this\n * package because it carries per-site `netlify-site:` / `node-version:` values\n * a byte template would strip fleet-wide.\n */\n\n/** Where the file lands in a site repo. Also the `gh api .../contents/<path>`\n * segment, so it must stay traversal-free (`assertUrlSegment` re-checks). */\nexport const WORKFLOW_PATH = \".github/workflows/prismic-models.yml\";\n\n/** The Actions secret the reusable workflow declares as `required: true`, and\n * the environment variable the CLI reads. Spelled ONCE, here, because the\n * failure mode of a second spelling is silent: a caller passing a name the\n * callee never declared supplies nothing, and the callee's required secret\n * arrives empty at the one step that writes to a live client's Prismic repo. */\nexport const SECRET = \"PRISMIC_WRITE_TOKEN\";\n\n/** The reusable workflow, without a ref. */\nexport const REUSABLE_WORKFLOW = \"reddoorla/.github/.github/workflows/prismic-models.yml\";\n\n/**\n * The ONE branch this delivery path can run its apply job on.\n *\n * Not a preference — the reusable workflow's apply job hard-codes\n * `github.ref == 'refs/heads/main'`. On a repo whose default branch is anything\n * else, a merged model PR would produce a green check and never reach Prismic,\n * which is why the recipe refuses such a repo instead of installing a workflow\n * that silently cannot fire.\n */\nexport const APPLY_BRANCH = \"main\";\n\n/** A commit in `reddoorla/.github`, plus the release tag that commit carries.\n * Both are written into the workflow: the SHA is what Actions resolves, the tag\n * is what a human (and Renovate's github-actions manager) reads. */\nexport type ReusableWorkflowPin = { sha: string; tag: string };\n\n/**\n * What the shipped pin says while the reusable workflow has not been published\n * to `reddoorla/.github` and tagged yet (plan Task 25).\n *\n * DELIBERATELY NOT 40 HEX CHARACTERS. A placeholder shaped like a real SHA is\n * indistinguishable from a real one in review, and would install a workflow\n * referencing a commit that does not exist into 15 client repositories — where\n * it fails at workflow-load time, on every model PR, with an error that names\n * neither this file nor the reason. Unresolved must be unmistakable, and\n * {@link isPinResolved} is what the recipe gates on.\n */\nexport const UNRESOLVED_PIN_SHA = \"UNRESOLVED-publish-and-tag-reddoorla-dot-github-first\";\n\n/**\n * The pin the fleet rolls out.\n *\n * RESOLVED 2026-08-14. `reddoorla/.github#30` merged as\n * 558395431ddcb481ecba3dd84b78b38c338cfa03 and that commit was tagged v1.4.0;\n * `gh api repos/reddoorla/.github/commits/v1.4.0 --jq .sha` returns the SHA\n * below. `validate` (the digest-pinning + Renovate-preset check that is the\n * only CI in that repo) passed on the merge commit.\n *\n * TO RE-RESOLVE after a future `reddoorla/.github` release: set `sha` to\n * `gh api repos/reddoorla/.github/commits/<tag> --jq .sha` and `tag` to that\n * tag — then VERIFY PROPAGATION rather than assuming it. Renovate does bump\n * this ref: 17 site repos took v1.2.0 -> v1.3.0 in the weeks after v1.3.0\n * was tagged 2026-07-14, on Renovate's weekly `renovate/all-minor-patch`\n * branches (some of which were already open before the tag, so a PR's\n * creation date can predate the version it ends up bumping). It runs self-hosted here and its author identity has already\n * changed once mid-window (operator PAT, authoring as `tucksravin`, until\n * 2026-08-02; `reddoor-renovate[bot]` since), so no single author filter\n * matches all 17 and `app/renovate` matches none of them. Search the\n * `renovate/*` head branch, not an author; an empty author-filtered search\n * is not evidence. But it proposed neither\n * of the last two tags for `ci.yml`: v1.4.0 (2026-08-14) was never offered,\n * and v1.4.1 (2026-09-01) reached the fleet only because ci.yml was swept BY\n * HAND across 21 repos that day (beachfront-dentistry#36). Treat propagation\n * as three steps to run, not a guarantee: (1) re-resolve this constant,\n * (2) release @reddoorla/maintenance, (3) `reddoor-maint prismic-ci --fleet\n * airtable` — the already-delivered gate content-compares the installed file\n * and opens a corrective PR per stale repo — plus ONE POSITIONAL RUN PER\n * PRE-LAUNCH SITE, which the Airtable inventory excludes\n * (`src/inventory/airtable.ts:50` filters `building`/`launching`).\n */\nexport const REUSABLE_WORKFLOW_PIN: ReusableWorkflowPin = {\n sha: \"558395431ddcb481ecba3dd84b78b38c338cfa03\",\n tag: \"v1.4.0\",\n};\n\n/**\n * Is this pin a real commit reference?\n *\n * A full 40-hex SHA and nothing else. Not a tag, not a branch: this repo pins\n * every `uses:` to a commit (see `src/recipes/sync-configs/templates.ts`, whose\n * renovate template spells out why — a retagged `@v3` runs attacker code with\n * whatever token the workflow holds). Do not assume the pin bumps itself:\n * Renovate has proposed this ref in the past but did not for either of the\n * last two tags — see the propagation note on\n * {@link REUSABLE_WORKFLOW_PIN} above.\n */\nexport function isPinResolved(pin: ReusableWorkflowPin): boolean {\n return /^[0-9a-f]{40}$/.test(pin.sha);\n}\n\n/**\n * Render the caller workflow for a pin.\n *\n * Path-filtered on both triggers: this must not run on every commit, only when\n * a model changes.\n */\nexport function prismicCiWorkflow(pin: ReusableWorkflowPin): string {\n return `# Delivers this site's Prismic model changes. Managed by @reddoorla/maintenance\n# (\\`reddoor-maint prismic-ci\\`) — change it there and re-run, not here.\n#\n# On a PR touching a model: comment the delta, write nothing.\n# On merge to ${APPLY_BRANCH}: push those models to Prismic. Never delete.\n#\n# THE BRANCH FILTER ON \\`push:\\` IS LOAD-BEARING, not tidiness. The reusable\n# workflow's apply job also guards \\`github.ref == 'refs/heads/${APPLY_BRANCH}'\\`,\n# and its comment calls this the other half of that gate: an unfiltered\n# \\`on: push:\\` fires on every branch AND every tag, so a feature branch's models\n# would reach production with no pull request and therefore no review anywhere in\n# the sequence. Keep both halves.\nname: prismic-models\n\non:\n # No branch filter here, deliberately: a model PR into any base deserves its\n # delta comment, and the dry job is INCAPABLE of writing — it never passes\n # \\`--apply\\`, and it holds no job that does.\n pull_request:\n paths:\n - \"customtypes/**\"\n - \"src/lib/slices/**/model.json\"\n push:\n branches: [${APPLY_BRANCH}]\n paths:\n - \"customtypes/**\"\n - \"src/lib/slices/**/model.json\"\n\njobs:\n prismic-models:\n # A called workflow's jobs can only NARROW what the caller granted, so the\n # PR-comment permission has to be granted here as well as there. Nothing in\n # either job pushes code, so \\`contents\\` stays read.\n permissions:\n contents: read\n pull-requests: write\n uses: ${REUSABLE_WORKFLOW}@${pin.sha} # ${pin.tag}\n secrets:\n # Spelled exactly as the reusable workflow declares it. A workflow-local\n # alias would not be a rename — it would be an undeclared secret, and the\n # required one would arrive empty.\n ${SECRET}: \\${{ secrets.${SECRET} }}\n`;\n}\n\n/** The workflow as currently shipped. Unusable — and refused by the recipe —\n * until {@link REUSABLE_WORKFLOW_PIN} is resolved. */\nexport const PRISMIC_CI_WORKFLOW = prismicCiWorkflow(REUSABLE_WORKFLOW_PIN);\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * The first published version whose `reddoor-maint` bin carries `prismic-models`.\n *\n * VERIFIED BY INSTALLING IT, not by reading a changelog: `npm i\n * @reddoorla/maintenance@0.83.0` in an empty directory, then `reddoor-maint\n * prismic-models --help`, prints the command's usage. Every earlier published\n * version — 0.82.0 included — exits with \"unknown command\".\n *\n * This matters because the reusable workflow does NOT install the CLI. It runs\n * `pnpm install --frozen-lockfile` and then the site's own installed bin, so the\n * version that executes in a client repo is whatever that repo's lockfile pins.\n * Installing the caller workflow next to an older binary produces a workflow\n * that fails at the first model PR, in a client repo, with an error about an\n * unknown command rather than about a rollout that ran too early.\n */\nexport const MIN_CLI_VERSION = \"0.83.0\";\n\n/** The dependency whose version decides whether the workflow can run at all. */\nconst PACKAGE = \"@reddoorla/maintenance\";\n\n/**\n * `version >= min`, compared NUMERICALLY, position by position.\n *\n * Not a string comparison, and not `localeCompare`: as strings `\"0.9.0\" >\n * \"0.83.0\"`, so a lexicographic gate waves through a site pinned seventy-four\n * releases before the command existed. That is the single most likely way for\n * this gate to be wrong while looking right.\n *\n * A prerelease (`0.83.0-beta.1`) sorts BELOW the release it precedes, per\n * semver. The fleet publishes no prereleases today; the rule is here so that if\n * one ever appears it is not silently treated as the release.\n */\nexport function atLeast(version: string, min: string): boolean {\n const parse = (v: string): { nums: number[]; pre: boolean } => {\n const [core = \"\", ...rest] = v.split(\"-\");\n return {\n nums: core.split(\".\").map((n) => Number.parseInt(n, 10) || 0),\n pre: rest.length > 0,\n };\n };\n const a = parse(version);\n const b = parse(min);\n for (let i = 0; i < 3; i++) {\n const av = a.nums[i] ?? 0;\n const bv = b.nums[i] ?? 0;\n if (av !== bv) return av > bv;\n }\n // Cores equal: a prerelease of the minimum is below it; anything else is equal.\n if (a.pre && !b.pre) return false;\n return true;\n}\n\n/** Either the version pnpm resolved, or WHY that could not be established.\n * The two are deliberately different shapes: a caller cannot read \"I could not\n * tell\" as a version by forgetting to check a flag. */\nexport type LockedCliVersion = { ok: true; version: string } | { ok: false; reason: string };\n\n/**\n * The version of {@link PACKAGE} the repo's committed pnpm lockfile resolves.\n *\n * Reads the LOCKFILE, never `package.json`. The declared range is not what\n * executes: espada declares `^0.81.0` and its lockfile resolves 0.69.0, and CI\n * installs `--frozen-lockfile`. A gate reading the range would pass a site whose\n * install is two dozen releases behind it.\n *\n * Parsed with a targeted scan rather than a YAML dependency — this needs one\n * field out of one block, the repo carries no YAML parser, and adding one to\n * read a single `version:` line is not a trade worth making. The scan is\n * deliberately narrow: it reads only `importers:` entries (what is installed),\n * never the `packages:`/`snapshots:` sections, which list every transitively\n * resolvable version and would happily report one nobody installed.\n *\n * Every failure is a NAMED reason, never a version and never a silent default.\n * A gate that cannot tell \"too old\" from \"could not read\" reports one as the\n * other, which is the absent-vs-unreadable collapse this whole feature exists to\n * prevent — arriving here at the last step before writing to a client repo.\n */\nexport async function readLockedCliVersion(repoRoot: string): Promise<LockedCliVersion> {\n let raw: string;\n try {\n raw = await readFile(join(repoRoot, \"pnpm-lock.yaml\"), \"utf-8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // ENOENT is the only error meaning \"this repo has no lockfile\". EACCES and\n // friends mean it is there and unreadable — a different answer, and one that\n // must not read as \"not a pnpm site\".\n if (code === \"ENOENT\") {\n return {\n ok: false,\n reason: `no pnpm-lock.yaml in the checkout — cannot tell which ${PACKAGE} version CI would run`,\n };\n }\n return { ok: false, reason: `pnpm-lock.yaml is present but unreadable (${String(err)})` };\n }\n\n // Only inside `importers:` — see the note above on packages:/snapshots:.\n const lines = raw.split(\"\\n\");\n const importersAt = lines.findIndex((l) => /^importers:\\s*$/.test(l));\n if (importersAt === -1) {\n return {\n ok: false,\n reason: \"pnpm-lock.yaml has no `importers:` section — unrecognised lockfile shape\",\n };\n }\n const end = lines.findIndex((l, i) => i > importersAt && /^[a-zA-Z]/.test(l));\n const importers = lines.slice(importersAt + 1, end === -1 ? lines.length : end);\n\n const found = new Set<string>();\n for (let i = 0; i < importers.length; i++) {\n if (!new RegExp(`^\\\\s+'?${PACKAGE.replace(\"/\", \"\\\\/\")}'?:\\\\s*$`).test(importers[i]!)) continue;\n // The entry's own `version:` sits within the next few lines, alongside\n // `specifier:`. Bounded so a malformed block cannot walk into the next\n // dependency and attribute its version to this one.\n for (let j = i + 1; j < Math.min(i + 4, importers.length); j++) {\n const m = /^\\s+version:\\s*(\\S+)\\s*$/.exec(importers[j]!);\n if (!m) continue;\n // Strip the peer-dependency suffix: real fleet lockfiles run it past 400\n // characters after the semver.\n found.add(m[1]!.split(\"(\")[0]!);\n break;\n }\n }\n\n if (found.size === 0) {\n return { ok: false, reason: `${PACKAGE} is not a dependency in this repo's lockfile` };\n }\n if (found.size > 1) {\n // Picking one would be a coin flip deciding whether a client repo gets a\n // workflow its binary cannot run.\n return {\n ok: false,\n reason: `lockfile resolves ${PACKAGE} to more than one version (${[...found].sort().join(\", \")}) — refusing to guess which one CI would run`,\n };\n }\n return { ok: true, version: [...found][0]! };\n}\n"],"mappings":"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","names":["readFile","join","join","readFile","messageOf"]}