@reddoorla/maintenance 0.95.1 → 0.96.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.
- package/README.md +37 -2
- package/dist/airtable-SJV4ZSMS.js +9 -0
- package/dist/{announce-CLQYX5I4.js → announce-45HVP4J4.js} +7 -7
- package/dist/{blux-4KNXUP2K.js → blux-RUFMNOUJ.js} +5 -5
- package/dist/{bump-deps-C5G2EFBB.js → bump-deps-EL3TKMIL.js} +6 -6
- package/dist/{chunk-XN37VTDB.js → chunk-37B4LVB3.js} +2 -2
- package/dist/{chunk-BVEDKMNG.js → chunk-3EA43S3R.js} +28 -10
- package/dist/chunk-3EA43S3R.js.map +1 -0
- package/dist/{chunk-XHHMM5HX.js → chunk-43SW3FGW.js} +63 -1
- package/dist/chunk-43SW3FGW.js.map +1 -0
- package/dist/{chunk-WMBLR5VA.js → chunk-44SDGPGQ.js} +3 -3
- package/dist/chunk-4UTJ4G6P.js +52 -0
- package/dist/chunk-4UTJ4G6P.js.map +1 -0
- package/dist/{chunk-R53VL3RI.js → chunk-5CJVUPJU.js} +1 -1
- package/dist/{chunk-R53VL3RI.js.map → chunk-5CJVUPJU.js.map} +1 -1
- package/dist/{chunk-KJRVMMM3.js → chunk-62YWOFVN.js} +36 -5
- package/dist/chunk-62YWOFVN.js.map +1 -0
- package/dist/{chunk-VSGMCCGQ.js → chunk-6INOBNRO.js} +2 -2
- package/dist/{chunk-BYGPUUCT.js → chunk-72FJZ6LO.js} +43 -11
- package/dist/chunk-72FJZ6LO.js.map +1 -0
- package/dist/{chunk-5GUJ36TL.js → chunk-74QRWE6G.js} +2 -2
- package/dist/chunk-A5QKNOXW.js +72 -0
- package/dist/chunk-A5QKNOXW.js.map +1 -0
- package/dist/{chunk-2NQUCS7O.js → chunk-BIYQN7FH.js} +3 -1
- package/dist/{chunk-2NQUCS7O.js.map → chunk-BIYQN7FH.js.map} +1 -1
- package/dist/{chunk-S7RYFUIK.js → chunk-CA6ADGOC.js} +2 -2
- package/dist/chunk-CGQZFQAY.js +38 -0
- package/dist/chunk-CGQZFQAY.js.map +1 -0
- package/dist/{chunk-YLYXW5PH.js → chunk-CXICCLNM.js} +34 -4
- package/dist/chunk-CXICCLNM.js.map +1 -0
- package/dist/{chunk-KZRR4NFG.js → chunk-D3CTOCUR.js} +3 -3
- package/dist/{chunk-CWXTNPNZ.js → chunk-DVJNYPFR.js} +3 -3
- package/dist/chunk-DVJNYPFR.js.map +1 -0
- package/dist/{chunk-AVEQUR44.js → chunk-EASYCD6F.js} +60 -17
- package/dist/chunk-EASYCD6F.js.map +1 -0
- package/dist/{chunk-BCKEP5S6.js → chunk-ECDJDHKT.js} +2 -2
- package/dist/{chunk-BI2VI6EM.js → chunk-EQQ4H6L3.js} +2 -2
- package/dist/{chunk-WVNDEYXL.js → chunk-GEUC5VGM.js} +11 -5
- package/dist/chunk-GEUC5VGM.js.map +1 -0
- package/dist/{chunk-Z2K4JPQ3.js → chunk-H445EH33.js} +2 -2
- package/dist/chunk-IHHAYXKY.js +351 -0
- package/dist/chunk-IHHAYXKY.js.map +1 -0
- package/dist/{chunk-FUWFLAWQ.js → chunk-IMYL67JV.js} +3 -3
- package/dist/{chunk-3Z6X3BHU.js → chunk-KN52PM63.js} +7 -2
- package/dist/{chunk-3Z6X3BHU.js.map → chunk-KN52PM63.js.map} +1 -1
- package/dist/{chunk-WJMPCVOR.js → chunk-LAJ3FF54.js} +12 -4
- package/dist/chunk-LAJ3FF54.js.map +1 -0
- package/dist/{chunk-5UINJNAJ.js → chunk-MZZDMBDT.js} +16 -3
- package/dist/chunk-MZZDMBDT.js.map +1 -0
- package/dist/{chunk-DFKBTBO3.js → chunk-NVLREB5R.js} +35 -6
- package/dist/chunk-NVLREB5R.js.map +1 -0
- package/dist/{chunk-R5IMFAFR.js → chunk-OTEVKRAH.js} +3 -3
- package/dist/{chunk-2ZAZ7ETT.js → chunk-P4A6D6HO.js} +4 -4
- package/dist/{chunk-XPJQXSC4.js → chunk-P7323YVW.js} +27 -5
- package/dist/chunk-P7323YVW.js.map +1 -0
- package/dist/{chunk-4763KQ6O.js → chunk-PXRN2MER.js} +2 -2
- package/dist/{chunk-TR3HFH6J.js → chunk-QFSL2L2B.js} +67 -10
- package/dist/chunk-QFSL2L2B.js.map +1 -0
- package/dist/{chunk-2LYOAB2E.js → chunk-RRE74VWI.js} +2 -2
- package/dist/{chunk-LM2NHRCJ.js → chunk-SHGIDTYF.js} +4 -4
- package/dist/chunk-SHGIDTYF.js.map +1 -0
- package/dist/{chunk-ZQV7UQNE.js → chunk-SQ4FSNYL.js} +2 -2
- package/dist/{chunk-C3IQQKXL.js → chunk-T76MA2FH.js} +110 -1
- package/dist/{chunk-C3IQQKXL.js.map → chunk-T76MA2FH.js.map} +1 -1
- package/dist/{chunk-6R56OKJZ.js → chunk-UIITN37J.js} +2 -2
- package/dist/{chunk-CJ2RMTT7.js → chunk-VEIIUCYL.js} +2 -2
- package/dist/{chunk-SVAAONYB.js → chunk-WQLNUEZL.js} +12 -2
- package/dist/{chunk-SVAAONYB.js.map → chunk-WQLNUEZL.js.map} +1 -1
- package/dist/{chunk-WSJ7RO54.js → chunk-XED2DNAI.js} +23 -5
- package/dist/chunk-XED2DNAI.js.map +1 -0
- package/dist/{chunk-FSGICFXB.js → chunk-XG2A6GIG.js} +17 -5
- package/dist/chunk-XG2A6GIG.js.map +1 -0
- package/dist/{chunk-TO7DJYLZ.js → chunk-XY7YV2YQ.js} +178 -39
- package/dist/chunk-XY7YV2YQ.js.map +1 -0
- package/dist/{chunk-HTABUCF4.js → chunk-Y5BVR4HW.js} +3 -3
- package/dist/{chunk-ZCL7C5LH.js → chunk-Y6FCXRIC.js} +1661 -387
- package/dist/chunk-Y6FCXRIC.js.map +1 -0
- package/dist/{chunk-2SLED3NB.js → chunk-YDZ7XGPZ.js} +1 -1
- package/dist/{chunk-2SLED3NB.js.map → chunk-YDZ7XGPZ.js.map} +1 -1
- package/dist/{chunk-QF6IQQFP.js → chunk-Z7FDJ35L.js} +18 -12
- package/dist/chunk-Z7FDJ35L.js.map +1 -0
- package/dist/{chunk-5EEMTJMT.js → chunk-ZH5LOQET.js} +4 -4
- package/dist/cli/bin.js +49 -31
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +18 -18
- package/dist/client-L7NAUG6J.js +11 -0
- package/dist/configs/playwright-a11y.d.ts +11 -1
- package/dist/configs/playwright-a11y.js +3 -1
- package/dist/{convert-to-pnpm-YTFREUJK.js → convert-to-pnpm-4DON7IFS.js} +6 -6
- package/dist/{db-XKZYDCKM.js → db-5K4CDEGJ.js} +45 -20
- package/dist/db-5K4CDEGJ.js.map +1 -0
- package/dist/deadletter-G4M3HSVM.js +17 -0
- package/dist/{digest-7UBGUBRK.js → digest-5AJGWS33.js} +45 -22
- package/dist/digest-5AJGWS33.js.map +1 -0
- package/dist/{digest-collectors-DJFN5ZDK.js → digest-collectors-MC5NWEUA.js} +12 -10
- package/dist/{digest-state-3KHZF7GD.js → digest-state-ZDPKR7MW.js} +18 -2
- package/dist/digest-state-ZDPKR7MW.js.map +1 -0
- package/dist/{ensure-site-OAAE5YE2.js → ensure-site-XESZHZEB.js} +39 -11
- package/dist/ensure-site-XESZHZEB.js.map +1 -0
- package/dist/{fleet-state-UEVZ3X3J.js → fleet-state-GJWGKTLD.js} +6 -4
- package/dist/{forms-notify-target-UC6PF3AE.js → forms-notify-target-4NPLX2UG.js} +5 -5
- package/dist/{freeze-A6MUCZU4.js → freeze-GUQ43EAU.js} +2 -2
- package/dist/{github-signals-TX5FPYP6.js → github-signals-DMU74SAX.js} +10 -10
- package/dist/{header-image-PSPT7CUM.js → header-image-BRTOLHJF.js} +6 -5
- package/dist/header-image-BRTOLHJF.js.map +1 -0
- package/dist/{health-endpoint-KVCJYWET.js → health-endpoint-UCLMSSCN.js} +8 -7
- package/dist/{health-endpoint-KVCJYWET.js.map → health-endpoint-UCLMSSCN.js.map} +1 -1
- package/dist/{health-mirror-XJAKA3EY.js → health-mirror-ONRT4DQ5.js} +7 -7
- package/dist/{import-airtable-TAKSN4NV.js → import-airtable-IIK2QUUF.js} +3 -3
- package/dist/index.d.ts +135 -4
- package/dist/index.js +68 -34
- package/dist/index.js.map +1 -1
- package/dist/{init-FFE4FCO2.js → init-3LMZTCVD.js} +19 -18
- package/dist/{init-FFE4FCO2.js.map → init-3LMZTCVD.js.map} +1 -1
- package/dist/{launch-UD36AHUN.js → launch-XTDMC6LD.js} +209 -61
- package/dist/launch-XTDMC6LD.js.map +1 -0
- package/dist/{lighthouse-VHI77PFB.js → lighthouse-52WSPZ5D.js} +2 -2
- package/dist/{lighthouse-airtable-YMH4UA7R.js → lighthouse-airtable-3NOFB42B.js} +3 -3
- package/dist/{match-harness-QRG6XZAR.js → match-harness-OW45CB5H.js} +14 -7
- package/dist/match-harness-OW45CB5H.js.map +1 -0
- package/dist/migrate-NE3DUMTM.js +7 -0
- package/dist/{notify-IISTHNBR.js → notify-K3WVH3DM.js} +3 -3
- package/dist/{onboard-TM2KESGF.js → onboard-CZYBWVWH.js} +6 -6
- package/dist/{orchestrate-HYVQPRCE.js → orchestrate-TGDFGY52.js} +8 -8
- package/dist/{parity-CAJ477PS.js → parity-AEKYSLN6.js} +5 -5
- package/dist/{pipeline-YROAY6DT.js → pipeline-UUPXOS2J.js} +3 -3
- package/dist/{preflight-RYIP54NY.js → preflight-POZZI3GN.js} +9 -9
- package/dist/{prismic-ci-6GKKZGB5.js → prismic-ci-AHY5ULJR.js} +38 -8
- package/dist/prismic-ci-AHY5ULJR.js.map +1 -0
- package/dist/{prismic-models-P6FWWVRS.js → prismic-models-URYGODNY.js} +9 -9
- package/dist/prospect/types.d.ts +72 -0
- package/dist/prospect/types.js +1 -1
- package/dist/{prospect-audit-PRHU53HS.js → prospect-audit-AJEE3Z4L.js} +16 -7
- package/dist/prospect-audit-AJEE3Z4L.js.map +1 -0
- package/dist/{prospect-audits-R3LWF6U7.js → prospect-audits-IZVQY3OQ.js} +4 -2
- package/dist/protection-audit-3J3UEOVT.js +413 -0
- package/dist/protection-audit-3J3UEOVT.js.map +1 -0
- package/dist/recipes/sync-configs.js +3 -3
- package/dist/{render-YGNGTX3W.js → render-MHUUHPLN.js} +36 -9
- package/dist/render-MHUUHPLN.js.map +1 -0
- package/dist/{render-from-row-IFSBHWDH.js → render-from-row-65DC6ZP3.js} +4 -4
- package/dist/{renovate-dispatch-U5Q52SKT.js → renovate-dispatch-ONJMBUHJ.js} +3 -3
- package/dist/{replay-PCSQOGCZ.js → replay-4FVFOMHH.js} +10 -23
- package/dist/replay-4FVFOMHH.js.map +1 -0
- package/dist/{report-LQS4ECTM.js → report-RC5LGPU3.js} +22 -20
- package/dist/report-RC5LGPU3.js.map +1 -0
- package/dist/{report-mirror-HDTOJE64.js → report-mirror-KKZZ6RMZ.js} +13 -9
- package/dist/{report-mirror-HDTOJE64.js.map → report-mirror-KKZZ6RMZ.js.map} +1 -1
- package/dist/{schema-A4WCPIF4.js → schema-KWUQEZ5O.js} +1 -1
- package/dist/schema-KWUQEZ5O.js.map +1 -0
- package/dist/{self-updating-MWXIZLL7.js → self-updating-RKKIYNV4.js} +8 -7
- package/dist/{self-updating-MWXIZLL7.js.map → self-updating-RKKIYNV4.js.map} +1 -1
- package/dist/{selftest-VXTHRPRN.js → selftest-AFLR7YV7.js} +10 -10
- package/dist/site-lookup-DTOLJ4EA.js +27 -0
- package/dist/site-lookup-DTOLJ4EA.js.map +1 -0
- package/dist/{site-mirror-WON4FTJ2.js → site-mirror-H2FYMN55.js} +23 -9
- package/dist/site-mirror-H2FYMN55.js.map +1 -0
- package/dist/{smoke-suite-7DGITCTZ.js → smoke-suite-BWHR3XX5.js} +8 -7
- package/dist/{smoke-suite-7DGITCTZ.js.map → smoke-suite-BWHR3XX5.js.map} +1 -1
- package/dist/{submissions-OVZPVBYG.js → submissions-II5J35QM.js} +4 -4
- package/dist/{submissions-3I3GAQAX.js → submissions-JDZP35JM.js} +49 -9
- package/dist/submissions-JDZP35JM.js.map +1 -0
- package/dist/{svelte-codemods-WSV2FGNG.js → svelte-codemods-QWGIRNN6.js} +6 -6
- package/dist/{sync-K3PKHVLH.js → sync-NKPDCMYW.js} +5 -5
- package/dist/{sync-configs-RRIHNLIJ.js → sync-configs-HGLQV55H.js} +6 -6
- package/dist/{upgrade-VTD6V4HS.js → upgrade-WZ44IARG.js} +6 -6
- package/dist/util/git.d.ts +48 -9
- package/dist/util/git.js +1 -1
- package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
- package/dist/{websites-TB4TCAWM.js → websites-6DHIHJ2D.js} +4 -2
- package/dist/{write-audits-to-airtable-53EVXKMU.js → write-audits-to-airtable-BIK6LNVC.js} +5 -5
- package/dist/write-audits-to-airtable-BIK6LNVC.js.map +1 -0
- package/package.json +3 -2
- package/dist/airtable-JGCPS7JH.js +0 -9
- package/dist/chunk-5UINJNAJ.js.map +0 -1
- package/dist/chunk-AVEQUR44.js.map +0 -1
- package/dist/chunk-BVEDKMNG.js.map +0 -1
- package/dist/chunk-BYGPUUCT.js.map +0 -1
- package/dist/chunk-CWXTNPNZ.js.map +0 -1
- package/dist/chunk-DFKBTBO3.js.map +0 -1
- package/dist/chunk-FSGICFXB.js.map +0 -1
- package/dist/chunk-GASBX52O.js +0 -12
- package/dist/chunk-GASBX52O.js.map +0 -1
- package/dist/chunk-KJRVMMM3.js.map +0 -1
- package/dist/chunk-LM2NHRCJ.js.map +0 -1
- package/dist/chunk-QF6IQQFP.js.map +0 -1
- package/dist/chunk-TO7DJYLZ.js.map +0 -1
- package/dist/chunk-TR3HFH6J.js.map +0 -1
- package/dist/chunk-WJMPCVOR.js.map +0 -1
- package/dist/chunk-WSJ7RO54.js.map +0 -1
- package/dist/chunk-WVNDEYXL.js.map +0 -1
- package/dist/chunk-XHHMM5HX.js.map +0 -1
- package/dist/chunk-XPJQXSC4.js.map +0 -1
- package/dist/chunk-YLYXW5PH.js.map +0 -1
- package/dist/chunk-ZCL7C5LH.js.map +0 -1
- package/dist/client-G2WGWPCA.js +0 -11
- package/dist/db-XKZYDCKM.js.map +0 -1
- package/dist/digest-7UBGUBRK.js.map +0 -1
- package/dist/digest-state-3KHZF7GD.js.map +0 -1
- package/dist/ensure-site-OAAE5YE2.js.map +0 -1
- package/dist/header-image-PSPT7CUM.js.map +0 -1
- package/dist/launch-UD36AHUN.js.map +0 -1
- package/dist/match-harness-QRG6XZAR.js.map +0 -1
- package/dist/migrate-Y7R4CESL.js +0 -7
- package/dist/prismic-ci-6GKKZGB5.js.map +0 -1
- package/dist/prospect-audit-PRHU53HS.js.map +0 -1
- package/dist/protection-audit-SJO53MEQ.js +0 -168
- package/dist/protection-audit-SJO53MEQ.js.map +0 -1
- package/dist/render-YGNGTX3W.js.map +0 -1
- package/dist/replay-PCSQOGCZ.js.map +0 -1
- package/dist/report-LQS4ECTM.js.map +0 -1
- package/dist/schema-A4WCPIF4.js.map +0 -1
- package/dist/site-mirror-WON4FTJ2.js.map +0 -1
- package/dist/submissions-3I3GAQAX.js.map +0 -1
- /package/dist/{airtable-JGCPS7JH.js.map → airtable-SJV4ZSMS.js.map} +0 -0
- /package/dist/{announce-CLQYX5I4.js.map → announce-45HVP4J4.js.map} +0 -0
- /package/dist/{blux-4KNXUP2K.js.map → blux-RUFMNOUJ.js.map} +0 -0
- /package/dist/{bump-deps-C5G2EFBB.js.map → bump-deps-EL3TKMIL.js.map} +0 -0
- /package/dist/{chunk-XN37VTDB.js.map → chunk-37B4LVB3.js.map} +0 -0
- /package/dist/{chunk-WMBLR5VA.js.map → chunk-44SDGPGQ.js.map} +0 -0
- /package/dist/{chunk-VSGMCCGQ.js.map → chunk-6INOBNRO.js.map} +0 -0
- /package/dist/{chunk-5GUJ36TL.js.map → chunk-74QRWE6G.js.map} +0 -0
- /package/dist/{chunk-S7RYFUIK.js.map → chunk-CA6ADGOC.js.map} +0 -0
- /package/dist/{chunk-KZRR4NFG.js.map → chunk-D3CTOCUR.js.map} +0 -0
- /package/dist/{chunk-BCKEP5S6.js.map → chunk-ECDJDHKT.js.map} +0 -0
- /package/dist/{chunk-BI2VI6EM.js.map → chunk-EQQ4H6L3.js.map} +0 -0
- /package/dist/{chunk-Z2K4JPQ3.js.map → chunk-H445EH33.js.map} +0 -0
- /package/dist/{chunk-FUWFLAWQ.js.map → chunk-IMYL67JV.js.map} +0 -0
- /package/dist/{chunk-R5IMFAFR.js.map → chunk-OTEVKRAH.js.map} +0 -0
- /package/dist/{chunk-2ZAZ7ETT.js.map → chunk-P4A6D6HO.js.map} +0 -0
- /package/dist/{chunk-4763KQ6O.js.map → chunk-PXRN2MER.js.map} +0 -0
- /package/dist/{chunk-2LYOAB2E.js.map → chunk-RRE74VWI.js.map} +0 -0
- /package/dist/{chunk-ZQV7UQNE.js.map → chunk-SQ4FSNYL.js.map} +0 -0
- /package/dist/{chunk-6R56OKJZ.js.map → chunk-UIITN37J.js.map} +0 -0
- /package/dist/{chunk-CJ2RMTT7.js.map → chunk-VEIIUCYL.js.map} +0 -0
- /package/dist/{chunk-HTABUCF4.js.map → chunk-Y5BVR4HW.js.map} +0 -0
- /package/dist/{chunk-5EEMTJMT.js.map → chunk-ZH5LOQET.js.map} +0 -0
- /package/dist/{client-G2WGWPCA.js.map → client-L7NAUG6J.js.map} +0 -0
- /package/dist/{convert-to-pnpm-YTFREUJK.js.map → convert-to-pnpm-4DON7IFS.js.map} +0 -0
- /package/dist/{digest-collectors-DJFN5ZDK.js.map → deadletter-G4M3HSVM.js.map} +0 -0
- /package/dist/{fleet-state-UEVZ3X3J.js.map → digest-collectors-MC5NWEUA.js.map} +0 -0
- /package/dist/{freeze-A6MUCZU4.js.map → fleet-state-GJWGKTLD.js.map} +0 -0
- /package/dist/{forms-notify-target-UC6PF3AE.js.map → forms-notify-target-4NPLX2UG.js.map} +0 -0
- /package/dist/{import-airtable-TAKSN4NV.js.map → freeze-GUQ43EAU.js.map} +0 -0
- /package/dist/{github-signals-TX5FPYP6.js.map → github-signals-DMU74SAX.js.map} +0 -0
- /package/dist/{health-mirror-XJAKA3EY.js.map → health-mirror-ONRT4DQ5.js.map} +0 -0
- /package/dist/{lighthouse-VHI77PFB.js.map → import-airtable-IIK2QUUF.js.map} +0 -0
- /package/dist/{lighthouse-airtable-YMH4UA7R.js.map → lighthouse-52WSPZ5D.js.map} +0 -0
- /package/dist/{migrate-Y7R4CESL.js.map → lighthouse-airtable-3NOFB42B.js.map} +0 -0
- /package/dist/{notify-IISTHNBR.js.map → migrate-NE3DUMTM.js.map} +0 -0
- /package/dist/{orchestrate-HYVQPRCE.js.map → notify-K3WVH3DM.js.map} +0 -0
- /package/dist/{onboard-TM2KESGF.js.map → onboard-CZYBWVWH.js.map} +0 -0
- /package/dist/{parity-CAJ477PS.js.map → orchestrate-TGDFGY52.js.map} +0 -0
- /package/dist/{pipeline-YROAY6DT.js.map → parity-AEKYSLN6.js.map} +0 -0
- /package/dist/{prospect-audits-R3LWF6U7.js.map → pipeline-UUPXOS2J.js.map} +0 -0
- /package/dist/{preflight-RYIP54NY.js.map → preflight-POZZI3GN.js.map} +0 -0
- /package/dist/{prismic-models-P6FWWVRS.js.map → prismic-models-URYGODNY.js.map} +0 -0
- /package/dist/{render-from-row-IFSBHWDH.js.map → prospect-audits-IZVQY3OQ.js.map} +0 -0
- /package/dist/{websites-TB4TCAWM.js.map → render-from-row-65DC6ZP3.js.map} +0 -0
- /package/dist/{renovate-dispatch-U5Q52SKT.js.map → renovate-dispatch-ONJMBUHJ.js.map} +0 -0
- /package/dist/{selftest-VXTHRPRN.js.map → selftest-AFLR7YV7.js.map} +0 -0
- /package/dist/{submissions-OVZPVBYG.js.map → submissions-II5J35QM.js.map} +0 -0
- /package/dist/{svelte-codemods-WSV2FGNG.js.map → svelte-codemods-QWGIRNN6.js.map} +0 -0
- /package/dist/{sync-K3PKHVLH.js.map → sync-NKPDCMYW.js.map} +0 -0
- /package/dist/{sync-configs-RRIHNLIJ.js.map → sync-configs-HGLQV55H.js.map} +0 -0
- /package/dist/{upgrade-VTD6V4HS.js.map → upgrade-WZ44IARG.js.map} +0 -0
- /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-53EVXKMU.js.map → websites-6DHIHJ2D.js.map} +0 -0
package/README.md
CHANGED
|
@@ -230,6 +230,40 @@ audit with `--fail-on-violations`, and most of the fleet carries pre-existing
|
|
|
230
230
|
accessibility debt — switching every site over centrally would red every repo at
|
|
231
231
|
once. Adopt it per site once that site's routes are clean, and it stays clean.
|
|
232
232
|
|
|
233
|
+
#### Running the browser gates against a production build
|
|
234
|
+
|
|
235
|
+
By default both browser gates — the `a11y` audit and the site's own smoke suite
|
|
236
|
+
— start `vite dev`. The production bundle is built in CI and then never opened
|
|
237
|
+
by a browser, and dev does not merely fail to reproduce some defects, it hides
|
|
238
|
+
them: module graph, code splitting, minification and asset hashing are most of
|
|
239
|
+
what "hydration works" means, and a stylesheet can be fetched under a different
|
|
240
|
+
CSP directive in each.
|
|
241
|
+
|
|
242
|
+
A site opts in:
|
|
243
|
+
|
|
244
|
+
```jsonc
|
|
245
|
+
// package.json
|
|
246
|
+
{
|
|
247
|
+
"reddoor": {
|
|
248
|
+
"gateServer": "preview", // "dev" (default) | "preview"
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
It requires a `preview` script (the starter has one), and it is deliberately not
|
|
254
|
+
a fleet-wide flip: a preview costs a `pnpm build` per run.
|
|
255
|
+
|
|
256
|
+
Under `preview` the a11y audit **splits** its run rather than moving it. The axe
|
|
257
|
+
scan keeps the dev server, because `/dev/a11y-fixtures` and `/dev/animate-in`
|
|
258
|
+
are dev fixture routes with no guarantee of surviving a production build — a
|
|
259
|
+
straight swap would have the route-status guard correctly report every fixture
|
|
260
|
+
as a missing route, turning a working gate into a red one that measures nothing.
|
|
261
|
+
The hydration smoke, which is the part dev actually hides, gets a built preview
|
|
262
|
+
on its own port. The pass summary names which server ran.
|
|
263
|
+
|
|
264
|
+
`REDDOOR_GATE_SERVER=preview` overrides the key for a one-off run. An
|
|
265
|
+
unrecognized value reads as `dev` rather than reaching a shell.
|
|
266
|
+
|
|
233
267
|
---
|
|
234
268
|
|
|
235
269
|
## Fleet mode
|
|
@@ -444,12 +478,13 @@ This repo's Netlify site hosts the whole dashboard + forms surface: the Resend d
|
|
|
444
478
|
| `FORMS_INGEST_TOKEN` | Shared secret gating `POST /api/forms/:slug` — the SAME value is set on each fleet site |
|
|
445
479
|
| `TURNSTILE_SECRET_KEY` | Cloudflare Turnstile secret for central spam verification. **Optional** — absent → Turnstile is disabled and fails open (the heuristic classifier is then the only defense). The per-site `PUBLIC_TURNSTILE_SITE_KEY` lives on each fleet site, not here |
|
|
446
480
|
| `TURNSTILE_SECRET_KEY_2`, `TURNSTILE_SECRET_KEY_3` | One secret per Turnstile **widget** — a widget caps at 10 hostnames, so the fleet spans several and `form-ingest.mts` tries each secret in turn. Adding a widget at Cloudflare means adding its secret here **and** a line in that array, in the same change; miss the code half and that widget's tokens go `"unverifiable"` (fail-open) and its gate is silently off. See [docs/runbooks/turnstile-widgets.md](docs/runbooks/turnstile-widgets.md) |
|
|
481
|
+
| `PROSPECT_EDIT_TOKEN` | Shared secret gating `POST /api/audit-report/:token/overrides`, where an operator's edits to a prospect report are saved — the SAME value is set on the marketing site, which proxies the save. Generate with `openssl rand -base64 32`. **Fails closed**: absent → every save 503s, which looks like a broken editor. Leading/trailing whitespace from a paste is tolerated, and warned about in the function logs |
|
|
447
482
|
| `DASHBOARD_PASSWORD` | Basic-auth password gating the cockpit, per-site dashboards, and every action endpoint — the dashboard is unauthable without it |
|
|
448
483
|
| `DASHBOARD_BASE_URL` | Public base URL of this deploy (e.g. `https://<your-site>.netlify.app`) — used to build absolute links such as report approve URLs |
|
|
449
484
|
| `RENOVATE_TOKEN` | GitHub PAT with `actions:write` — powers the dashboard's "Trigger Renovate" button (the button 502s without it) |
|
|
450
485
|
| `GH_TOKEN` | GitHub token for the dashboard's request-path REST calls (fleet-refresh dispatch + GitHub-sourced signals) |
|
|
451
486
|
|
|
452
|
-
> The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `DASHBOARD_PASSWORD` / `DASHBOARD_BASE_URL` / `RENOVATE_TOKEN` / `GH_TOKEN` are additionally required by the operator **dashboard** this same deploy serves (cockpit at `/`, per-site at `/s/:slug`).
|
|
487
|
+
> The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `PROSPECT_EDIT_TOKEN` is needed only to SAVE edits to a prospect report (serving one needs no secret beyond its URL token); `DASHBOARD_PASSWORD` / `DASHBOARD_BASE_URL` / `RENOVATE_TOKEN` / `GH_TOKEN` are additionally required by the operator **dashboard** this same deploy serves (cockpit at `/`, per-site at `/s/:slug`).
|
|
453
488
|
|
|
454
489
|
**3. Trigger a deploy** (Deploys → Trigger deploy → Deploy site). When it goes green, curl the health endpoint:
|
|
455
490
|
|
|
@@ -472,7 +507,7 @@ Expected:
|
|
|
472
507
|
}
|
|
473
508
|
```
|
|
474
509
|
|
|
475
|
-
Any `false` means the env var didn't land — re-check step 2 and re-deploy. (`TURSO_DATABASE_URL` is surfaced here because its absence 500s the dashboard + forms surface — the most common fresh-deploy failure; `DASHBOARD_PASSWORD` and the other dashboard vars aren't in this presence list, so verify those by loading the cockpit.) The health endpoint reports presence-only and never returns secret values, so the output is safe to share in a support ticket.
|
|
510
|
+
Any `false` means the env var didn't land — re-check step 2 and re-deploy. (`TURSO_DATABASE_URL` is surfaced here because its absence 500s the dashboard + forms surface — the most common fresh-deploy failure; `DASHBOARD_PASSWORD` and the other dashboard vars aren't in this presence list, so verify those by loading the cockpit. `PROSPECT_EDIT_TOKEN` isn't in it either, and it fails closed: verify it by saving an edit to a prospect report and confirming you don't get a 503 — a 404 there means the value doesn't match the marketing site's, not that it's missing, since a wrong secret and an unknown report deliberately answer alike.) The health endpoint reports presence-only and never returns secret values, so the output is safe to share in a support ticket.
|
|
476
511
|
|
|
477
512
|
**4. Register the webhook in Resend:** [resend.com/webhooks](https://resend.com/webhooks) → Add Endpoint → paste the function URL from step 3 → select events `email.delivered`, `email.bounced`, `email.complained` → save. Copy the generated **Signing Secret** (`whsec_…`) into Netlify's `RESEND_WEBHOOK_SECRET` env var (step 2) and trigger another deploy so the secret takes effect.
|
|
478
513
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scoresFromRow
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-44SDGPGQ.js";
|
|
4
4
|
import {
|
|
5
5
|
fetchGaUsers,
|
|
6
6
|
fetchSearch,
|
|
7
7
|
readGaConfig,
|
|
8
8
|
refreshHeaderImage
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-72FJZ6LO.js";
|
|
10
10
|
import {
|
|
11
11
|
queueDraft
|
|
12
12
|
} from "./chunk-YFAXOD7Z.js";
|
|
@@ -32,12 +32,12 @@ import {
|
|
|
32
32
|
resolveCopy
|
|
33
33
|
} from "./chunk-KJGKZLCT.js";
|
|
34
34
|
import "./chunk-BGTYPVLT.js";
|
|
35
|
-
import "./chunk-
|
|
35
|
+
import "./chunk-MZZDMBDT.js";
|
|
36
36
|
import {
|
|
37
37
|
listWebsites,
|
|
38
38
|
siteSlug,
|
|
39
39
|
updateAnalyticsHealth
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-KN52PM63.js";
|
|
41
41
|
import "./chunk-RARREDQE.js";
|
|
42
42
|
import "./chunk-6VAL7XJT.js";
|
|
43
43
|
|
|
@@ -203,8 +203,8 @@ function formatAnnounceResult(result) {
|
|
|
203
203
|
return result.results.map(formatSiteResult).join("\n");
|
|
204
204
|
}
|
|
205
205
|
async function runAnnounceCommand(site, _opts) {
|
|
206
|
-
const { makeReportMirror } = await import("./report-mirror-
|
|
207
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
206
|
+
const { makeReportMirror } = await import("./report-mirror-KKZZ6RMZ.js");
|
|
207
|
+
const { makeSiteMirror } = await import("./site-mirror-H2FYMN55.js");
|
|
208
208
|
const reportMirror = await makeReportMirror();
|
|
209
209
|
const siteMirror = await makeSiteMirror();
|
|
210
210
|
const result = await announce({ ...site ? { site } : {}, reportMirror, siteMirror });
|
|
@@ -215,4 +215,4 @@ export {
|
|
|
215
215
|
formatAnnounceResult,
|
|
216
216
|
runAnnounceCommand
|
|
217
217
|
};
|
|
218
|
-
//# sourceMappingURL=announce-
|
|
218
|
+
//# sourceMappingURL=announce-45HVP4J4.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assetRef,
|
|
3
|
-
richText
|
|
4
|
-
} from "./chunk-UWJZXL2D.js";
|
|
5
1
|
import {
|
|
6
2
|
FROZEN_PAGE_TYPE,
|
|
7
3
|
extractMapConfig,
|
|
8
4
|
frozenPageCustomType,
|
|
9
5
|
makeIsMapMount
|
|
10
6
|
} from "./chunk-DRDAEELJ.js";
|
|
7
|
+
import {
|
|
8
|
+
assetRef,
|
|
9
|
+
richText
|
|
10
|
+
} from "./chunk-UWJZXL2D.js";
|
|
11
11
|
import {
|
|
12
12
|
CDN_HOSTS,
|
|
13
13
|
collectAssetUrls
|
|
@@ -4767,4 +4767,4 @@ ${lines.join("\n")}`,
|
|
|
4767
4767
|
export {
|
|
4768
4768
|
runBluxCommand
|
|
4769
4769
|
};
|
|
4770
|
-
//# sourceMappingURL=blux-
|
|
4770
|
+
//# sourceMappingURL=blux-RUFMNOUJ.js.map
|
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
appendSkipNotice,
|
|
6
6
|
fleetWorkdir,
|
|
7
7
|
prepareFleetSites
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SHGIDTYF.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveSites
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-H445EH33.js";
|
|
12
12
|
import {
|
|
13
13
|
bumpDeps
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-VEIIUCYL.js";
|
|
15
|
+
import "./chunk-SQ4FSNYL.js";
|
|
16
16
|
import "./chunk-Z3JXLULD.js";
|
|
17
17
|
import "./chunk-LBYOLBW7.js";
|
|
18
18
|
import "./chunk-RARREDQE.js";
|
|
19
19
|
import "./chunk-XXTZBPUY.js";
|
|
20
|
-
import "./chunk-
|
|
20
|
+
import "./chunk-EASYCD6F.js";
|
|
21
21
|
|
|
22
22
|
// src/cli/commands/bump-deps.ts
|
|
23
23
|
import { resolve } from "path";
|
|
@@ -57,4 +57,4 @@ async function runBumpDepsCommand(site, opts) {
|
|
|
57
57
|
export {
|
|
58
58
|
runBumpDepsCommand
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=bump-deps-
|
|
60
|
+
//# sourceMappingURL=bump-deps-EL3TKMIL.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
siteSlug
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KN52PM63.js";
|
|
4
4
|
|
|
5
5
|
// src/audits/lighthouse-airtable.ts
|
|
6
6
|
import { readFile } from "fs/promises";
|
|
@@ -48,4 +48,4 @@ export {
|
|
|
48
48
|
lighthouseScoresFromResult,
|
|
49
49
|
resolveSlugFromCwd
|
|
50
50
|
};
|
|
51
|
-
//# sourceMappingURL=chunk-
|
|
51
|
+
//# sourceMappingURL=chunk-37B4LVB3.js.map
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
2
|
onboard
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CA6ADGOC.js";
|
|
4
4
|
import {
|
|
5
5
|
convertToPnpm
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DVJNYPFR.js";
|
|
7
7
|
import {
|
|
8
8
|
svelteCodemods
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RRE74VWI.js";
|
|
10
10
|
import {
|
|
11
11
|
healthEndpoint
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XED2DNAI.js";
|
|
13
13
|
import {
|
|
14
14
|
smokeSuite
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-NVLREB5R.js";
|
|
16
|
+
import {
|
|
17
|
+
RESTORED_NOTE,
|
|
18
|
+
refusedByGit,
|
|
19
|
+
undoRefusedWrites
|
|
20
|
+
} from "./chunk-4UTJ4G6P.js";
|
|
16
21
|
import {
|
|
17
22
|
syncConfigs
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-IMYL67JV.js";
|
|
19
24
|
import {
|
|
20
25
|
withRecipe
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-SQ4FSNYL.js";
|
|
22
27
|
import {
|
|
23
28
|
runAudits
|
|
24
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-XY7YV2YQ.js";
|
|
25
30
|
import {
|
|
26
31
|
siteLabel
|
|
27
32
|
} from "./chunk-XXTZBPUY.js";
|
|
@@ -89,10 +94,23 @@ async function a11yFixturesPage(site) {
|
|
|
89
94
|
}
|
|
90
95
|
return { kind: "apply", plan: { target } };
|
|
91
96
|
},
|
|
92
|
-
apply: async (planned, { commit }) => {
|
|
97
|
+
apply: async (planned, { commit, cwd }) => {
|
|
93
98
|
await mkdir(dirname(planned.target), { recursive: true });
|
|
94
99
|
await writeFile(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, "utf-8");
|
|
95
100
|
await commit("feat: add /dev/a11y-fixtures starter route");
|
|
101
|
+
const refusal = await refusedByGit(
|
|
102
|
+
cwd,
|
|
103
|
+
[A11Y_FIXTURES_PAGE_RELATIVE],
|
|
104
|
+
"the /dev/a11y-fixtures route"
|
|
105
|
+
);
|
|
106
|
+
if (refusal) {
|
|
107
|
+
await undoRefusedWrites(
|
|
108
|
+
cwd,
|
|
109
|
+
/* @__PURE__ */ new Map([[A11Y_FIXTURES_PAGE_RELATIVE, null]]),
|
|
110
|
+
refusal.missing
|
|
111
|
+
);
|
|
112
|
+
return { kind: "failed", notes: refusal.notes + RESTORED_NOTE };
|
|
113
|
+
}
|
|
96
114
|
return { kind: "ok" };
|
|
97
115
|
}
|
|
98
116
|
});
|
|
@@ -143,4 +161,4 @@ export {
|
|
|
143
161
|
DEFAULT_INIT_STEPS,
|
|
144
162
|
init
|
|
145
163
|
};
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
164
|
+
//# sourceMappingURL=chunk-3EA43S3R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/recipes/a11y-fixtures-page/index.ts","../src/recipes/a11y-fixtures-page/template.ts","../src/recipes/init.ts"],"sourcesContent":["import { access, mkdir, 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 { refusedByGit, undoRefusedWrites, RESTORED_NOTE } from \"../_head-guard.js\";\nimport { A11Y_FIXTURES_PAGE_RELATIVE, A11Y_FIXTURES_PAGE_TEMPLATE } from \"./template.js\";\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route\n * doesn't already exist. The hardcoded URL in `src/configs/lighthouse.ts` +\n * `src/configs/playwright-a11y.ts` targets this path — newly-onboarded sites\n * need the route to exist for either audit to pass. Operator edits to an\n * existing page are never clobbered (noop on existing file).\n */\nexport async function a11yFixturesPage(site: Site): Promise<RecipeResult> {\n const target = join(site.path, A11Y_FIXTURES_PAGE_RELATIVE);\n return withRecipe<{ target: string }>({\n name: \"a11y-fixtures-page\",\n site,\n plan: async () => {\n if (await fileExists(target)) {\n return { kind: \"noop\", notes: `${A11Y_FIXTURES_PAGE_RELATIVE} already exists` };\n }\n return { kind: \"apply\", plan: { target } };\n },\n apply: async (planned, { commit, cwd }) => {\n await mkdir(dirname(planned.target), { recursive: true });\n await writeFile(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, \"utf-8\");\n await commit(\"feat: add /dev/a11y-fixtures starter route\");\n\n // Did git TAKE it? A site that ignores `src/routes/dev/` (a plausible rule\n // — the route exists only for the audits) gets the file on disk, nothing\n // in the commit, and a result that reads as done. The lighthouse and\n // playwright-a11y configs would then target a route no clone has, and\n // every later run noops on \"already exists\" (#741).\n const refusal = await refusedByGit(\n cwd,\n [A11Y_FIXTURES_PAGE_RELATIVE],\n \"the /dev/a11y-fixtures route\",\n );\n if (refusal) {\n await undoRefusedWrites(\n cwd,\n new Map([[A11Y_FIXTURES_PAGE_RELATIVE, null]]),\n refusal.missing,\n );\n return { kind: \"failed\", notes: refusal.notes + RESTORED_NOTE };\n }\n return { kind: \"ok\" };\n },\n });\n}\n","/** Relative path inside a site where the a11y fixtures route lives. The\n * hardcoded URL in `src/configs/lighthouse.ts` + `src/configs/playwright-a11y.ts`\n * is `/dev/a11y-fixtures`, so a SvelteKit `+page.svelte` here resolves. */\nexport const A11Y_FIXTURES_PAGE_RELATIVE = \"src/routes/dev/a11y-fixtures/+page.svelte\";\n\n/** Stub `+page.svelte` for newly-onboarded sites. Generic on purpose —\n * landmarks, heading hierarchy, and a relative link cover the axe-core +\n * lhci defaults without committing the operator to any specific fixture\n * shape. Replace with site-specific patterns over time. */\nexport const A11Y_FIXTURES_PAGE_TEMPLATE = `<svelte:head>\n <title>a11y fixtures — Reddoor</title>\n <meta\n name=\"description\"\n content=\"Reddoor accessibility fixtures — semantic landmarks, heading hierarchy, and a stable target for @lhci/cli and Playwright + axe-core coverage. Not linked from the public site.\"\n />\n</svelte:head>\n\n<main>\n <header>\n <h1>Accessibility fixtures</h1>\n <p>\n This page exists so <code>@lhci/cli</code> and Playwright + axe-core have a\n stable target with predictable a11y characteristics. It is not linked from\n the public site.\n </p>\n </header>\n\n <section aria-labelledby=\"landmarks-heading\">\n <h2 id=\"landmarks-heading\">Landmarks</h2>\n <p>\n A single <code>main</code> wraps the page; sections each declare\n <code>aria-labelledby</code> matched to their heading id so screen readers\n and axe both see a clean outline.\n </p>\n </section>\n\n <section aria-labelledby=\"links-heading\">\n <h2 id=\"links-heading\">Links</h2>\n <p>\n <a href=\"/\">Back to home</a> — relative link with descriptive visible text,\n so no <code>aria-label</code> override is needed.\n </p>\n </section>\n</main>\n`;\n","import type { AuditResult, RecipeResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { convertToPnpm } from \"./convert-to-pnpm.js\";\nimport { onboard } from \"./onboard.js\";\nimport { syncConfigs } from \"./sync-configs.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport { healthEndpoint } from \"./health-endpoint/index.js\";\nimport { smokeSuite } from \"./smoke-suite/index.js\";\nimport { runAudits } from \"../audits/index.js\";\n\nexport type InitStepResult =\n | { kind: \"recipe\"; result: RecipeResult }\n | { kind: \"audit\"; results: AuditResult[] }\n | { kind: \"error\"; message: string };\n\nexport type InitStep = {\n name: string;\n run: (site: Site) => Promise<InitStepResult>;\n};\n\nexport type InitResult = {\n site: string;\n steps: Array<{ name: string; result: InitStepResult }>;\n /** True if every step ran; false if an `error` or `failed` recipe result\n * short-circuited the chain. `noop` recipes do not break completeness. */\n complete: boolean;\n};\n\nexport type InitOptions = {\n /** Override the default step list. Tests inject mocked steps; production\n * code relies on the default. */\n steps?: InitStep[];\n};\n\nfunction recipeStep(name: string, fn: (site: Site) => Promise<RecipeResult>): InitStep {\n return {\n name,\n run: async (site) => ({ kind: \"recipe\", result: await fn(site) }),\n };\n}\n\n/** convert-to-pnpm → onboard → sync-configs → svelte-codemods →\n * a11y-fixtures-page → health-endpoint → smoke-suite → audit. Order is\n * deliberate — every step depends on the prior one's output (pnpm before\n * onboard's installs, onboard's deps before sync-configs writes lighthouserc,\n * fixtures page before audit actually has a route to hit; smoke-suite's\n * `pnpm install` lands before the closing audit). */\nexport const DEFAULT_INIT_STEPS: InitStep[] = [\n recipeStep(\"convert-to-pnpm\", convertToPnpm),\n recipeStep(\"onboard\", onboard),\n recipeStep(\"sync-configs\", syncConfigs),\n recipeStep(\"svelte-codemods\", svelteCodemods),\n recipeStep(\"a11y-fixtures-page\", a11yFixturesPage),\n recipeStep(\"health-endpoint\", healthEndpoint),\n recipeStep(\"smoke-suite\", smokeSuite),\n {\n name: \"audit\",\n run: async (site) => ({ kind: \"audit\", results: await runAudits(site) }),\n },\n];\n\n/**\n * One-shot guided onboarding. Runs the default step sequence against a\n * site, collecting per-step results into an InitResult. Each underlying\n * recipe still creates its own branch — init is a thin orchestrator, not\n * a branch-collapser; the operator ends up with one stack of branches per\n * mutated step (recipes that noop don't branch).\n *\n * Stops the chain on the first uncaught error or `failed` recipe result.\n * `noop` results are expected (e.g. running init twice) and continue the\n * chain. The final audit pass runs if no prior step errored.\n */\nexport async function init(site: Site, opts: InitOptions = {}): Promise<InitResult> {\n const steps = opts.steps ?? DEFAULT_INIT_STEPS;\n const out: Array<{ name: string; result: InitStepResult }> = [];\n\n for (const step of steps) {\n let result: InitStepResult;\n try {\n result = await step.run(site);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n out.push({ name: step.name, result: { kind: \"error\", message } });\n return { site: siteLabel(site), steps: out, complete: false };\n }\n out.push({ name: step.name, result });\n if (result.kind === \"recipe\" && result.result.status === \"failed\") {\n return { site: siteLabel(site), steps: out, complete: false };\n }\n }\n\n return { site: siteLabel(site), steps: out, complete: true };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,QAAQ,OAAO,iBAAiB;AACzC,SAAS,SAAS,YAAY;;;ACEvB,IAAM,8BAA8B;AAMpC,IAAM,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ADF3C,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,iBAAiB,MAAmC;AACxE,QAAM,SAAS,KAAK,KAAK,MAAM,2BAA2B;AAC1D,SAAO,WAA+B;AAAA,IACpC,MAAM;AAAA,IACN;AAAA,IACA,MAAM,YAAY;AAChB,UAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,eAAO,EAAE,MAAM,QAAQ,OAAO,GAAG,2BAA2B,kBAAkB;AAAA,MAChF;AACA,aAAO,EAAE,MAAM,SAAS,MAAM,EAAE,OAAO,EAAE;AAAA,IAC3C;AAAA,IACA,OAAO,OAAO,SAAS,EAAE,QAAQ,IAAI,MAAM;AACzC,YAAM,MAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,YAAM,UAAU,QAAQ,QAAQ,6BAA6B,OAAO;AACpE,YAAM,OAAO,4CAA4C;AAOzD,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACA,CAAC,2BAA2B;AAAA,QAC5B;AAAA,MACF;AACA,UAAI,SAAS;AACX,cAAM;AAAA,UACJ;AAAA,UACA,oBAAI,IAAI,CAAC,CAAC,6BAA6B,IAAI,CAAC,CAAC;AAAA,UAC7C,QAAQ;AAAA,QACV;AACA,eAAO,EAAE,MAAM,UAAU,OAAO,QAAQ,QAAQ,cAAc;AAAA,MAChE;AACA,aAAO,EAAE,MAAM,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AACH;;;AEzBA,SAAS,WAAW,MAAc,IAAqD;AACrF,SAAO;AAAA,IACL;AAAA,IACA,KAAK,OAAO,UAAU,EAAE,MAAM,UAAU,QAAQ,MAAM,GAAG,IAAI,EAAE;AAAA,EACjE;AACF;AAQO,IAAM,qBAAiC;AAAA,EAC5C,WAAW,mBAAmB,aAAa;AAAA,EAC3C,WAAW,WAAW,OAAO;AAAA,EAC7B,WAAW,gBAAgB,WAAW;AAAA,EACtC,WAAW,mBAAmB,cAAc;AAAA,EAC5C,WAAW,sBAAsB,gBAAgB;AAAA,EACjD,WAAW,mBAAmB,cAAc;AAAA,EAC5C,WAAW,eAAe,UAAU;AAAA,EACpC;AAAA,IACE,MAAM;AAAA,IACN,KAAK,OAAO,UAAU,EAAE,MAAM,SAAS,SAAS,MAAM,UAAU,IAAI,EAAE;AAAA,EACxE;AACF;AAaA,eAAsB,KAAK,MAAY,OAAoB,CAAC,GAAwB;AAClF,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,MAAuD,CAAC;AAE9D,aAAW,QAAQ,OAAO;AACxB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,IAAI,IAAI;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAI,KAAK,EAAE,MAAM,KAAK,MAAM,QAAQ,EAAE,MAAM,SAAS,QAAQ,EAAE,CAAC;AAChE,aAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM;AAAA,IAC9D;AACA,QAAI,KAAK,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AACpC,QAAI,OAAO,SAAS,YAAY,OAAO,OAAO,WAAW,UAAU;AACjE,aAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,KAAK;AAC7D;","names":[]}
|
|
@@ -31,6 +31,7 @@ function mapRollupState(state) {
|
|
|
31
31
|
function mapMergeable(state) {
|
|
32
32
|
return state === "MERGEABLE" || state === "CONFLICTING" ? state : "UNKNOWN";
|
|
33
33
|
}
|
|
34
|
+
var RENOVATE_MERGE_WINDOW_PAGE = 100;
|
|
34
35
|
var KNOWN_DASHBOARD_SECTIONS = /* @__PURE__ */ new Set([
|
|
35
36
|
// 43+
|
|
36
37
|
"pending approval",
|
|
@@ -376,6 +377,58 @@ function makeGitHub(deps) {
|
|
|
376
377
|
};
|
|
377
378
|
});
|
|
378
379
|
},
|
|
380
|
+
async openSecretAlerts(repo) {
|
|
381
|
+
const r = await spawn(
|
|
382
|
+
"gh",
|
|
383
|
+
[
|
|
384
|
+
"api",
|
|
385
|
+
"--paginate",
|
|
386
|
+
`repos/${repo}/secret-scanning/alerts?state=open&per_page=100`,
|
|
387
|
+
"--jq",
|
|
388
|
+
".[].number"
|
|
389
|
+
],
|
|
390
|
+
{ env, timeoutMs: 6e4 }
|
|
391
|
+
);
|
|
392
|
+
if (r.code !== 0) {
|
|
393
|
+
if (/HTTP 40[34]/.test(r.stderr)) return "unavailable";
|
|
394
|
+
throw new Error(`openSecretAlerts(${repo}) failed: ${r.stderr.trim()}`);
|
|
395
|
+
}
|
|
396
|
+
return r.stdout.split("\n").filter((l) => l.trim().length > 0).length;
|
|
397
|
+
},
|
|
398
|
+
async repoTextFile(repo, path) {
|
|
399
|
+
assertUrlSegment("path", repo);
|
|
400
|
+
assertUrlSegment("path", path);
|
|
401
|
+
const r = await spawn(
|
|
402
|
+
"gh",
|
|
403
|
+
["api", `repos/${repo}/contents/${path}`, "--jq", '.content // ""'],
|
|
404
|
+
{ env, timeoutMs: 6e4 }
|
|
405
|
+
);
|
|
406
|
+
if (r.code !== 0) {
|
|
407
|
+
if (/HTTP 404/.test(r.stderr)) return null;
|
|
408
|
+
throw new Error(`repoTextFile(${repo}/${path}) failed: ${r.stderr.trim()}`);
|
|
409
|
+
}
|
|
410
|
+
const encoded = r.stdout.replace(/\s+/g, "");
|
|
411
|
+
if (encoded.length === 0) return "";
|
|
412
|
+
return Buffer.from(encoded, "base64").toString("utf-8");
|
|
413
|
+
},
|
|
414
|
+
async listWorkflowPaths(repo) {
|
|
415
|
+
assertUrlSegment("path", repo);
|
|
416
|
+
const r = await spawn(
|
|
417
|
+
"gh",
|
|
418
|
+
[
|
|
419
|
+
"api",
|
|
420
|
+
`repos/${repo}/contents/.github/workflows`,
|
|
421
|
+
"--jq",
|
|
422
|
+
'.[] | select(.type=="file") | .path'
|
|
423
|
+
],
|
|
424
|
+
{ env, timeoutMs: 6e4 }
|
|
425
|
+
);
|
|
426
|
+
if (r.code !== 0) {
|
|
427
|
+
if (/HTTP 404/.test(r.stderr)) return [];
|
|
428
|
+
throw new Error(`listWorkflowPaths(${repo}) failed: ${r.stderr.trim()}`);
|
|
429
|
+
}
|
|
430
|
+
return r.stdout.split("\n").map((l) => l.trim()).filter((l) => l.endsWith(".yml") || l.endsWith(".yaml"));
|
|
431
|
+
},
|
|
379
432
|
async workflowHealth(repo, filename) {
|
|
380
433
|
const wf = await spawn(
|
|
381
434
|
"gh",
|
|
@@ -428,6 +481,15 @@ function makeGitHub(deps) {
|
|
|
428
481
|
}
|
|
429
482
|
const raw = JSON.parse(out);
|
|
430
483
|
return { authorIsMachine: isMachineAuthor(raw), committedAt: raw.date };
|
|
484
|
+
},
|
|
485
|
+
async renovateMergeWindow(repo) {
|
|
486
|
+
const out = await gh([
|
|
487
|
+
"api",
|
|
488
|
+
`repos/${repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${RENOVATE_MERGE_WINDOW_PAGE}`
|
|
489
|
+
]);
|
|
490
|
+
const arr = JSON.parse(out);
|
|
491
|
+
const merges = arr.filter((p) => p.merged_at !== null && (p.head?.ref ?? "").startsWith("renovate/")).map((p) => ({ headRef: p.head.ref, mergedAt: p.merged_at })).sort((a, b) => b.mergedAt.localeCompare(a.mergedAt));
|
|
492
|
+
return { merges, truncated: arr.length >= RENOVATE_MERGE_WINDOW_PAGE };
|
|
431
493
|
}
|
|
432
494
|
};
|
|
433
495
|
}
|
|
@@ -442,4 +504,4 @@ export {
|
|
|
442
504
|
assertUrlSegment,
|
|
443
505
|
makeGitHub
|
|
444
506
|
};
|
|
445
|
-
//# sourceMappingURL=chunk-
|
|
507
|
+
//# sourceMappingURL=chunk-43SW3FGW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/github/gh.ts"],"sourcesContent":["import { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport type { ExistingRuleset, RulesetPayload } from \"./rulesets.js\";\n\n/** Aggregate CI state of a PR's head commit, normalized from GitHub's rollup. */\nexport type CiState = \"passing\" | \"failing\" | \"pending\" | \"none\";\n\n/** GitHub's computed mergeability of a PR. `UNKNOWN` is transient — GitHub is\n * still computing it (e.g. right after a push) — so it should be read as \"not\n * known to conflict\", never as conflicting. */\nexport type PrMergeable = \"MERGEABLE\" | \"CONFLICTING\" | \"UNKNOWN\";\n\n/** A minimal open-PR summary with its head-commit CI rollup state + mergeability. */\nexport type PullRequestSummary = {\n number: number;\n title: string;\n url: string;\n headRef: string;\n ciState: CiState;\n mergeable: PrMergeable;\n};\n\n/**\n * Reject a value before it's interpolated into a `gh api` URL path. The\n * `owner/repo` split methods already validate shape; this is the defense-in-depth\n * guard for the `branch` and file-`path` segments. An unexpected value (`..`, a\n * leading `/`, whitespace, or a URL-structural char like `?#%` or a backslash)\n * could otherwise retarget the endpoint (escape the intended path, smuggle a\n * query string, or traverse). Conservative by design — legit branch names like\n * `maint/self-updating-x` and paths like `.github/workflows/ci.yml` pass.\n *\n * Both branch refs (`maint/self-updating-x`) and file paths\n * (`.github/workflows/ci.yml`) legitimately contain `/`, so a single slash is\n * allowed; what's rejected is `..`, a leading `/`, whitespace, or a\n * URL-structural char (`?`, `#`, `%`, backslash) that could escape or retarget\n * the endpoint.\n */\nexport function assertUrlSegment(kind: \"branch\" | \"path\", value: string): void {\n const structural = /[\\s?#%\\\\]|\\.\\./;\n if (value.length === 0 || value.startsWith(\"/\") || structural.test(value)) {\n throw new Error(\n `unsafe ${kind} for gh api path (illegal characters or traversal): ${JSON.stringify(value)}`,\n );\n }\n}\n\n/** Map GitHub's `statusCheckRollup.state` enum to our normalized CiState. */\nfunction mapRollupState(state: string | null | undefined): CiState {\n switch (state) {\n case \"SUCCESS\":\n return \"passing\";\n case \"FAILURE\":\n case \"ERROR\":\n return \"failing\";\n case \"PENDING\":\n case \"EXPECTED\":\n return \"pending\";\n default:\n return \"none\"; // null/undefined = no checks reported\n }\n}\n\n/** Coerce GitHub's `mergeable` enum to our PrMergeable. Anything unexpected\n * (including the literal `UNKNOWN` GitHub returns while still computing) maps to\n * `UNKNOWN` — i.e. \"not known to conflict\". Only an explicit `CONFLICTING` is. */\nfunction mapMergeable(state: string | null | undefined): PrMergeable {\n return state === \"MERGEABLE\" || state === \"CONFLICTING\" ? state : \"UNKNOWN\";\n}\n\nexport type GitHub = {\n openPullRequest: (\n repo: string,\n pr: { head: string; base: string; title: string; body: string },\n ) => Promise<{ url: string }>;\n /** Turn GitHub's platform auto-merge ON for a repo. Retained as the documented\n * ROLLBACK path — the fleet drives repos to auto-merge OFF (see\n * {@link disableRepoAutoMerge} and `recipes/self-updating`). */\n enableRepoAutoMerge: (repo: string) => Promise<void>;\n /** Turn GitHub's platform auto-merge OFF for a repo. This is the fleet default:\n * platform auto-merge is a per-PR flag anyone with write access can arm, and it\n * merges unattended outside Renovate's `packageRules`. Renovate merges from\n * inside its own run instead. */\n disableRepoAutoMerge: (repo: string) => Promise<void>;\n protectBranch: (repo: string, branch: string, requiredChecks: string[]) => Promise<void>;\n setRepoSecret: (repo: string, name: string, value: string) => Promise<void>;\n repoExists: (repo: string) => Promise<boolean>;\n defaultBranch: (repo: string) => Promise<string>;\n filesOnBranch: (repo: string, branch: string, paths: string[]) => Promise<string[]>;\n /** The raw contents of `path` on `branch`, or null when the file is absent (404).\n * Lets a caller detect a present-but-STALE file (content drift), where\n * `filesOnBranch` only sees existence. */\n fileContentsOnBranch: (repo: string, branch: string, path: string) => Promise<string | null>;\n branchProtectionContexts: (repo: string, branch: string) => Promise<string[]>;\n secretExists: (repo: string, name: string) => Promise<boolean>;\n autoMergeEnabled: (repo: string) => Promise<boolean>;\n findOpenSelfUpdatingPR: (repo: string) => Promise<string | null>;\n /** All open PRs on a repo with each head commit's normalized CI rollup state. */\n openPullRequests: (repo: string) => Promise<PullRequestSummary[]>;\n /** The default branch's latest-commit date + normalized CI rollup, one query. */\n defaultBranchStatus: (repo: string) => Promise<{ ciState: CiState; lastCommitAt: string | null }>;\n /** Renovate PRs (head `renovate/*`) merged at/after `sinceIso`. Used to record\n * `pr_automerged` fleet events. gh-shell (Actions only). */\n mergedRenovatePullRequests: (\n repo: string,\n sinceIso: string,\n ) => Promise<Array<{ number: number; title: string; url: string; mergedAt: string }>>;\n /** Fire a `workflow_dispatch` for `<workflow>` (a filename like `renovate.yml`)\n * on `ref`. Requires the token's `actions:write` scope; a 404 (no such\n * workflow) or 403 (missing scope) surfaces as a thrown error. */\n dispatchWorkflow: (repo: string, workflow: string, ref: string) => Promise<void>;\n /** REPO-sourced rulesets only (`includes_parents=false`): org-level rulesets\n * aren't ours to manage (and don't exist on the Free plan). */\n listRepoRulesets: (repo: string) => Promise<Array<{ id: number; name: string }>>;\n getRuleset: (repo: string, id: number) => Promise<ExistingRuleset>;\n createRuleset: (repo: string, payload: RulesetPayload) => Promise<void>;\n /** The update verb is PUT — `PATCH repos/{repo}/rulesets/{id}` returns 404\n * (verified live 2026-08-01), so a PATCH-based update would silently no-op\n * on every drifted repo while the recipe reported success. */\n updateRuleset: (repo: string, id: number, payload: RulesetPayload) => Promise<void>;\n /** True iff a check-run named `context` exists on `ref`'s HEAD commit. The\n * evidence gate for requiring a status check: with an empty bypass list, a\n * required context that never fires makes the repo permanently unmergeable\n * by everyone — so a context must be OBSERVED before it is required.\n * False-negatives (fresh repo, API hiccup) safely degrade to \"don't require\n * yet\"; false-positives are impossible (we name-match real runs). */\n checkContextObserved: (repo: string, ref: string, context: string) => Promise<boolean>;\n /** \"public\" | \"private\" (| \"internal\" on GHEC). Rulesets on private repos\n * need a paid plan, so callers gate on this rather than attempting. */\n repoVisibility: (repo: string) => Promise<string>;\n /** Every repo in the org (paginated) — the anti-hand-typed-list enumerator.\n * Sweeps driven by a hand-maintained or Airtable-scoped list have already\n * produced two false \"all clear\"s; enumerate from the API instead.\n * secretScanning/pushProtection come from the same listing (no extra call);\n * \"unavailable\" means the token couldn't read security_and_analysis (needs\n * admin/security read) — callers must treat that as unverified, not fine. */\n listOrgRepos: (org: string) => Promise<\n Array<{\n name: string;\n visibility: string;\n archived: boolean;\n secretScanning: string;\n pushProtection: string;\n }>\n >;\n /** Liveness of one workflow file: registered? state? when did it last\n * SUCCEED? Success, not mere existence of a run: a dead credential or\n * broken config still CREATES a run on every cron tick that fails in\n * seconds, keeping created_at perpetually fresh — counting those as alive\n * is exactly the silent-stop blindness this probe exists to catch.\n * A clean 404 is the answer `{present: false}` — any OTHER failure throws,\n * because \"couldn't check\" must never read as \"healthy\" downstream. */\n workflowHealth: (repo: string, filename: string) => Promise<WorkflowHealth>;\n /** Renovate's own Dependency Dashboard issue, which is the only place it\n * reports branches it has DECIDED TO STOP MANAGING. workflowHealth answers\n * \"did Renovate run?\"; this answers \"was it allowed to do anything?\" — a\n * distinction that cost the fleet 9 repos of frozen updates (see\n * renovateBlockedGaps). Absent dashboard is the answer `{present:false}`,\n * not an error: `dependencyDashboard` is a config a repo may disable. */\n dependencyDashboard: (repo: string) => Promise<DependencyDashboard>;\n /** Tip commit of one branch, or `null` if the branch is gone — which is the\n * answer, not an error: a dashboard naming a deleted branch is simply one\n * Renovate has not rewritten yet. */\n branchTip: (repo: string, branch: string) => Promise<BranchTip | null>;\n /** Every `renovate/*` head MERGED inside one page of the repo's closed PRs,\n * plus whether that page filled up. The classification (security vs\n * feature) is deliberately left to the caller so the verdict stays a pure,\n * fixture-testable function — see renovateOutcome. */\n renovateMergeWindow: (repo: string) => Promise<RenovateMergeWindow>;\n /** How many secret-scanning alerts are OPEN on a repo — i.e. leaked\n * credentials nobody has triaged. Settings say a detector runs; only this\n * says whether anyone read it (see secretScanningGaps). `\"unavailable\"` is\n * the token that may not read alerts (403/404 — needs `security_events`),\n * and callers must treat it as unverified, never as zero. */\n openSecretAlerts: (repo: string) => Promise<number | \"unavailable\">;\n /** One text file at the repo's DEFAULT branch, or `null` when it does not\n * exist. `null` is an ANSWER — a repo with no `package.json` is not a node\n * repo, which the pnpm-pin sweep must read as out of scope rather than as a\n * gap. Every other failure THROWS: \"I could not read it\" arriving\n * downstream as \"there is no pin\" is the silent-green collapse these\n * sweeps exist to kill. */\n repoTextFile: (repo: string, path: string) => Promise<string | null>;\n /** Paths of every file in `.github/workflows`, or `[]` when the directory is\n * absent (a repo may legitimately have no workflows). Used to find the\n * `pnpm/action-setup` steps that pin a pnpm `version:` input — see\n * parsePnpmActionSetupPins for why the file must be PARSED, not grepped. */\n listWorkflowPaths: (repo: string) => Promise<string[]>;\n};\n\nexport type WorkflowHealth =\n { present: false } | { present: true; state: string; lastSuccessAt: string | null };\n\nexport type DependencyDashboard =\n { present: false } | { present: true; blockedBranches: string[]; unknownSections: string[] };\n\n/** Tip commit of a branch. `authorIsMachine` answers \"does any human own this\n * branch?\" — see renovateBlockedGaps for why that decides whether a blocked\n * branch is an orphan or a human's in-flight work. */\nexport type BranchTip = { authorIsMachine: boolean; committedAt: string };\n\n/** One page of closed PRs is all this costs, so the window has an EDGE, and\n * the edge is load-bearing: `reddoor-maintenance` ships enough PRs that 100\n * closed ones reach back only ~12 days, while a quiet site repo's 100 reach\n * back ~90. `truncated` is what keeps \"nothing in the window\" from being read\n * as \"never merged one\" on the busy repos — the difference between a measured\n * drought and an unmeasured one. */\nexport const RENOVATE_MERGE_WINDOW_PAGE = 100;\n\nexport type RenovateMergeWindow = {\n /** Merged `renovate/*` PRs seen in the window, newest merge first. */\n merges: Array<{ headRef: string; mergedAt: string }>;\n /** The page came back FULL, so older history exists that was not read. */\n truncated: boolean;\n};\n\n/** The section headings Renovate is known to emit, normalised to lowercase.\n * Sourced from lib/workers/repository/dependency-dashboard.ts at both live\n * majors — the wording moved once already (see BLOCKED_SECTION_RE), so the\n * legacy spellings stay in the set. Anything OUTSIDE this set means our\n * vocabulary has drifted from Renovate's and the blocked check can no longer\n * be trusted; parseUnknownSections reports it rather than reading silence as\n * health. */\nconst KNOWN_DASHBOARD_SECTIONS = new Set([\n // 43+\n \"pending approval\",\n \"group size not met\",\n \"awaiting schedule\",\n \"rate-limited\",\n \"errored\",\n \"pr creation approval required\",\n \"pr edited (blocked)\",\n \"pending status checks\",\n \"pending branch automerge\",\n \"other branches\",\n \"open\",\n \"pr closed (blocked)\",\n \"repository problems\",\n \"config migration needed\",\n \"config migration needed (blocked)\",\n \"deprecations / replacements\",\n \"abandoned dependencies\",\n \"vulnerabilities\",\n \"detected dependencies\",\n // <=42, kept so an older self-hosted Renovate does not read as drift\n \"edited/blocked\",\n \"ignored or blocked\",\n]);\n\n/** Both spellings of the \"I have stopped managing these branches\" heading.\n * Renovate renamed it `Edited/Blocked` -> `PR Edited (Blocked)` in 43.0.0,\n * and the fleet takes its Renovate major from whatever renovatebot/github-action\n * bakes in — so an ordinary action bump moves it. Matching only the current\n * wording would make this surface silently blind on the next rename. */\nconst BLOCKED_SECTION_RE = /^#{1,2}\\s+(?:PR Edited \\(Blocked\\)|Edited\\/Blocked)\\s*$/i;\n\n/** `##` (or the `# ---` join separator) opens a new section; `###` does NOT.\n * Renovate emits `### <category>` subheadings INSIDE a section when\n * dependencyDashboardCategory is set, and treating those as boundaries would\n * truncate the blocked list to nothing. */\nconst SECTION_HEADING_RE = /^#{1,2}\\s+(.*?)\\s*$/;\n\n/**\n * Pull the branches Renovate has given up on out of a Dependency Dashboard\n * body. They live under the blocked heading — \"The following updates have been\n * manually edited so Renovate will no longer make changes.\" — each as a\n * `<!-- rebase-branch=NAME -->` marker.\n *\n * Section-scoped on purpose: `rebase-branch=` markers also appear under \"Open\"\n * (where they mean \"tick to rebase\", i.e. perfectly healthy — 11 fleet repos\n * carry them today), so a whole-body grep would report those repos as blocked.\n * `PR Closed (Blocked)` is deliberately NOT read: it is Renovate's ledger of\n * operator instructions it has honoured (closing a Renovate PR is the\n * documented ignore idiom) and it uses `recreate-branch=`, not `rebase-branch=`.\n */\nexport function parseBlockedBranches(body: string): string[] {\n const found: string[] = [];\n let inBlocked = false;\n for (const line of body.split(\"\\n\")) {\n if (SECTION_HEADING_RE.test(line)) {\n inBlocked = BLOCKED_SECTION_RE.test(line);\n continue;\n }\n if (!inBlocked) continue;\n const marker = line.match(/rebase-branch=([^\\s>]+)/);\n if (marker?.[1]) found.push(marker[1]);\n }\n return [...new Set(found)];\n}\n\n/**\n * Section headings in a dashboard body that Renovate is not known to emit.\n * `parseBlockedBranches` returning `[]` is ambiguous — it means both \"nothing\n * is blocked\" and \"I did not understand this dashboard\" — and this is what\n * separates them, so a heading rename cannot turn the whole fleet green in\n * silence. Same doctrine as workflowHealth: \"couldn't check\" must never read\n * as \"healthy\" downstream.\n */\nexport function parseUnknownSections(body: string): string[] {\n const unknown: string[] = [];\n for (const line of body.split(\"\\n\")) {\n const heading = line.match(/^##\\s+(.*?)\\s*$/);\n const title = heading?.[1];\n if (!title) continue;\n if (!KNOWN_DASHBOARD_SECTIONS.has(title.toLowerCase())) unknown.push(title);\n }\n return [...new Set(unknown)];\n}\n\nexport function makeGitHub(deps: { token: string; spawn?: SpawnFn }): GitHub {\n const spawn = deps.spawn ?? defaultSpawn;\n const env = { ...process.env, GH_TOKEN: deps.token };\n\n async function gh(args: string[]): Promise<string> {\n const r = await spawn(\"gh\", args, { env, timeoutMs: 60_000 });\n if (r.code !== 0) throw new Error(`gh ${args[0]} failed (code ${r.code}): ${r.stderr.trim()}`);\n return r.stdout;\n }\n\n /** `gh api` with a JSON request body. Ruleset payloads have nested arrays/\n * objects that `-f`/`-F` field flags cannot express, and the spawn wrapper\n * has no stdin — so the body rides a private temp file via `--input`. */\n async function ghJson(method: \"POST\" | \"PUT\", path: string, body: unknown): Promise<string> {\n const dir = await mkdtemp(join(tmpdir(), \"gh-json-\"));\n const file = join(dir, \"body.json\");\n try {\n await writeFile(file, JSON.stringify(body), \"utf-8\");\n return await gh([\"api\", \"-X\", method, path, \"--input\", file]);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n return {\n async openPullRequest(repo, pr) {\n const out = await gh([\n \"pr\",\n \"create\",\n \"--repo\",\n repo,\n \"--head\",\n pr.head,\n \"--base\",\n pr.base,\n \"--title\",\n pr.title,\n \"--body\",\n pr.body,\n ]);\n return { url: out.trim() };\n },\n async enableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=true\"]);\n },\n async disableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=false\"]);\n },\n async protectBranch(repo, branch, requiredChecks) {\n assertUrlSegment(\"branch\", branch);\n const args = [\n \"api\",\n \"-X\",\n \"PUT\",\n `repos/${repo}/branches/${branch}/protection`,\n \"-H\",\n \"Accept: application/vnd.github+json\",\n \"-F\",\n \"required_status_checks[strict]=true\",\n ...requiredChecks.flatMap((c) => [\"-f\", `required_status_checks[contexts][]=${c}`]),\n \"-F\",\n \"enforce_admins=true\",\n \"-F\",\n \"required_pull_request_reviews=null\",\n \"-F\",\n \"restrictions=null\",\n ];\n await gh(args);\n },\n async setRepoSecret(repo, name, value) {\n await gh([\"secret\", \"set\", name, \"--repo\", repo, \"--body\", value]);\n },\n async repoExists(repo) {\n const r = await spawn(\"gh\", [\"api\", `repos/${repo}`], { env, timeoutMs: 60_000 });\n return r.code === 0;\n },\n async defaultBranch(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".default_branch\"]);\n return out.trim();\n },\n // filesOnBranch and branchProtectionContexts call `spawn` directly (not the\n // throwing `gh()` helper) because a 404 is an expected, meaningful answer —\n // \"file/protection absent\" — not an error. The remaining readers use `gh()`\n // since a non-200 there is a genuine failure (e.g. missing token scope).\n async filesOnBranch(repo, branch, paths) {\n assertUrlSegment(\"branch\", branch);\n const present: string[] = [];\n for (const p of paths) {\n assertUrlSegment(\"path\", p);\n const r = await spawn(\"gh\", [`api`, `repos/${repo}/contents/${p}?ref=${branch}`], {\n env,\n timeoutMs: 60_000,\n });\n if (r.code === 0) present.push(p);\n }\n return present;\n },\n async fileContentsOnBranch(repo, branch, path) {\n // Same spawn-directly rationale as filesOnBranch: a 404 (file absent) is an\n // expected answer (null), not an error. The `raw` media type returns the file\n // bytes verbatim (no base64 envelope) so the result compares directly against a\n // canonical template.\n assertUrlSegment(\"branch\", branch);\n assertUrlSegment(\"path\", path);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/contents/${path}?ref=${branch}`,\n \"-H\",\n \"Accept: application/vnd.github.raw\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return null; // 404 = file absent on this branch\n return r.stdout;\n },\n async branchProtectionContexts(repo, branch) {\n assertUrlSegment(\"branch\", branch);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/branches/${branch}/protection`,\n \"--jq\",\n \".required_status_checks.contexts[]?\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return []; // 404 = no protection configured\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n },\n async secretExists(repo, name) {\n // per_page=100: the REST default of 30 would false-negative on a repo with >30 secrets,\n // wrongly reporting an existing secret absent (→ a needless overwrite).\n const out = await gh([\n \"api\",\n `repos/${repo}/actions/secrets?per_page=100`,\n \"--jq\",\n \".secrets[].name\",\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(name);\n },\n async autoMergeEnabled(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".allow_auto_merge\"]);\n return out.trim() === \"true\";\n },\n async findOpenSelfUpdatingPR(repo) {\n // per_page=100: with the REST default of 30, a repo with >30 open PRs (plausible under\n // Renovate) could page past the existing self-updating PR and open a duplicate.\n const out = await gh([\n \"api\",\n `repos/${repo}/pulls?state=open&per_page=100`,\n \"--jq\",\n '.[] | select(.head.ref | startswith(\"maint/self-updating-\")) | .html_url',\n ]);\n const first = out\n .split(\"\\n\")\n .map((l) => l.trim())\n .find((l) => l.length > 0);\n return first ?? null;\n },\n async openPullRequests(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`openPullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"pullRequests(states:OPEN,first:100,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number title url headRefName mergeable \" +\n \"commits(last:1){nodes{commit{statusCheckRollup{state}}}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n pullRequests?: {\n nodes?: Array<{\n number: number;\n title: string;\n url: string;\n headRefName: string;\n mergeable?: string;\n commits?: {\n nodes?: Array<{ commit?: { statusCheckRollup?: { state?: string } } }>;\n };\n }>;\n };\n };\n };\n };\n const nodes = parsed.data?.repository?.pullRequests?.nodes ?? [];\n return nodes.map((n) => ({\n number: n.number,\n title: n.title,\n url: n.url,\n headRef: n.headRefName,\n ciState: mapRollupState(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state),\n mergeable: mapMergeable(n.mergeable),\n }));\n },\n async defaultBranchStatus(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`defaultBranchStatus: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"defaultBranchRef{target{... on Commit{committedDate statusCheckRollup{state}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n defaultBranchRef?: {\n target?: { committedDate?: string; statusCheckRollup?: { state?: string } | null };\n } | null;\n };\n };\n };\n const target = parsed.data?.repository?.defaultBranchRef?.target;\n return {\n ciState: mapRollupState(target?.statusCheckRollup?.state),\n lastCommitAt: target?.committedDate ?? null,\n };\n },\n async mergedRenovatePullRequests(repo, sinceIso) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`mergedRenovatePullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n // per_page=50: comfortably covers a week of Renovate merges on one repo. The\n // list endpoint returns merged_at + head.ref, so the filter is local.\n const out = await gh([\n \"api\",\n `repos/${owner}/${name}/pulls?state=closed&sort=updated&direction=desc&per_page=50`,\n ]);\n const arr = JSON.parse(out) as Array<{\n number: number;\n title: string;\n html_url: string;\n merged_at: string | null;\n head?: { ref?: string };\n }>;\n return arr\n .filter(\n (p) =>\n p.merged_at !== null &&\n p.merged_at >= sinceIso && // ISO8601 UTC strings sort lexicographically\n (p.head?.ref ?? \"\").startsWith(\"renovate/\"),\n )\n .map((p) => ({\n number: p.number,\n title: p.title,\n url: p.html_url,\n mergedAt: p.merged_at as string,\n }));\n },\n async dispatchWorkflow(repo, workflow, ref) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`dispatchWorkflow: expected \"owner/repo\", got \"${repo}\"`);\n }\n // Every segment interpolates into the API path, so guard them all like the\n // other write methods do (defense in depth). `owner`/`name` are the most\n // operator-controlled (typed into Airtable's \"Git repo\"); `workflow` is a\n // constant today; `ref` is repo-sourced. A junk value like `repo?x=1` would\n // otherwise smuggle a query string past the bare two-part shape check.\n assertUrlSegment(\"path\", owner);\n assertUrlSegment(\"path\", name);\n assertUrlSegment(\"path\", workflow);\n assertUrlSegment(\"branch\", ref);\n await gh([\n \"api\",\n \"-X\",\n \"POST\",\n `repos/${owner}/${name}/actions/workflows/${workflow}/dispatches`,\n \"-f\",\n `ref=${ref}`,\n ]);\n },\n async listRepoRulesets(repo) {\n // includes_parents=false: only rulesets SOURCED on this repo are ours to\n // heal; an org-level ruleset (paid plans) must never be PUT from here.\n const out = await gh([\n \"api\",\n `repos/${repo}/rulesets?per_page=100&includes_parents=false`,\n \"--jq\",\n '.[] | \"\\\\(.id)\\\\t\\\\(.name)\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.includes(\"\\t\")) // a tab-less line has no id/name split — never fabricate a NaN row from it\n .map((l) => {\n const tab = l.indexOf(\"\\t\");\n return { id: Number(l.slice(0, tab)), name: l.slice(tab + 1) };\n })\n .filter((r) => Number.isSafeInteger(r.id));\n },\n async getRuleset(repo, id) {\n const out = await gh([\"api\", `repos/${repo}/rulesets/${Math.trunc(id)}`]);\n return JSON.parse(out) as ExistingRuleset;\n },\n async createRuleset(repo, payload) {\n await ghJson(\"POST\", `repos/${repo}/rulesets`, payload);\n },\n async updateRuleset(repo, id, payload) {\n // PUT, not PATCH: PATCH on this endpoint 404s (verified live 2026-08-01),\n // which a naive implementation would surface as \"update failed\" at best —\n // or, with a lenient error path, as a silent no-op reported as healed.\n await ghJson(\"PUT\", `repos/${repo}/rulesets/${Math.trunc(id)}`, payload);\n },\n async checkContextObserved(repo, ref, context) {\n assertUrlSegment(\"branch\", ref);\n // spawn-direct (not the throwing gh()): a 404/409 — empty repo, no commits\n // on the ref yet — is an expected answer meaning \"no evidence\", not an\n // error. Any failure degrades to false, i.e. \"don't require the check\n // yet\", which is the safe direction (see checkContextObserved's contract).\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/commits/${ref}/check-runs?per_page=100`,\n \"--jq\",\n \".check_runs[].name\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return false;\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(context);\n },\n async repoVisibility(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".visibility\"]);\n return out.trim();\n },\n async listOrgRepos(org) {\n assertUrlSegment(\"path\", org);\n // --paginate: the org is >27 repos and the default page of 30 is exactly\n // the trap that produced the 2026-07-31 false \"queue is empty\".\n const out = await gh([\n \"api\",\n \"--paginate\",\n `orgs/${org}/repos?per_page=100`,\n \"--jq\",\n '.[] | \"\\\\(.name)\\\\t\\\\(.visibility)\\\\t\\\\(.archived)\\\\t\\\\(.security_and_analysis.secret_scanning.status // \"unavailable\")\\\\t\\\\(.security_and_analysis.secret_scanning_push_protection.status // \"unavailable\")\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => {\n const [name = \"\", visibility = \"\", archived = \"\", ss = \"\", pp = \"\"] = l.split(\"\\t\");\n return {\n name,\n visibility,\n archived: archived === \"true\",\n secretScanning: ss || \"unavailable\",\n pushProtection: pp || \"unavailable\",\n };\n });\n },\n async openSecretAlerts(repo) {\n // spawn-direct: 403 (token without security_events) and 404 (scanning\n // off, or no visibility) are ANSWERS — \"I could not look\" — which the\n // caller turns into a gap. Anything else throws, so a transport failure\n // can never arrive downstream as a zero.\n //\n // --paginate + counting ids rather than `--jq length`: length prints once\n // PER PAGE, so a repo with 101 open alerts would print \"100\\n1\" and read\n // as 100 or NaN depending on who parsed it. Counting ids is page-shape\n // independent.\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n \"--paginate\",\n `repos/${repo}/secret-scanning/alerts?state=open&per_page=100`,\n \"--jq\",\n \".[].number\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) {\n if (/HTTP 40[34]/.test(r.stderr)) return \"unavailable\";\n throw new Error(`openSecretAlerts(${repo}) failed: ${r.stderr.trim()}`);\n }\n return r.stdout.split(\"\\n\").filter((l) => l.trim().length > 0).length;\n },\n async repoTextFile(repo, path) {\n assertUrlSegment(\"path\", repo);\n assertUrlSegment(\"path\", path);\n // spawn-direct: a 404 is the expected ANSWER \"this file is not there\",\n // and only that. Anything else throws (see the type's contract).\n const r = await spawn(\n \"gh\",\n [\"api\", `repos/${repo}/contents/${path}`, \"--jq\", '.content // \"\"'],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) {\n if (/HTTP 404/.test(r.stderr)) return null;\n throw new Error(`repoTextFile(${repo}/${path}) failed: ${r.stderr.trim()}`);\n }\n // The contents API returns base64 WRAPPED at 60 columns, so the newlines\n // have to come out before decoding or the tail of every file is lost.\n const encoded = r.stdout.replace(/\\s+/g, \"\");\n if (encoded.length === 0) return \"\";\n return Buffer.from(encoded, \"base64\").toString(\"utf-8\");\n },\n async listWorkflowPaths(repo) {\n assertUrlSegment(\"path\", repo);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/contents/.github/workflows`,\n \"--jq\",\n '.[] | select(.type==\"file\") | .path',\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) {\n if (/HTTP 404/.test(r.stderr)) return [];\n throw new Error(`listWorkflowPaths(${repo}) failed: ${r.stderr.trim()}`);\n }\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.endsWith(\".yml\") || l.endsWith(\".yaml\"));\n },\n async workflowHealth(repo, filename) {\n // spawn-direct for the workflow GET: 404 (file not registered as a\n // workflow) is an expected ANSWER, not an error. Everything else throws —\n // per the contract, an unreadable workflow must surface as unverified.\n const wf = await spawn(\n \"gh\",\n [\"api\", `repos/${repo}/actions/workflows/${filename}`, \"--jq\", \".state\"],\n { env, timeoutMs: 60_000 },\n );\n if (wf.code !== 0) {\n if (/HTTP 404/.test(wf.stderr)) return { present: false };\n throw new Error(`workflowHealth(${repo}/${filename}) failed: ${wf.stderr.trim()}`);\n }\n // status=success: filter to runs that actually CONCLUDED success. Without\n // it, a schedule whose every run fails (revoked App key, broken config)\n // reads as hours-fresh forever while zero updates flow.\n const runs = await gh([\n \"api\",\n `repos/${repo}/actions/workflows/${filename}/runs?status=success&per_page=1`,\n \"--jq\",\n '.workflow_runs[0].created_at // \"\"',\n ]);\n return { present: true, state: wf.stdout.trim(), lastSuccessAt: runs.trim() || null };\n },\n async dependencyDashboard(repo) {\n // Every open Dependency Dashboard, not just the first: the App-identity\n // migration left several repos holding TWO (alamo-anatomy had #3 and\n // #39), and the stale one is not always the one carrying the blocked\n // section. `select(.pull_request | not)` because /issues returns PRs too.\n //\n // --paginate is load-bearing, not defensive: /issues returns issues AND\n // PRs newest-first, the dashboard is created at onboarding and so is\n // typically the OLDEST open issue in the repo, and the title filter runs\n // client-side in jq AFTER the page cut. Without it, a repo with 100 open\n // items newer than its dashboard reads as {present:false} — which this\n // surface deliberately treats as not-a-gap, i.e. a blocked repo would\n // report clean. (Verified against renovatebot/renovate itself, whose\n // dashboard is issue #2958 from 2018 and carries a live blocked section.)\n const out = await gh([\n \"api\",\n \"--paginate\",\n `repos/${repo}/issues?state=open&per_page=100`,\n \"--jq\",\n '[.[] | select(.pull_request | not) | select(.title == \"Dependency Dashboard\") | (.body // \"\")] | join(\"\\\\n# ---\\\\n\")',\n ]);\n const body = out.trim();\n if (body.length === 0) return { present: false };\n return {\n present: true,\n blockedBranches: parseBlockedBranches(body),\n unknownSections: parseUnknownSections(body),\n };\n },\n async branchTip(repo, branch) {\n // Raw, not percent-encoded: the ref segment legitimately contains `/`\n // (`renovate/all-minor-patch`) and GitHub resolves it as a ref only when\n // the slash is literal. assertUrlSegment is what keeps that safe.\n assertUrlSegment(\"branch\", branch);\n let out: string;\n try {\n out = await gh([\n \"api\",\n `repos/${repo}/commits/${branch}`,\n \"--jq\",\n // .author is the GitHub ACCOUNT (null when the commit email matches\n // none), .commit.author is the raw git identity, which always exists.\n '{type: (.author.type // \"\"), login: (.author.login // \"\"), email: .commit.author.email, name: .commit.author.name, date: .commit.committer.date}',\n ]);\n } catch (e) {\n if (/HTTP 404/.test(e instanceof Error ? e.message : String(e))) return null;\n throw e;\n }\n const raw = JSON.parse(out) as {\n type: string;\n login: string;\n email: string;\n name: string;\n date: string;\n };\n return { authorIsMachine: isMachineAuthor(raw), committedAt: raw.date };\n },\n async renovateMergeWindow(repo) {\n // `sort=updated&direction=desc` is the only ordering this endpoint offers\n // that tracks recency; merged_at + head.ref come back on the list item, so\n // no per-PR call is needed. ONE request per repo is the whole cost.\n const out = await gh([\n \"api\",\n `repos/${repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${RENOVATE_MERGE_WINDOW_PAGE}`,\n ]);\n const arr = JSON.parse(out) as Array<{\n merged_at: string | null;\n head?: { ref?: string };\n }>;\n const merges = arr\n .filter((p) => p.merged_at !== null && (p.head?.ref ?? \"\").startsWith(\"renovate/\"))\n .map((p) => ({ headRef: p.head!.ref!, mergedAt: p.merged_at as string }))\n // ISO8601 UTC strings sort lexicographically; `updated` order is not\n // merge order, so sort explicitly rather than trusting the page.\n .sort((a, b) => b.mergedAt.localeCompare(a.mergedAt));\n return { merges, truncated: arr.length >= RENOVATE_MERGE_WINDOW_PAGE };\n },\n };\n}\n\n/**\n * Does this commit have a human behind it? Deliberately NOT just\n * `.author.type === \"Bot\"`: the identity whose orphaned branches froze nine\n * repos in August 2026 was `renovate-bot`, a PAT-driven machine account that\n * GitHub reports as type `User`. A rule keyed on the Bot type would have\n * missed the exact incident this audit exists to catch, so the known machine\n * logins are matched by name too.\n *\n * A miss here costs latency, never blindness — renovateBlockedGaps still\n * reports any blocked branch once it goes stale, whoever authored it.\n */\nexport function isMachineAuthor(commit: {\n type: string;\n login: string;\n email: string;\n name: string;\n}): boolean {\n if (commit.type === \"Bot\") return true;\n return [commit.login, commit.email, commit.name].some((f) =>\n /(\\[bot\\]|^renovate-bot$|^renovate-bot@)/i.test(f ?? \"\"),\n );\n}\n"],"mappings":";;;;;AAAA,SAAS,SAAS,IAAI,iBAAiB;AACvC,SAAS,cAAc;AACvB,SAAS,YAAY;AAqCd,SAAS,iBAAiB,MAAyB,OAAqB;AAC7E,QAAM,aAAa;AACnB,MAAI,MAAM,WAAW,KAAK,MAAM,WAAW,GAAG,KAAK,WAAW,KAAK,KAAK,GAAG;AACzE,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,uDAAuD,KAAK,UAAU,KAAK,CAAC;AAAA,IAC5F;AAAA,EACF;AACF;AAGA,SAAS,eAAe,OAA2C;AACjE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKA,SAAS,aAAa,OAA+C;AACnE,SAAO,UAAU,eAAe,UAAU,gBAAgB,QAAQ;AACpE;AA0IO,IAAM,6BAA6B;AAgB1C,IAAM,2BAA2B,oBAAI,IAAI;AAAA;AAAA,EAEvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF,CAAC;AAOD,IAAM,qBAAqB;AAM3B,IAAM,qBAAqB;AAepB,SAAS,qBAAqB,MAAwB;AAC3D,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,QAAI,mBAAmB,KAAK,IAAI,GAAG;AACjC,kBAAY,mBAAmB,KAAK,IAAI;AACxC;AAAA,IACF;AACA,QAAI,CAAC,UAAW;AAChB,UAAM,SAAS,KAAK,MAAM,yBAAyB;AACnD,QAAI,SAAS,CAAC,EAAG,OAAM,KAAK,OAAO,CAAC,CAAC;AAAA,EACvC;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;AAC3B;AAUO,SAAS,qBAAqB,MAAwB;AAC3D,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,UAAM,QAAQ,UAAU,CAAC;AACzB,QAAI,CAAC,MAAO;AACZ,QAAI,CAAC,yBAAyB,IAAI,MAAM,YAAY,CAAC,EAAG,SAAQ,KAAK,KAAK;AAAA,EAC5E;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC;AAC7B;AAEO,SAAS,WAAW,MAAkD;AAC3E,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,MAAM,EAAE,GAAG,QAAQ,KAAK,UAAU,KAAK,MAAM;AAEnD,iBAAe,GAAG,MAAiC;AACjD,UAAM,IAAI,MAAM,MAAM,MAAM,MAAM,EAAE,KAAK,WAAW,IAAO,CAAC;AAC5D,QAAI,EAAE,SAAS,EAAG,OAAM,IAAI,MAAM,MAAM,KAAK,CAAC,CAAC,iBAAiB,EAAE,IAAI,MAAM,EAAE,OAAO,KAAK,CAAC,EAAE;AAC7F,WAAO,EAAE;AAAA,EACX;AAKA,iBAAe,OAAO,QAAwB,MAAc,MAAgC;AAC1F,UAAM,MAAM,MAAM,QAAQ,KAAK,OAAO,GAAG,UAAU,CAAC;AACpD,UAAM,OAAO,KAAK,KAAK,WAAW;AAClC,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,UAAU,IAAI,GAAG,OAAO;AACnD,aAAO,MAAM,GAAG,CAAC,OAAO,MAAM,QAAQ,MAAM,WAAW,IAAI,CAAC;AAAA,IAC9D,UAAE;AACA,YAAM,GAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,gBAAgB,MAAM,IAAI;AAC9B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG;AAAA,QACH;AAAA,QACA,GAAG;AAAA,QACH;AAAA,QACA,GAAG;AAAA,QACH;AAAA,QACA,GAAG;AAAA,MACL,CAAC;AACD,aAAO,EAAE,KAAK,IAAI,KAAK,EAAE;AAAA,IAC3B;AAAA,IACA,MAAM,oBAAoB,MAAM;AAC9B,YAAM,GAAG,CAAC,OAAO,MAAM,SAAS,SAAS,IAAI,IAAI,MAAM,uBAAuB,CAAC;AAAA,IACjF;AAAA,IACA,MAAM,qBAAqB,MAAM;AAC/B,YAAM,GAAG,CAAC,OAAO,MAAM,SAAS,SAAS,IAAI,IAAI,MAAM,wBAAwB,CAAC;AAAA,IAClF;AAAA,IACA,MAAM,cAAc,MAAM,QAAQ,gBAAgB;AAChD,uBAAiB,UAAU,MAAM;AACjC,YAAM,OAAO;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,IAAI,aAAa,MAAM;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,eAAe,QAAQ,CAAC,MAAM,CAAC,MAAM,sCAAsC,CAAC,EAAE,CAAC;AAAA,QAClF;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,GAAG,IAAI;AAAA,IACf;AAAA,IACA,MAAM,cAAc,MAAM,MAAM,OAAO;AACrC,YAAM,GAAG,CAAC,UAAU,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IACnE;AAAA,IACA,MAAM,WAAW,MAAM;AACrB,YAAM,IAAI,MAAM,MAAM,MAAM,CAAC,OAAO,SAAS,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW,IAAO,CAAC;AAChF,aAAO,EAAE,SAAS;AAAA,IACpB;AAAA,IACA,MAAM,cAAc,MAAM;AACxB,YAAM,MAAM,MAAM,GAAG,CAAC,OAAO,SAAS,IAAI,IAAI,QAAQ,iBAAiB,CAAC;AACxE,aAAO,IAAI,KAAK;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,cAAc,MAAM,QAAQ,OAAO;AACvC,uBAAiB,UAAU,MAAM;AACjC,YAAM,UAAoB,CAAC;AAC3B,iBAAW,KAAK,OAAO;AACrB,yBAAiB,QAAQ,CAAC;AAC1B,cAAM,IAAI,MAAM,MAAM,MAAM,CAAC,OAAO,SAAS,IAAI,aAAa,CAAC,QAAQ,MAAM,EAAE,GAAG;AAAA,UAChF;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AACD,YAAI,EAAE,SAAS,EAAG,SAAQ,KAAK,CAAC;AAAA,MAClC;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,qBAAqB,MAAM,QAAQ,MAAM;AAK7C,uBAAiB,UAAU,MAAM;AACjC,uBAAiB,QAAQ,IAAI;AAC7B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,UACE;AAAA,UACA,SAAS,IAAI,aAAa,IAAI,QAAQ,MAAM;AAAA,UAC5C;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,EAAG,QAAO;AACzB,aAAO,EAAE;AAAA,IACX;AAAA,IACA,MAAM,yBAAyB,MAAM,QAAQ;AAC3C,uBAAiB,UAAU,MAAM;AACjC,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,UACE;AAAA,UACA,SAAS,IAAI,aAAa,MAAM;AAAA,UAChC;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,EAAG,QAAO,CAAC;AAC1B,aAAO,EAAE,OACN,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,IAC/B;AAAA,IACA,MAAM,aAAa,MAAM,MAAM;AAG7B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,IACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,SAAS,IAAI;AAAA,IAClB;AAAA,IACA,MAAM,iBAAiB,MAAM;AAC3B,YAAM,MAAM,MAAM,GAAG,CAAC,OAAO,SAAS,IAAI,IAAI,QAAQ,mBAAmB,CAAC;AAC1E,aAAO,IAAI,KAAK,MAAM;AAAA,IACxB;AAAA,IACA,MAAM,uBAAuB,MAAM;AAGjC,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,IACX,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;AAC3B,aAAO,SAAS;AAAA,IAClB;AAAA,IACA,MAAM,iBAAiB,MAAM;AAC3B,YAAM,CAAC,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7C,UAAI,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,GAAG;AACtC,cAAM,IAAI,MAAM,iDAAiD,IAAI,GAAG;AAAA,MAC1E;AACA,YAAM,QACJ;AAGF,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,KAAK;AAAA,QACd;AAAA,QACA,SAAS,KAAK;AAAA,QACd;AAAA,QACA,QAAQ,IAAI;AAAA,MACd,CAAC;AACD,YAAM,SAAS,KAAK,MAAM,GAAG;AAkB7B,YAAM,QAAQ,OAAO,MAAM,YAAY,cAAc,SAAS,CAAC;AAC/D,aAAO,MAAM,IAAI,CAAC,OAAO;AAAA,QACvB,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP,SAAS,EAAE;AAAA,QACX,SAAS,eAAe,EAAE,SAAS,QAAQ,CAAC,GAAG,QAAQ,mBAAmB,KAAK;AAAA,QAC/E,WAAW,aAAa,EAAE,SAAS;AAAA,MACrC,EAAE;AAAA,IACJ;AAAA,IACA,MAAM,oBAAoB,MAAM;AAC9B,YAAM,CAAC,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7C,UAAI,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,GAAG;AACtC,cAAM,IAAI,MAAM,oDAAoD,IAAI,GAAG;AAAA,MAC7E;AACA,YAAM,QACJ;AAEF,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,KAAK;AAAA,QACd;AAAA,QACA,SAAS,KAAK;AAAA,QACd;AAAA,QACA,QAAQ,IAAI;AAAA,MACd,CAAC;AACD,YAAM,SAAS,KAAK,MAAM,GAAG;AAS7B,YAAM,SAAS,OAAO,MAAM,YAAY,kBAAkB;AAC1D,aAAO;AAAA,QACL,SAAS,eAAe,QAAQ,mBAAmB,KAAK;AAAA,QACxD,cAAc,QAAQ,iBAAiB;AAAA,MACzC;AAAA,IACF;AAAA,IACA,MAAM,2BAA2B,MAAM,UAAU;AAC/C,YAAM,CAAC,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7C,UAAI,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,GAAG;AACtC,cAAM,IAAI,MAAM,2DAA2D,IAAI,GAAG;AAAA,MACpF;AAGA,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,SAAS,KAAK,IAAI,IAAI;AAAA,MACxB,CAAC;AACD,YAAM,MAAM,KAAK,MAAM,GAAG;AAO1B,aAAO,IACJ;AAAA,QACC,CAAC,MACC,EAAE,cAAc,QAChB,EAAE,aAAa;AAAA,SACd,EAAE,MAAM,OAAO,IAAI,WAAW,WAAW;AAAA,MAC9C,EACC,IAAI,CAAC,OAAO;AAAA,QACX,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP,UAAU,EAAE;AAAA,MACd,EAAE;AAAA,IACN;AAAA,IACA,MAAM,iBAAiB,MAAM,UAAU,KAAK;AAC1C,YAAM,CAAC,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7C,UAAI,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,GAAG;AACtC,cAAM,IAAI,MAAM,iDAAiD,IAAI,GAAG;AAAA,MAC1E;AAMA,uBAAiB,QAAQ,KAAK;AAC9B,uBAAiB,QAAQ,IAAI;AAC7B,uBAAiB,QAAQ,QAAQ;AACjC,uBAAiB,UAAU,GAAG;AAC9B,YAAM,GAAG;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,KAAK,IAAI,IAAI,sBAAsB,QAAQ;AAAA,QACpD;AAAA,QACA,OAAO,GAAG;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,IACA,MAAM,iBAAiB,MAAM;AAG3B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,IACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAI,CAAC,EAC9B,IAAI,CAAC,MAAM;AACV,cAAM,MAAM,EAAE,QAAQ,GAAI;AAC1B,eAAO,EAAE,IAAI,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,MAAM,MAAM,CAAC,EAAE;AAAA,MAC/D,CAAC,EACA,OAAO,CAAC,MAAM,OAAO,cAAc,EAAE,EAAE,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM,WAAW,MAAM,IAAI;AACzB,YAAM,MAAM,MAAM,GAAG,CAAC,OAAO,SAAS,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC,EAAE,CAAC;AACxE,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB;AAAA,IACA,MAAM,cAAc,MAAM,SAAS;AACjC,YAAM,OAAO,QAAQ,SAAS,IAAI,aAAa,OAAO;AAAA,IACxD;AAAA,IACA,MAAM,cAAc,MAAM,IAAI,SAAS;AAIrC,YAAM,OAAO,OAAO,SAAS,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC,IAAI,OAAO;AAAA,IACzE;AAAA,IACA,MAAM,qBAAqB,MAAM,KAAK,SAAS;AAC7C,uBAAiB,UAAU,GAAG;AAK9B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,UACE;AAAA,UACA,SAAS,IAAI,YAAY,GAAG;AAAA,UAC5B;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,EAAG,QAAO;AACzB,aAAO,EAAE,OACN,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,SAAS,OAAO;AAAA,IACrB;AAAA,IACA,MAAM,eAAe,MAAM;AACzB,YAAM,MAAM,MAAM,GAAG,CAAC,OAAO,SAAS,IAAI,IAAI,QAAQ,aAAa,CAAC;AACpE,aAAO,IAAI,KAAK;AAAA,IAClB;AAAA,IACA,MAAM,aAAa,KAAK;AACtB,uBAAiB,QAAQ,GAAG;AAG5B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA,QAAQ,GAAG;AAAA,QACX;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,IACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,MAAM;AACV,cAAM,CAAC,OAAO,IAAI,aAAa,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,EAAE,IAAI,EAAE,MAAM,GAAI;AAClF,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,UAAU,aAAa;AAAA,UACvB,gBAAgB,MAAM;AAAA,UACtB,gBAAgB,MAAM;AAAA,QACxB;AAAA,MACF,CAAC;AAAA,IACL;AAAA,IACA,MAAM,iBAAiB,MAAM;AAU3B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS,IAAI;AAAA,UACb;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,GAAG;AAChB,YAAI,cAAc,KAAK,EAAE,MAAM,EAAG,QAAO;AACzC,cAAM,IAAI,MAAM,oBAAoB,IAAI,aAAa,EAAE,OAAO,KAAK,CAAC,EAAE;AAAA,MACxE;AACA,aAAO,EAAE,OAAO,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE;AAAA,IACjE;AAAA,IACA,MAAM,aAAa,MAAM,MAAM;AAC7B,uBAAiB,QAAQ,IAAI;AAC7B,uBAAiB,QAAQ,IAAI;AAG7B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA,CAAC,OAAO,SAAS,IAAI,aAAa,IAAI,IAAI,QAAQ,gBAAgB;AAAA,QAClE,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,GAAG;AAChB,YAAI,WAAW,KAAK,EAAE,MAAM,EAAG,QAAO;AACtC,cAAM,IAAI,MAAM,gBAAgB,IAAI,IAAI,IAAI,aAAa,EAAE,OAAO,KAAK,CAAC,EAAE;AAAA,MAC5E;AAGA,YAAM,UAAU,EAAE,OAAO,QAAQ,QAAQ,EAAE;AAC3C,UAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,aAAO,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,OAAO;AAAA,IACxD;AAAA,IACA,MAAM,kBAAkB,MAAM;AAC5B,uBAAiB,QAAQ,IAAI;AAC7B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,UACE;AAAA,UACA,SAAS,IAAI;AAAA,UACb;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,EAAE,SAAS,GAAG;AAChB,YAAI,WAAW,KAAK,EAAE,MAAM,EAAG,QAAO,CAAC;AACvC,cAAM,IAAI,MAAM,qBAAqB,IAAI,aAAa,EAAE,OAAO,KAAK,CAAC,EAAE;AAAA,MACzE;AACA,aAAO,EAAE,OACN,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,OAAO,CAAC;AAAA,IAC5D;AAAA,IACA,MAAM,eAAe,MAAM,UAAU;AAInC,YAAM,KAAK,MAAM;AAAA,QACf;AAAA,QACA,CAAC,OAAO,SAAS,IAAI,sBAAsB,QAAQ,IAAI,QAAQ,QAAQ;AAAA,QACvE,EAAE,KAAK,WAAW,IAAO;AAAA,MAC3B;AACA,UAAI,GAAG,SAAS,GAAG;AACjB,YAAI,WAAW,KAAK,GAAG,MAAM,EAAG,QAAO,EAAE,SAAS,MAAM;AACxD,cAAM,IAAI,MAAM,kBAAkB,IAAI,IAAI,QAAQ,aAAa,GAAG,OAAO,KAAK,CAAC,EAAE;AAAA,MACnF;AAIA,YAAM,OAAO,MAAM,GAAG;AAAA,QACpB;AAAA,QACA,SAAS,IAAI,sBAAsB,QAAQ;AAAA,QAC3C;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,EAAE,SAAS,MAAM,OAAO,GAAG,OAAO,KAAK,GAAG,eAAe,KAAK,KAAK,KAAK,KAAK;AAAA,IACtF;AAAA,IACA,MAAM,oBAAoB,MAAM;AAc9B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,OAAO,IAAI,KAAK;AACtB,UAAI,KAAK,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM;AAC/C,aAAO;AAAA,QACL,SAAS;AAAA,QACT,iBAAiB,qBAAqB,IAAI;AAAA,QAC1C,iBAAiB,qBAAqB,IAAI;AAAA,MAC5C;AAAA,IACF;AAAA,IACA,MAAM,UAAU,MAAM,QAAQ;AAI5B,uBAAiB,UAAU,MAAM;AACjC,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,GAAG;AAAA,UACb;AAAA,UACA,SAAS,IAAI,YAAY,MAAM;AAAA,UAC/B;AAAA;AAAA;AAAA,UAGA;AAAA,QACF,CAAC;AAAA,MACH,SAAS,GAAG;AACV,YAAI,WAAW,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAG,QAAO;AACxE,cAAM;AAAA,MACR;AACA,YAAM,MAAM,KAAK,MAAM,GAAG;AAO1B,aAAO,EAAE,iBAAiB,gBAAgB,GAAG,GAAG,aAAa,IAAI,KAAK;AAAA,IACxE;AAAA,IACA,MAAM,oBAAoB,MAAM;AAI9B,YAAM,MAAM,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,SAAS,IAAI,4DAA4D,0BAA0B;AAAA,MACrG,CAAC;AACD,YAAM,MAAM,KAAK,MAAM,GAAG;AAI1B,YAAM,SAAS,IACZ,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS,EAAE,MAAM,OAAO,IAAI,WAAW,WAAW,CAAC,EACjF,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,KAAM,KAAM,UAAU,EAAE,UAAoB,EAAE,EAGvE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,QAAQ,CAAC;AACtD,aAAO,EAAE,QAAQ,WAAW,IAAI,UAAU,2BAA2B;AAAA,IACvE;AAAA,EACF;AACF;AAaO,SAAS,gBAAgB,QAKpB;AACV,MAAI,OAAO,SAAS,MAAO,QAAO;AAClC,SAAO,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,IAAI,EAAE;AAAA,IAAK,CAAC,MACrD,2CAA2C,KAAK,KAAK,EAAE;AAAA,EACzD;AACF;","names":[]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchGaUsers,
|
|
3
3
|
fetchSearch
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-72FJZ6LO.js";
|
|
5
5
|
import {
|
|
6
6
|
announcementSiteExtras,
|
|
7
7
|
resolveCopy
|
|
8
8
|
} from "./chunk-KJGKZLCT.js";
|
|
9
9
|
import {
|
|
10
10
|
siteSlug
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KN52PM63.js";
|
|
12
12
|
|
|
13
13
|
// src/reports/report-data.ts
|
|
14
14
|
var PREVIEW_WINDOW_DAYS = 30;
|
|
@@ -60,4 +60,4 @@ export {
|
|
|
60
60
|
scoresFromRow,
|
|
61
61
|
buildReportDataForSite
|
|
62
62
|
};
|
|
63
|
-
//# sourceMappingURL=chunk-
|
|
63
|
+
//# sourceMappingURL=chunk-44SDGPGQ.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ignoreRulesFor,
|
|
3
|
+
pathsMissingFromHead
|
|
4
|
+
} from "./chunk-EASYCD6F.js";
|
|
5
|
+
|
|
6
|
+
// src/recipes/_head-guard.ts
|
|
7
|
+
import { rm, rmdir } from "fs/promises";
|
|
8
|
+
import { writeFile } from "fs/promises";
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
var RESTORED_NOTE = "Everything this run wrote to those paths has been put back as it was.";
|
|
11
|
+
async function refusedByGit(cwd, installed, what) {
|
|
12
|
+
const missing = await pathsMissingFromHead(cwd, installed);
|
|
13
|
+
if (missing.length === 0) return null;
|
|
14
|
+
const rules = await ignoreRulesFor(cwd, missing);
|
|
15
|
+
const sources = [...new Set(rules.map((r) => r.split(" ")[0]))];
|
|
16
|
+
const explained = new Set(rules.map((r) => r.split(" ")[1] ?? ""));
|
|
17
|
+
const unexplained = missing.filter((p) => !explained.has(p));
|
|
18
|
+
return {
|
|
19
|
+
missing,
|
|
20
|
+
notes: `${what} was NOT installed: ${missing.length} of ${installed.length} paths git refused to track, so a fresh clone and CI get nothing. Absent from the commit: ${missing.join(", ")}. ` + (sources.length > 0 ? `Excluded by ${sources.join(", ")} \u2014 narrow or remove those rules and re-run. ` : "") + (unexplained.length > 0 ? `No ignore rule matched ${unexplained.join(", ")} \u2014 check .git/info/exclude, core.excludesFile, and whether the directory is a nested repository. ` : "")
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function undoRefusedWrites(cwd, before, missing) {
|
|
24
|
+
const removed = [];
|
|
25
|
+
for (const [rel, prev] of before) {
|
|
26
|
+
if (!missing.includes(rel)) continue;
|
|
27
|
+
if (prev === null) {
|
|
28
|
+
await rm(join(cwd, rel), { force: true });
|
|
29
|
+
removed.push(rel);
|
|
30
|
+
} else {
|
|
31
|
+
await writeFile(join(cwd, rel), prev, "utf-8");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const rel of removed) {
|
|
35
|
+
let d = dirname(rel);
|
|
36
|
+
while (d !== "." && d !== "/" && d !== "") {
|
|
37
|
+
try {
|
|
38
|
+
await rmdir(join(cwd, d));
|
|
39
|
+
} catch {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
d = dirname(d);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
RESTORED_NOTE,
|
|
49
|
+
refusedByGit,
|
|
50
|
+
undoRefusedWrites
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-4UTJ4G6P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/recipes/_head-guard.ts"],"sourcesContent":["import { rm, rmdir } from \"node:fs/promises\";\nimport { writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { ignoreRulesFor, pathsMissingFromHead } from \"../util/git.js\";\n\n/** A refusal: what git did not take, and what to tell the operator about it. */\nexport type HeadRefusal = {\n /** The subset of the manifest that is NOT in HEAD's tree. */\n missing: string[];\n /** Operator-facing: what is absent from the commit, and which rule excluded it. */\n notes: string;\n};\n\n/** Appended by a caller that has already put the refused paths back. */\nexport const RESTORED_NOTE =\n \"Everything this run wrote to those paths has been put back as it was.\";\n\n/**\n * POSITIVE EVIDENCE that the commit really carries `installed`: every path must\n * be FOUND in HEAD's tree. `commit()` stages with `git add -A`, which honours\n * the target site's `.gitignore` and exits 0 either way, and git cannot\n * re-include a file whose PARENT DIRECTORY is excluded. So a recipe can write\n * its files to disk, commit none of them, and report success — #734 for\n * `match-harness`, generalised to every recipe that creates NEW paths by #741.\n *\n * `what` names the install in the operator's terms (\"the /health endpoint\").\n * Returns null when every path landed — i.e. the guard GRANTS.\n *\n * PRESENCE, NOT CONTENT: a path in HEAD with the wrong bytes passes here. This\n * answers \"the commit contains what the recipe says it installed\", and must\n * never be read as \"the recipe worked\".\n */\nexport async function refusedByGit(\n cwd: string,\n installed: readonly string[],\n what: string,\n): Promise<HeadRefusal | null> {\n const missing = await pathsMissingFromHead(cwd, installed);\n if (missing.length === 0) return null;\n\n // `<source>:<line>:<pattern>\\t<path>` rows. A missing path with no row is\n // absent for some other reason (a nested repository, say) and is reported as\n // such rather than guessed at.\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 return {\n missing,\n notes:\n `${what} was NOT installed: ${missing.length} of ${installed.length} paths git refused to ` +\n `track, so a fresh clone and CI get nothing. 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, ` +\n `core.excludesFile, and whether the directory is a nested repository. `\n : \"\"),\n };\n}\n\n/**\n * Put the checkout back exactly as this run found it, for the paths git refused.\n * Only paths THIS RUN wrote are touched — `before` maps each to its prior\n * content, or null when it did not exist; a path that reached the commit is left\n * to the caller's branch restore, and one we never wrote is never ours.\n * `git checkout -f` cannot do this job: these paths are ignored, so git does not\n * know they exist.\n *\n * Without it the residue is worse than the defect. Every recipe here noops on\n * \"already exists\", so the file git refused would make the NEXT run report the\n * site done over a path no clone will ever have.\n */\nexport async function undoRefusedWrites(\n cwd: string,\n before: ReadonlyMap<string, string | null>,\n missing: readonly string[],\n): Promise<void> {\n const removed: string[] = [];\n for (const [rel, prev] of before) {\n if (!missing.includes(rel)) 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 // Prune the directories this run created, deepest first and only while they\n // are empty: rmdir refuses a non-empty directory, which is exactly the guard\n // wanted, and the walk upward stops at the first one that is someone else's.\n for (const rel of removed) {\n let d = dirname(rel);\n while (d !== \".\" && d !== \"/\" && d !== \"\") {\n try {\n await rmdir(join(cwd, d));\n } catch {\n break;\n }\n d = dirname(d);\n }\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,IAAI,aAAa;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,SAAS,YAAY;AAYvB,IAAM,gBACX;AAiBF,eAAsB,aACpB,KACA,WACA,MAC6B;AAC7B,QAAM,UAAU,MAAM,qBAAqB,KAAK,SAAS;AACzD,MAAI,QAAQ,WAAW,EAAG,QAAO;AAKjC,QAAM,QAAQ,MAAM,eAAe,KAAK,OAAO;AAC/C,QAAM,UAAU,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,GAAI,EAAE,CAAC,CAAE,CAAC,CAAC;AAChE,QAAM,YAAY,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,GAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AAClE,QAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;AAE3D,SAAO;AAAA,IACL;AAAA,IACA,OACE,GAAG,IAAI,uBAAuB,QAAQ,MAAM,OAAO,UAAU,MAAM,6FACI,QAAQ,KAAK,IAAI,CAAC,QACxF,QAAQ,SAAS,IACd,eAAe,QAAQ,KAAK,IAAI,CAAC,sDACjC,OACH,YAAY,SAAS,IAClB,0BAA0B,YAAY,KAAK,IAAI,CAAC,2GAEhD;AAAA,EACR;AACF;AAcA,eAAsB,kBACpB,KACA,QACA,SACe;AACf,QAAM,UAAoB,CAAC;AAC3B,aAAW,CAAC,KAAK,IAAI,KAAK,QAAQ;AAChC,QAAI,CAAC,QAAQ,SAAS,GAAG,EAAG;AAC5B,QAAI,SAAS,MAAM;AACjB,YAAM,GAAG,KAAK,KAAK,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;AACxC,cAAQ,KAAK,GAAG;AAAA,IAClB,OAAO;AACL,YAAM,UAAU,KAAK,KAAK,GAAG,GAAG,MAAM,OAAO;AAAA,IAC/C;AAAA,EACF;AAIA,aAAW,OAAO,SAAS;AACzB,QAAI,IAAI,QAAQ,GAAG;AACnB,WAAO,MAAM,OAAO,MAAM,OAAO,MAAM,IAAI;AACzC,UAAI;AACF,cAAM,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,MAC1B,QAAQ;AACN;AAAA,MACF;AACA,UAAI,QAAQ,CAAC;AAAA,IACf;AAAA,EACF;AACF;","names":[]}
|