@reddoorla/maintenance 0.95.1 → 0.97.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -7
- package/dist/{announce-CLQYX5I4.js → announce-5ZD2SBGD.js} +39 -31
- package/dist/announce-5ZD2SBGD.js.map +1 -0
- package/dist/attachments-JFGFQRXQ.js +10 -0
- package/dist/{blux-4KNXUP2K.js → blux-NXQLMS4W.js} +1 -1
- package/dist/{bump-deps-C5G2EFBB.js → bump-deps-775VLDYB.js} +6 -6
- package/dist/{chunk-FUWFLAWQ.js → chunk-3RLDX3Q2.js} +5 -5
- package/dist/{chunk-XHHMM5HX.js → chunk-43SW3FGW.js} +63 -1
- package/dist/chunk-43SW3FGW.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-S7RYFUIK.js → chunk-6PGXKN6F.js} +2 -2
- package/dist/{chunk-HTABUCF4.js → chunk-6THEZQLA.js} +3 -3
- package/dist/chunk-A5QKNOXW.js +72 -0
- package/dist/chunk-A5QKNOXW.js.map +1 -0
- package/dist/chunk-ASLDCWBF.js +41 -0
- package/dist/chunk-ASLDCWBF.js.map +1 -0
- package/dist/{chunk-CWXTNPNZ.js → chunk-AYDQQBNG.js} +3 -3
- package/dist/chunk-AYDQQBNG.js.map +1 -0
- package/dist/{chunk-WVNDEYXL.js → chunk-BINFZQ2T.js} +57 -6
- package/dist/chunk-BINFZQ2T.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-YLYXW5PH.js → chunk-CXICCLNM.js} +34 -4
- package/dist/chunk-CXICCLNM.js.map +1 -0
- package/dist/{chunk-BYGPUUCT.js → chunk-DRNZVORX.js} +66 -30
- package/dist/chunk-DRNZVORX.js.map +1 -0
- package/dist/{chunk-LM2NHRCJ.js → chunk-DYTYPKFN.js} +4 -4
- package/dist/chunk-DYTYPKFN.js.map +1 -0
- package/dist/{chunk-DFKBTBO3.js → chunk-EN6CTBUN.js} +35 -6
- package/dist/chunk-EN6CTBUN.js.map +1 -0
- package/dist/{chunk-5EEMTJMT.js → chunk-FHFXJ3EM.js} +8 -6
- package/dist/chunk-FHFXJ3EM.js.map +1 -0
- package/dist/{chunk-Z2K4JPQ3.js → chunk-FP6HOW6J.js} +10 -7
- package/dist/chunk-FP6HOW6J.js.map +1 -0
- package/dist/{chunk-2LYOAB2E.js → chunk-HSMN35LU.js} +2 -2
- package/dist/chunk-ICSRWW5Q.js +148 -0
- package/dist/chunk-ICSRWW5Q.js.map +1 -0
- package/dist/{chunk-FSGICFXB.js → chunk-IDBPII6G.js} +17 -5
- package/dist/chunk-IDBPII6G.js.map +1 -0
- package/dist/{chunk-C3IQQKXL.js → chunk-IOTOOTII.js} +121 -1
- package/dist/{chunk-C3IQQKXL.js.map → chunk-IOTOOTII.js.map} +1 -1
- package/dist/{chunk-BCKEP5S6.js → chunk-JHJNNJLG.js} +2 -2
- package/dist/{chunk-TO7DJYLZ.js → chunk-L3S3QZXL.js} +178 -39
- package/dist/chunk-L3S3QZXL.js.map +1 -0
- package/dist/{chunk-WSJ7RO54.js → chunk-LAG6IP7Y.js} +23 -5
- package/dist/chunk-LAG6IP7Y.js.map +1 -0
- package/dist/{chunk-WJMPCVOR.js → chunk-LAJ3FF54.js} +12 -4
- package/dist/chunk-LAJ3FF54.js.map +1 -0
- package/dist/{chunk-2ZAZ7ETT.js → chunk-LDHRIQUW.js} +32 -18
- package/dist/chunk-LDHRIQUW.js.map +1 -0
- package/dist/{chunk-6R56OKJZ.js → chunk-LF2TJN4X.js} +2 -2
- package/dist/chunk-LF2TJN4X.js.map +1 -0
- package/dist/{chunk-ZQV7UQNE.js → chunk-LUF52EIY.js} +2 -2
- package/dist/{chunk-5UINJNAJ.js → chunk-LZMAG73U.js} +34 -21
- package/dist/chunk-LZMAG73U.js.map +1 -0
- package/dist/{chunk-VSGMCCGQ.js → chunk-MMSMZC7S.js} +2 -2
- package/dist/chunk-MVKNU4QD.js +37 -0
- package/dist/chunk-MVKNU4QD.js.map +1 -0
- package/dist/{chunk-XPJQXSC4.js → chunk-P7323YVW.js} +27 -5
- package/dist/chunk-P7323YVW.js.map +1 -0
- package/dist/{chunk-KJRVMMM3.js → chunk-PARKXDXI.js} +36 -5
- package/dist/chunk-PARKXDXI.js.map +1 -0
- package/dist/{chunk-4763KQ6O.js → chunk-PXRN2MER.js} +2 -2
- package/dist/{chunk-WMBLR5VA.js → chunk-PZZ6HM5T.js} +3 -3
- package/dist/{chunk-TR3HFH6J.js → chunk-QFSL2L2B.js} +67 -10
- package/dist/chunk-QFSL2L2B.js.map +1 -0
- package/dist/chunk-RQJFGK3A.js +51 -0
- package/dist/chunk-RQJFGK3A.js.map +1 -0
- package/dist/{chunk-3Z6X3BHU.js → chunk-TC4GANMX.js} +53 -150
- package/dist/chunk-TC4GANMX.js.map +1 -0
- package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
- package/dist/chunk-TEMQPSCM.js.map +1 -0
- package/dist/chunk-TJLKWQOX.js +52 -0
- package/dist/chunk-TJLKWQOX.js.map +1 -0
- package/dist/{chunk-CJ2RMTT7.js → chunk-TV76A6GN.js} +2 -2
- package/dist/{chunk-KZRR4NFG.js → chunk-TVZ6QCOE.js} +3 -3
- package/dist/{chunk-BVEDKMNG.js → chunk-U2ZI6G7H.js} +29 -11
- package/dist/chunk-U2ZI6G7H.js.map +1 -0
- package/dist/chunk-UPE6D4AT.js +69 -0
- package/dist/chunk-UPE6D4AT.js.map +1 -0
- package/dist/{chunk-R5IMFAFR.js → chunk-VMGHQUTV.js} +7 -16
- package/dist/chunk-VMGHQUTV.js.map +1 -0
- package/dist/chunk-VQMAAQ5M.js +53 -0
- package/dist/chunk-VQMAAQ5M.js.map +1 -0
- package/dist/{chunk-AVEQUR44.js → chunk-WMF5S2MB.js} +60 -17
- package/dist/chunk-WMF5S2MB.js.map +1 -0
- 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-XN37VTDB.js → chunk-X56LAKPR.js} +3 -3
- package/dist/chunk-X56LAKPR.js.map +1 -0
- 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-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
- package/dist/chunk-YJBSB4LP.js.map +1 -0
- package/dist/{chunk-BI2VI6EM.js → chunk-YXWBM26P.js} +2 -2
- package/dist/{chunk-QF6IQQFP.js → chunk-Z7FDJ35L.js} +18 -12
- package/dist/chunk-Z7FDJ35L.js.map +1 -0
- package/dist/cli/bin.js +111 -51
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +33 -3
- package/dist/cli/commands/audit.js +28 -25
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-DGTO65WB.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-KLWA53N5.js} +6 -6
- package/dist/{db-XKZYDCKM.js → db-II5WAK4D.js} +45 -20
- package/dist/db-II5WAK4D.js.map +1 -0
- package/dist/deadletter-G4M3HSVM.js +17 -0
- package/dist/{digest-7UBGUBRK.js → digest-CWKK2DMR.js} +56 -32
- package/dist/digest-CWKK2DMR.js.map +1 -0
- package/dist/{digest-collectors-DJFN5ZDK.js → digest-collectors-CYTN2EUX.js} +17 -12
- 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-2QZHFVQ3.js +70 -0
- package/dist/ensure-site-2QZHFVQ3.js.map +1 -0
- package/dist/ensure-site-JEKV2ODE.js +105 -0
- package/dist/ensure-site-JEKV2ODE.js.map +1 -0
- package/dist/{ensure-site-OAAE5YE2.js → ensure-site-OO4UPWNQ.js} +49 -44
- package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
- package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
- package/dist/{fleet-state-UEVZ3X3J.js → fleet-state-RJROYXKO.js} +19 -7
- package/dist/{forms-notify-target-UC6PF3AE.js → forms-notify-target-X6UEOI2M.js} +19 -15
- package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
- package/dist/{freeze-A6MUCZU4.js → freeze-GUQ43EAU.js} +2 -2
- package/dist/{github-signals-TX5FPYP6.js → github-signals-QIRTHHET.js} +22 -17
- package/dist/github-signals-QIRTHHET.js.map +1 -0
- package/dist/{header-image-PSPT7CUM.js → header-image-H7ZJAV26.js} +15 -18
- package/dist/header-image-H7ZJAV26.js.map +1 -0
- package/dist/{health-endpoint-KVCJYWET.js → health-endpoint-BJ6BKRHF.js} +8 -7
- package/dist/{health-endpoint-KVCJYWET.js.map → health-endpoint-BJ6BKRHF.js.map} +1 -1
- package/dist/{health-mirror-XJAKA3EY.js → health-mirror-KU3G7V2Y.js} +10 -10
- package/dist/{import-airtable-TAKSN4NV.js → import-airtable-UBF5GYAL.js} +3 -3
- package/dist/index.d.ts +158 -419
- package/dist/index.js +91 -46
- package/dist/index.js.map +1 -1
- package/dist/{init-FFE4FCO2.js → init-GOWDA5QS.js} +19 -18
- package/dist/{init-FFE4FCO2.js.map → init-GOWDA5QS.js.map} +1 -1
- package/dist/{launch-UD36AHUN.js → launch-7N3X2MXB.js} +229 -79
- package/dist/launch-7N3X2MXB.js.map +1 -0
- package/dist/{lighthouse-VHI77PFB.js → lighthouse-52WSPZ5D.js} +2 -2
- package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
- package/dist/match-harness-GBCDSVPS.js +417 -0
- package/dist/match-harness-GBCDSVPS.js.map +1 -0
- package/dist/migrate-XZ7U5DFC.js +7 -0
- package/dist/{notify-IISTHNBR.js → notify-K3WVH3DM.js} +4 -4
- package/dist/{onboard-TM2KESGF.js → onboard-JQQIMQW7.js} +6 -6
- package/dist/{orchestrate-HYVQPRCE.js → orchestrate-L2A5BZFY.js} +13 -10
- package/dist/parity-2JGVUCZD.js +14 -0
- package/dist/{pipeline-YROAY6DT.js → pipeline-UUPXOS2J.js} +3 -3
- package/dist/{preflight-RYIP54NY.js → preflight-O7Q6FWHU.js} +35 -11
- package/dist/preflight-O7Q6FWHU.js.map +1 -0
- package/dist/{prismic-ci-6GKKZGB5.js → prismic-ci-NAPWOXXD.js} +39 -9
- package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
- package/dist/{prismic-models-P6FWWVRS.js → prismic-models-LL6ORTQ2.js} +20 -17
- package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
- package/dist/prospect/types.d.ts +72 -0
- package/dist/prospect/types.js +1 -1
- package/dist/{prospect-audit-PRHU53HS.js → prospect-audit-FMPGQIRQ.js} +16 -7
- package/dist/prospect-audit-FMPGQIRQ.js.map +1 -0
- package/dist/{prospect-audits-R3LWF6U7.js → prospect-audits-IZVQY3OQ.js} +4 -2
- package/dist/protection-audit-YXJKFU6O.js +413 -0
- package/dist/protection-audit-YXJKFU6O.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-75JTED2Z.js} +6 -4
- package/dist/{renovate-dispatch-U5Q52SKT.js → renovate-dispatch-2IHAZDVA.js} +14 -9
- package/dist/{renovate-dispatch-U5Q52SKT.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
- package/dist/{replay-PCSQOGCZ.js → replay-4FVFOMHH.js} +10 -23
- package/dist/replay-4FVFOMHH.js.map +1 -0
- package/dist/{report-LQS4ECTM.js → report-IAHXN5CW.js} +103 -57
- package/dist/report-IAHXN5CW.js.map +1 -0
- package/dist/{report-mirror-HDTOJE64.js → report-mirror-4QM57LMS.js} +34 -12
- package/dist/report-mirror-4QM57LMS.js.map +1 -0
- package/dist/roster-GDNSIRAK.js +19 -0
- package/dist/roster-GDNSIRAK.js.map +1 -0
- 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-ZUWPGYUP.js} +8 -7
- package/dist/{self-updating-MWXIZLL7.js.map → self-updating-ZUWPGYUP.js.map} +1 -1
- package/dist/{selftest-VXTHRPRN.js → selftest-G4W3LIBQ.js} +21 -17
- package/dist/{selftest-VXTHRPRN.js.map → selftest-G4W3LIBQ.js.map} +1 -1
- package/dist/site-create-ZU3ACBTO.js +45 -0
- package/dist/site-create-ZU3ACBTO.js.map +1 -0
- 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-WR7ACXOV.js} +26 -12
- package/dist/site-mirror-WR7ACXOV.js.map +1 -0
- package/dist/{smoke-suite-7DGITCTZ.js → smoke-suite-WAUPI6EJ.js} +8 -7
- package/dist/{smoke-suite-7DGITCTZ.js.map → smoke-suite-WAUPI6EJ.js.map} +1 -1
- package/dist/{submissions-OVZPVBYG.js → submissions-2SHCFOKD.js} +12 -7
- package/dist/submissions-2SHCFOKD.js.map +1 -0
- 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-VOLOKKFI.js} +6 -6
- package/dist/{sync-K3PKHVLH.js → sync-F4USL4K5.js} +6 -6
- package/dist/{sync-configs-RRIHNLIJ.js → sync-configs-OXSWKVZ2.js} +6 -6
- package/dist/turso-KQKBBQCK.js +24 -0
- package/dist/turso-KQKBBQCK.js.map +1 -0
- package/dist/{upgrade-VTD6V4HS.js → upgrade-EVAL3JTO.js} +6 -6
- package/dist/util/git.d.ts +49 -10
- package/dist/util/git.js +1 -1
- package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
- package/dist/{websites-TB4TCAWM.js → websites-Y6FN2XFD.js} +27 -20
- package/dist/{write-audits-to-airtable-53EVXKMU.js → write-audits-to-airtable-TI3JMWSY.js} +7 -5
- package/package.json +3 -2
- package/dist/airtable-JGCPS7JH.js +0 -9
- package/dist/announce-CLQYX5I4.js.map +0 -1
- package/dist/attachments-NLZIJ5NU.js +0 -9
- package/dist/chunk-2ZAZ7ETT.js.map +0 -1
- package/dist/chunk-3Z6X3BHU.js.map +0 -1
- package/dist/chunk-5EEMTJMT.js.map +0 -1
- package/dist/chunk-5GUJ36TL.js +0 -53
- package/dist/chunk-5GUJ36TL.js.map +0 -1
- package/dist/chunk-5UINJNAJ.js.map +0 -1
- package/dist/chunk-6R56OKJZ.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-F3JWOPTG.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-IY6256GK.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-R5IMFAFR.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-XN37VTDB.js.map +0 -1
- package/dist/chunk-XPJQXSC4.js.map +0 -1
- package/dist/chunk-YFAXOD7Z.js +0 -42
- package/dist/chunk-YFAXOD7Z.js.map +0 -1
- package/dist/chunk-YLYXW5PH.js.map +0 -1
- package/dist/chunk-Z2K4JPQ3.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/fleet-events-cF2Om88w.d.ts +0 -24
- package/dist/forms-notify-target-UC6PF3AE.js.map +0 -1
- package/dist/github-signals-TX5FPYP6.js.map +0 -1
- package/dist/header-image-PSPT7CUM.js.map +0 -1
- package/dist/launch-UD36AHUN.js.map +0 -1
- package/dist/lighthouse-airtable-YMH4UA7R.js +0 -12
- package/dist/match-harness-QRG6XZAR.js +0 -70
- package/dist/match-harness-QRG6XZAR.js.map +0 -1
- package/dist/migrate-Y7R4CESL.js +0 -7
- package/dist/parity-CAJ477PS.js +0 -14
- package/dist/preflight-RYIP54NY.js.map +0 -1
- package/dist/prismic-ci-6GKKZGB5.js.map +0 -1
- package/dist/prismic-models-P6FWWVRS.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/report-mirror-HDTOJE64.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/submissions-OVZPVBYG.js.map +0 -1
- /package/dist/{airtable-JGCPS7JH.js.map → attachments-JFGFQRXQ.js.map} +0 -0
- /package/dist/{blux-4KNXUP2K.js.map → blux-NXQLMS4W.js.map} +0 -0
- /package/dist/{bump-deps-C5G2EFBB.js.map → bump-deps-775VLDYB.js.map} +0 -0
- /package/dist/{chunk-FUWFLAWQ.js.map → chunk-3RLDX3Q2.js.map} +0 -0
- /package/dist/{chunk-S7RYFUIK.js.map → chunk-6PGXKN6F.js.map} +0 -0
- /package/dist/{chunk-HTABUCF4.js.map → chunk-6THEZQLA.js.map} +0 -0
- /package/dist/{chunk-2LYOAB2E.js.map → chunk-HSMN35LU.js.map} +0 -0
- /package/dist/{chunk-BCKEP5S6.js.map → chunk-JHJNNJLG.js.map} +0 -0
- /package/dist/{chunk-ZQV7UQNE.js.map → chunk-LUF52EIY.js.map} +0 -0
- /package/dist/{chunk-VSGMCCGQ.js.map → chunk-MMSMZC7S.js.map} +0 -0
- /package/dist/{chunk-4763KQ6O.js.map → chunk-PXRN2MER.js.map} +0 -0
- /package/dist/{chunk-WMBLR5VA.js.map → chunk-PZZ6HM5T.js.map} +0 -0
- /package/dist/{chunk-CJ2RMTT7.js.map → chunk-TV76A6GN.js.map} +0 -0
- /package/dist/{chunk-KZRR4NFG.js.map → chunk-TVZ6QCOE.js.map} +0 -0
- /package/dist/{chunk-BI2VI6EM.js.map → chunk-YXWBM26P.js.map} +0 -0
- /package/dist/{attachments-NLZIJ5NU.js.map → client-DGTO65WB.js.map} +0 -0
- /package/dist/{convert-to-pnpm-YTFREUJK.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
- /package/dist/{client-G2WGWPCA.js.map → deadletter-G4M3HSVM.js.map} +0 -0
- /package/dist/{digest-collectors-DJFN5ZDK.js.map → digest-collectors-CYTN2EUX.js.map} +0 -0
- /package/dist/{fleet-state-UEVZ3X3J.js.map → fleet-state-RJROYXKO.js.map} +0 -0
- /package/dist/{freeze-A6MUCZU4.js.map → freeze-GUQ43EAU.js.map} +0 -0
- /package/dist/{health-mirror-XJAKA3EY.js.map → health-mirror-KU3G7V2Y.js.map} +0 -0
- /package/dist/{import-airtable-TAKSN4NV.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
- /package/dist/{lighthouse-VHI77PFB.js.map → lighthouse-52WSPZ5D.js.map} +0 -0
- /package/dist/{lighthouse-airtable-YMH4UA7R.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
- /package/dist/{migrate-Y7R4CESL.js.map → migrate-XZ7U5DFC.js.map} +0 -0
- /package/dist/{notify-IISTHNBR.js.map → notify-K3WVH3DM.js.map} +0 -0
- /package/dist/{onboard-TM2KESGF.js.map → onboard-JQQIMQW7.js.map} +0 -0
- /package/dist/{orchestrate-HYVQPRCE.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
- /package/dist/{parity-CAJ477PS.js.map → parity-2JGVUCZD.js.map} +0 -0
- /package/dist/{pipeline-YROAY6DT.js.map → pipeline-UUPXOS2J.js.map} +0 -0
- /package/dist/{prospect-audits-R3LWF6U7.js.map → prospect-audits-IZVQY3OQ.js.map} +0 -0
- /package/dist/{render-from-row-IFSBHWDH.js.map → render-from-row-75JTED2Z.js.map} +0 -0
- /package/dist/{svelte-codemods-WSV2FGNG.js.map → svelte-codemods-VOLOKKFI.js.map} +0 -0
- /package/dist/{sync-K3PKHVLH.js.map → sync-F4USL4K5.js.map} +0 -0
- /package/dist/{sync-configs-RRIHNLIJ.js.map → sync-configs-OXSWKVZ2.js.map} +0 -0
- /package/dist/{upgrade-VTD6V4HS.js.map → upgrade-EVAL3JTO.js.map} +0 -0
- /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
- /package/dist/{websites-TB4TCAWM.js.map → websites-Y6FN2XFD.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-53EVXKMU.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ Installs the matching harness used to rebuild a site against a live reference (a
|
|
|
158
158
|
```bash
|
|
159
159
|
reddoor-maint match-harness --ref https://client.webflow.io
|
|
160
160
|
reddoor-maint match-harness acme --ref https://acme.webflow.io --cand http://localhost:4173
|
|
161
|
-
reddoor-maint match-harness --fleet
|
|
161
|
+
reddoor-maint match-harness --fleet turso --ref https://client.webflow.io --matrix 1440,834,390
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
`--ref` is **required**. The harness gates against a live reference; with no reference it can only ever report an absence of errors, so the recipe refuses instead of installing something that goes green on nothing. `--cand` defaults to `http://localhost:5173` and `--matrix` to `1440,834,390`.
|
|
@@ -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
|
|
@@ -385,10 +419,10 @@ Nothing is written and nothing is sent.
|
|
|
385
419
|
0. **Prereq: refresh Lighthouse scores on each Websites row.** From each site's checkout:
|
|
386
420
|
|
|
387
421
|
```bash
|
|
388
|
-
reddoor-maint audit lighthouse --write-
|
|
422
|
+
reddoor-maint audit lighthouse --write-back
|
|
389
423
|
```
|
|
390
424
|
|
|
391
|
-
This runs Lighthouse and writes the 4 scores directly to the matching Websites row (slug auto-derived from `package.json#name`; pass `--write-
|
|
425
|
+
This runs Lighthouse and writes the 4 scores directly to the matching Websites row (slug auto-derived from `package.json#name`; pass `--write-back=<slug>` to override), along with a `Last lighthouse audit at` timestamp. The report orchestrator copies these into the new Reports row — drafting a report for a site missing scores fails with a clear error.
|
|
392
426
|
|
|
393
427
|
1. **Draft overdue reports**
|
|
394
428
|
|
|
@@ -444,12 +478,12 @@ 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
|
-
| `
|
|
450
|
-
| `GH_TOKEN` | GitHub token for the dashboard's request-path REST calls (fleet-refresh dispatch + GitHub-sourced signals) |
|
|
484
|
+
| `GH_TOKEN` | GitHub token for the dashboard's request-path REST calls, with `actions:write` — the "Trigger Renovate" button, the fleet-refresh and prospect-audit dispatches (each answers 503 `not-configured` without it), plus GitHub-sourced signals. The only name these functions read: `RENOVATE_TOKEN` was retired with the operator PAT |
|
|
451
485
|
|
|
452
|
-
> The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `
|
|
486
|
+
> The first seven vars cover the webhook + forms ingest (`TURNSTILE_SECRET_KEY` optional); `PROSPECT_EDIT_TOKEN` is needed only to SAVE edits to a prospect report (serving one needs no secret beyond its URL token); `DASHBOARD_PASSWORD` / `DASHBOARD_BASE_URL` / `GH_TOKEN` are additionally required by the operator **dashboard** this same deploy serves (cockpit at `/`, per-site at `/s/:slug`).
|
|
453
487
|
|
|
454
488
|
**3. Trigger a deploy** (Deploys → Trigger deploy → Deploy site). When it goes green, curl the health endpoint:
|
|
455
489
|
|
|
@@ -472,7 +506,7 @@ Expected:
|
|
|
472
506
|
}
|
|
473
507
|
```
|
|
474
508
|
|
|
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.
|
|
509
|
+
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
510
|
|
|
477
511
|
**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
512
|
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scoresFromRow
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PZZ6HM5T.js";
|
|
4
4
|
import {
|
|
5
5
|
fetchGaUsers,
|
|
6
6
|
fetchSearch,
|
|
7
7
|
readGaConfig,
|
|
8
8
|
refreshHeaderImage
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DRNZVORX.js";
|
|
10
10
|
import {
|
|
11
|
+
createReportDraft,
|
|
12
|
+
findReportForPeriod,
|
|
11
13
|
queueDraft
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-UPE6D4AT.js";
|
|
13
15
|
import {
|
|
14
|
-
createDraft,
|
|
15
|
-
findReportByPeriod,
|
|
16
16
|
updateReportScores
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-TEMQPSCM.js";
|
|
18
|
+
import "./chunk-ASLDCWBF.js";
|
|
18
19
|
import "./chunk-VAD43MOR.js";
|
|
19
20
|
import {
|
|
20
21
|
uploadAttachment
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YJBSB4LP.js";
|
|
22
23
|
import {
|
|
23
24
|
openBase,
|
|
24
25
|
readAirtableConfig
|
|
@@ -32,23 +33,26 @@ import {
|
|
|
32
33
|
resolveCopy
|
|
33
34
|
} from "./chunk-KJGKZLCT.js";
|
|
34
35
|
import "./chunk-BGTYPVLT.js";
|
|
35
|
-
import "./chunk-
|
|
36
|
+
import "./chunk-LZMAG73U.js";
|
|
36
37
|
import {
|
|
37
|
-
listWebsites,
|
|
38
|
-
siteSlug,
|
|
39
38
|
updateAnalyticsHealth
|
|
40
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-TC4GANMX.js";
|
|
40
|
+
import {
|
|
41
|
+
siteSlug
|
|
42
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
43
|
+
import "./chunk-RQJFGK3A.js";
|
|
41
44
|
import "./chunk-RARREDQE.js";
|
|
42
45
|
import "./chunk-6VAL7XJT.js";
|
|
43
46
|
|
|
44
47
|
// src/recipes/announce.ts
|
|
45
48
|
var GA_WINDOW_DAYS = 30;
|
|
46
49
|
async function announce(deps) {
|
|
47
|
-
const base = deps
|
|
48
|
-
const now = deps
|
|
49
|
-
const
|
|
50
|
+
const base = deps.base ?? openBase(readAirtableConfig());
|
|
51
|
+
const now = deps.now ?? /* @__PURE__ */ new Date();
|
|
52
|
+
const writer = deps.reportMirror;
|
|
53
|
+
const websites = await deps.roster();
|
|
50
54
|
let targets = websites.filter((w) => w.status === "maintained");
|
|
51
|
-
if (deps
|
|
55
|
+
if (deps.site) {
|
|
52
56
|
const wanted = siteSlug(deps.site);
|
|
53
57
|
targets = targets.filter((w) => siteSlug(w.name) === wanted);
|
|
54
58
|
}
|
|
@@ -61,8 +65,8 @@ async function announce(deps) {
|
|
|
61
65
|
results.push({ site: w.name, status: "skipped-no-scores" });
|
|
62
66
|
continue;
|
|
63
67
|
}
|
|
64
|
-
if (deps
|
|
65
|
-
await refreshHeaderImage(w, deps
|
|
68
|
+
if (deps.refreshHeader !== false) {
|
|
69
|
+
await refreshHeaderImage(w, deps.refreshHeader ?? {});
|
|
66
70
|
}
|
|
67
71
|
const periodEnd = now;
|
|
68
72
|
const periodStart = new Date(now.getTime() - GA_WINDOW_DAYS * 24 * 60 * 60 * 1e3);
|
|
@@ -82,17 +86,17 @@ async function announce(deps) {
|
|
|
82
86
|
w.id,
|
|
83
87
|
gaResult.softFailed || searchResult.softFailed ? now.toISOString() : null
|
|
84
88
|
);
|
|
85
|
-
await deps
|
|
89
|
+
await deps.siteMirror?.health(w.id, fields);
|
|
86
90
|
} catch (e) {
|
|
87
91
|
console.warn(`\u26A0 analytics-health write skipped for ${w.name}: ${e.message}`);
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
let report;
|
|
91
95
|
let statusKind;
|
|
92
|
-
const existing = await
|
|
96
|
+
const existing = await findReportForPeriod(writer, w.id, "Announcement", period);
|
|
93
97
|
if (existing) {
|
|
94
98
|
await updateReportScores(base, existing.id, scores, now, enrichment);
|
|
95
|
-
await
|
|
99
|
+
await writer.patch(existing.id, {
|
|
96
100
|
lighthouse_performance: scores.performance,
|
|
97
101
|
lighthouse_accessibility: scores.accessibility,
|
|
98
102
|
lighthouse_best_practices: scores.bestPractices,
|
|
@@ -106,11 +110,9 @@ async function announce(deps) {
|
|
|
106
110
|
report = existing;
|
|
107
111
|
statusKind = "reused";
|
|
108
112
|
} else {
|
|
109
|
-
report = await
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
deps?.reportMirror?.created
|
|
113
|
-
);
|
|
113
|
+
report = await createReportDraft(draftInputFor(w, scores, now, period, enrichment), {
|
|
114
|
+
create: writer.create
|
|
115
|
+
});
|
|
114
116
|
statusKind = "drafted";
|
|
115
117
|
}
|
|
116
118
|
const slug = siteSlug(w.name);
|
|
@@ -141,7 +143,7 @@ async function announce(deps) {
|
|
|
141
143
|
`${slug}-${now.toISOString().slice(0, 10)}.html`,
|
|
142
144
|
"text/html"
|
|
143
145
|
);
|
|
144
|
-
await
|
|
146
|
+
await writer.body(report.id, html);
|
|
145
147
|
} catch (uploadErr) {
|
|
146
148
|
console.warn(
|
|
147
149
|
`\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
@@ -150,7 +152,7 @@ async function announce(deps) {
|
|
|
150
152
|
const queue = await queueDraft(
|
|
151
153
|
base,
|
|
152
154
|
{ id: report.id, siteId: w.id, reportType: "Announcement" },
|
|
153
|
-
|
|
155
|
+
writer
|
|
154
156
|
);
|
|
155
157
|
const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
|
|
156
158
|
results.push({
|
|
@@ -203,11 +205,17 @@ function formatAnnounceResult(result) {
|
|
|
203
205
|
return result.results.map(formatSiteResult).join("\n");
|
|
204
206
|
}
|
|
205
207
|
async function runAnnounceCommand(site, _opts) {
|
|
206
|
-
const { makeReportMirror } = await import("./report-mirror-
|
|
207
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
208
|
+
const { makeReportMirror } = await import("./report-mirror-4QM57LMS.js");
|
|
209
|
+
const { makeSiteMirror } = await import("./site-mirror-WR7ACXOV.js");
|
|
208
210
|
const reportMirror = await makeReportMirror();
|
|
209
211
|
const siteMirror = await makeSiteMirror();
|
|
210
|
-
const
|
|
212
|
+
const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
|
|
213
|
+
const result = await announce({
|
|
214
|
+
...site ? { site } : {},
|
|
215
|
+
roster: () => readFleetRoster(),
|
|
216
|
+
reportMirror,
|
|
217
|
+
siteMirror
|
|
218
|
+
});
|
|
211
219
|
const hadError = result.results.some((r) => r.status === "error");
|
|
212
220
|
return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };
|
|
213
221
|
}
|
|
@@ -215,4 +223,4 @@ export {
|
|
|
215
223
|
formatAnnounceResult,
|
|
216
224
|
runAnnounceCommand
|
|
217
225
|
};
|
|
218
|
-
//# sourceMappingURL=announce-
|
|
226
|
+
//# sourceMappingURL=announce-5ZD2SBGD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/recipes/announce.ts","../src/cli/commands/announce.ts"],"sourcesContent":["import { openBase, readAirtableConfig } from \"../reports/airtable/client.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { siteSlug, updateAnalyticsHealth } from \"../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { readGaConfig } from \"../reports/ga/config.js\";\nimport { updateReportScores, type ReportEnrichment } from \"../reports/airtable/reports.js\";\nimport { createReportDraft, findReportForPeriod } from \"../reports/create-report.js\";\nimport type { DraftInput } from \"../reports/draft-fields.js\";\nimport type { ReportMirror } from \"../reports/report-mirror.js\";\nimport type { SiteMirror } from \"../db/site-mirror.js\";\nimport { queueDraft } from \"../reports/queue.js\";\nimport { uploadAttachment } from \"../reports/airtable/attachments.js\";\nimport { renderReportHtml } from \"../reports/render.js\";\nimport { resolveCopy } from \"../reports/copy.js\";\nimport { fetchGaUsers, fetchSearch, refreshHeaderImage } from \"../reports/draft.js\";\nimport type { RefreshHeaderDeps } from \"../reports/draft.js\";\nimport { announcementSiteExtras } from \"../reports/announcement-email/template.js\";\nimport type { LighthouseScores } from \"../reports/types.js\";\nimport { defaultReportSubject } from \"../reports/subject.js\";\nimport { scoresFromRow } from \"../reports/report-data.js\";\n\nexport type AnnounceSiteResult =\n | {\n site: string;\n status: \"drafted\" | \"reused\";\n reportId: string;\n recipientMissing: boolean;\n /** False when a higher-or-equal-tier report was already queued (single-queue rule). */\n queued: boolean;\n }\n | { site: string; status: \"skipped-no-scores\" }\n | { site: string; status: \"error\"; message: string };\n\nexport type AnnounceResult = { results: AnnounceSiteResult[] };\n\n/** The traffic/search lookback window (days) the announcement reports on. The trend compares it\n * against the equal-length prior window, and the email labels it \"vs the previous N days\". */\nconst GA_WINDOW_DAYS = 30;\n\nexport type AnnounceDeps = {\n /** Airtable handle. Defaults to opening the live base from credentials. */\n base?: AirtableBase;\n /** When set, restrict to the single site whose slug matches. Default: all maintenance sites. */\n site?: string;\n /** Single timestamp driving the period key, render, draft, and preview filename. */\n now?: Date;\n /**\n * Header-image refresh, matching `draftReportForSite`'s opt-out shape: UNSET means\n * refresh (the production default), `false` skips it so unit suites don't launch a\n * browser. Announce previously never refreshed at all, so a site whose stored header\n * predated a plate change kept announcing with the old one until its next\n * Maintenance/Testing draft happened to heal it.\n */\n refreshHeader?: RefreshHeaderDeps | false;\n /** Every site in the fleet, read from TURSO (#646 step 4) — the Airtable\n * roster this replaces could not see a `site_<ULID>` site, so a site created\n * since step 3 was never announced. Required, not defaulted: every unit test\n * calls `announce` with a fake base, and a default would open a real libSQL\n * handle from inside the suite. The CLI composition root wires\n * `readFleetRoster`. */\n roster: () => Promise<WebsiteRow[]>;\n /** #539 Phase 5, and since #646 step 4 the store that MINTS and holds the\n * report row: the created row (or a reused row's refreshed scores), the\n * rendered body, and the queue flag. Required for the same reason `roster`\n * is — a default would open a real libSQL handle from inside the suite. */\n reportMirror: ReportMirror;\n /** #539 Phase 5: the Websites-row twin — announce stamps `Analytics soft-fail\n * at` on the SITE row, a different Turso table from the report. */\n siteMirror?: SiteMirror;\n};\n\n/**\n * Draft the monthly-report ANNOUNCEMENT email for every `maintenance` site (or one,\n * via `deps.site`). Airtable-driven and fleet-wide: unlike `launch`, it runs no audits\n * and takes no `Site`/inventory object — it reads the Lighthouse scores already stored\n * on each Websites row. DRAFTS ONLY; the M3 approve loop sends.\n *\n * One bad site must never abort the run: each site is wrapped in its own try/catch that\n * records an `error` result and continues.\n */\nexport async function announce(deps: AnnounceDeps): Promise<AnnounceResult> {\n const base = deps.base ?? openBase(readAirtableConfig());\n const now = deps.now ?? new Date();\n const writer = deps.reportMirror;\n\n const websites = await deps.roster();\n let targets = websites.filter((w) => w.status === \"maintained\");\n if (deps.site) {\n const wanted = siteSlug(deps.site);\n targets = targets.filter((w) => siteSlug(w.name) === wanted);\n }\n\n const period = now.toISOString().slice(0, 7);\n const results: AnnounceSiteResult[] = [];\n\n for (const w of targets) {\n try {\n const scores = scoresFromRow(w);\n if (scores === null) {\n results.push({ site: w.name, status: \"skipped-no-scores\" });\n continue;\n }\n\n // Refresh BEFORE the render so a queued announcement always has a current header\n // stored — the send re-reads the attachment, so a stale one here reaches the\n // client. Best-effort by construction: refreshHeaderImage returns false and never\n // throws, so a capture failure leaves the stored image and the draft continues.\n if (deps.refreshHeader !== false) {\n await refreshHeaderImage(w, deps.refreshHeader ?? {});\n }\n\n // Traffic + search snapshot over a ~30-day window ending now (fetchPeriodUsers derives\n // the equal-length previous window for the trend). Reuses the report pipeline's\n // soft-failing enrichment: GA/search unconfigured or an API error leaves the numbers\n // null and the email simply omits the traffic section — it never blocks the draft.\n const periodEnd = now;\n const periodStart = new Date(now.getTime() - GA_WINDOW_DAYS * 24 * 60 * 60 * 1000);\n const gaResult = await fetchGaUsers(w, periodStart, periodEnd);\n const searchResult = await fetchSearch(w, periodStart, periodEnd);\n const gaUsers = gaResult.value;\n const search = searchResult.value;\n const enrichment: ReportEnrichment = {\n ...(gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {}),\n ...(search ? { searchFoundPage1: search.foundOnPage1 } : {}),\n ...(search?.foundOnPage1 && search.position !== null\n ? { searchPosition: search.position }\n : {}),\n };\n\n // Record this site's GA/Search enrichment health for the per-site analytics-failure\n // signal (cockpit/digest) — exactly as the `--due` draft path does. Without this, an\n // announcement-time GA outage hides the traffic block with NO operator signal (it reads\n // identically to \"site has no GA configured\"). Set the timestamp on a soft-fail, clear\n // it on a clean enrichment so the signal self-heals. Best-effort: the column is\n // operator-added, so until it exists the write throws — which must not break the draft.\n if (readGaConfig() !== null && Boolean(w.ga4PropertyId || w.searchQuery)) {\n try {\n const fields = await updateAnalyticsHealth(\n base,\n w.id,\n gaResult.softFailed || searchResult.softFailed ? now.toISOString() : null,\n );\n // Mirror the EXACT FieldSet Airtable got — see draft.ts.\n await deps.siteMirror?.health(w.id, fields);\n } catch (e) {\n console.warn(`⚠ analytics-health write skipped for ${w.name}: ${(e as Error).message}`);\n }\n }\n\n // Dedupe: reuse an existing Announcement row for this (site, period) rather than\n // stacking a second draft. The reuse path refreshes the stored scores + traffic/search\n // (and Completed on) so the eventually-sent email — which reads the row — isn't stale.\n // The create path writes them via createDraft.\n let report;\n let statusKind: \"drafted\" | \"reused\";\n const existing = await findReportForPeriod(writer, w.id, \"Announcement\", period);\n if (existing) {\n await updateReportScores(base, existing.id, scores, now, enrichment);\n // Mirror the SAME refresh: the reuse path exists so the eventually-sent\n // email is not stale, and the console reads those numbers too.\n await writer.patch(existing.id, {\n lighthouse_performance: scores.performance,\n lighthouse_accessibility: scores.accessibility,\n lighthouse_best_practices: scores.bestPractices,\n lighthouse_seo: scores.seo,\n completed_on: now.toISOString().slice(0, 10),\n ...(enrichment.gaUsersCurrent !== undefined\n ? { ga_users_current: enrichment.gaUsersCurrent }\n : {}),\n ...(enrichment.gaUsersPrevious !== undefined\n ? { ga_users_previous: enrichment.gaUsersPrevious }\n : {}),\n ...(enrichment.searchFoundPage1 !== undefined\n ? { search_found_page1: enrichment.searchFoundPage1 ? 1 : 0 }\n : {}),\n ...(enrichment.searchPosition !== undefined\n ? { search_position: enrichment.searchPosition }\n : {}),\n });\n report = existing;\n statusKind = \"reused\";\n } else {\n // #646 step 4: minted and written in Turso (`report_<ULID>`). The\n // Airtable Reports row this replaces is skipped, not written — see\n // `createReportDraft`.\n report = await createReportDraft(draftInputFor(w, scores, now, period, enrichment), {\n create: writer.create,\n });\n statusKind = \"drafted\";\n }\n\n const slug = siteSlug(w.name);\n const { html } = await renderReportHtml({\n siteName: w.name,\n siteUrl: w.url,\n reportType: \"Announcement\",\n completedOn: now,\n lighthouse: scores,\n gaUsersCurrent: gaUsers?.current,\n gaUsersPrevious: gaUsers?.previous,\n gaPeriodDays: GA_WINDOW_DAYS,\n searchPosition: search?.foundOnPage1 ? (search.position ?? undefined) : undefined,\n lastTestedDate: null,\n commentary: null,\n copy: resolveCopy(w),\n headerImageCid: `${slug}-header`,\n // cadence (the client's go-forward pace, \"None\" omitted) + default-on improvement\n // callouts. Shared with the send re-render via announcementSiteExtras so the sent\n // email matches this reviewed preview.\n ...announcementSiteExtras(w),\n });\n\n // A preview-upload hiccup must NOT fail the site — log and continue.\n try {\n await uploadAttachment(\n report.id,\n \"Rendered HTML\",\n html,\n `${slug}-${now.toISOString().slice(0, 10)}.html`,\n \"text/html\",\n );\n // Store the same body in Turso — the console's preview route reads it\n // there, not from the Airtable attachment (whose URL expires).\n await writer.body(report.id, html);\n } catch (uploadErr) {\n console.warn(\n `⚠ Announcement preview upload skipped for ${w.name}: ${\n uploadErr instanceof Error ? uploadErr.message : String(uploadErr)\n }`,\n );\n }\n\n // Critical: NOT wrapped — without queueing, the draft never enters the approve queue,\n // so a failure here must surface as an error result for the site. queueDraft also\n // supersedes any lower-tier (Maintenance/Testing) drafts queued for this site, and\n // stands down if an equal-or-higher report is already queued (single-queue rule).\n const queue = await queueDraft(\n base,\n { id: report.id, siteId: w.id, reportType: \"Announcement\" },\n writer,\n );\n\n const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());\n results.push({\n site: w.name,\n status: statusKind,\n reportId: report.id,\n recipientMissing,\n queued: queue.queued,\n });\n } catch (err) {\n results.push({\n site: w.name,\n status: \"error\",\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return { results };\n}\n\n/** Build the Announcement `DraftInput`. Announcements have no period window and no prior\n * maintenance test, so periodStart/periodEnd/completedOn all collapse to `now` and\n * `lastTestedDate` is null. The subject override gives the email a purpose-built line. */\nfunction draftInputFor(\n w: WebsiteRow,\n scores: LighthouseScores,\n now: Date,\n period: string,\n enrichment: ReportEnrichment,\n): DraftInput {\n return {\n reportId: `${w.name} — Announcement — ${now.toISOString().slice(0, 10)}`,\n siteId: w.id,\n reportType: \"Announcement\",\n period,\n periodStart: now,\n periodEnd: now,\n completedOn: now,\n lighthouse: scores,\n lastTestedDate: null,\n subjectOverride: defaultReportSubject({\n name: w.name,\n url: w.url,\n type: \"Announcement\",\n date: now,\n }),\n ...enrichment,\n };\n}\n","import { announce, type AnnounceResult, type AnnounceSiteResult } from \"../../recipes/announce.js\";\n\nexport type AnnounceCommandOptions = {\n cwd?: string;\n};\n\nfunction formatSiteResult(r: AnnounceSiteResult): string {\n if (r.status === \"skipped-no-scores\") return `[${r.site}] skipped-no-scores`;\n if (r.status === \"error\") return `[${r.site}] error: ${r.message}`;\n const note = r.recipientMissing ? \" ⚠ recipient missing\" : \"\";\n return `[${r.site}] ${r.status}${note}`;\n}\n\nexport function formatAnnounceResult(result: AnnounceResult): string {\n if (result.results.length === 0) return \"No maintenance sites to announce.\";\n return result.results.map(formatSiteResult).join(\"\\n\");\n}\n\n/**\n * `announce [site]` — Airtable-driven and fleet-wide. Draft the monthly-report\n * announcement email for every `maintenance` site (or one, when `site` is given) into\n * the M3 approve queue. Never sends; the operator approves each draft and the next send\n * run delivers it. Reads the Lighthouse scores already stored on each Websites row —\n * no audits are run.\n */\nexport async function runAnnounceCommand(\n site: string | undefined,\n _opts: AnnounceCommandOptions,\n): Promise<{ output: string; code: number }> {\n // #539 Phase 5: the create-side Turso dual-write is wired HERE rather than\n // inside `announce`, so a unit suite calling the recipe with a fake base can\n // never open a real libSQL handle (and, with TURSO_* exported locally, write\n // into production).\n const { makeReportMirror } = await import(\"../../reports/report-mirror.js\");\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n const reportMirror = await makeReportMirror();\n const siteMirror = await makeSiteMirror();\n // #646 step 4: the fleet roster comes from Turso — an Airtable roster cannot\n // see a `site_<ULID>` site, so one created since step 3 was never announced.\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n const result = await announce({\n ...(site ? { site } : {}),\n roster: () => readFleetRoster(),\n reportMirror,\n siteMirror,\n });\n const hadError = result.results.some((r) => r.status === \"error\");\n return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
appendSkipNotice,
|
|
6
6
|
fleetWorkdir,
|
|
7
7
|
prepareFleetSites
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DYTYPKFN.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveSites
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FP6HOW6J.js";
|
|
12
12
|
import {
|
|
13
13
|
bumpDeps
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-TV76A6GN.js";
|
|
15
|
+
import "./chunk-LUF52EIY.js";
|
|
16
16
|
import "./chunk-Z3JXLULD.js";
|
|
17
17
|
import "./chunk-LBYOLBW7.js";
|
|
18
18
|
import "./chunk-RARREDQE.js";
|
|
19
19
|
import "./chunk-XXTZBPUY.js";
|
|
20
|
-
import "./chunk-
|
|
20
|
+
import "./chunk-WMF5S2MB.js";
|
|
21
21
|
|
|
22
22
|
// src/cli/commands/bump-deps.ts
|
|
23
23
|
import { resolve } from "path";
|
|
@@ -57,4 +57,4 @@ async function runBumpDepsCommand(site, opts) {
|
|
|
57
57
|
export {
|
|
58
58
|
runBumpDepsCommand
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=bump-deps-
|
|
60
|
+
//# sourceMappingURL=bump-deps-775VLDYB.js.map
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
withRecipe
|
|
3
|
-
} from "./chunk-ZQV7UQNE.js";
|
|
4
1
|
import {
|
|
5
2
|
ALL_TEMPLATES,
|
|
6
3
|
RENOVATE_ACTION_CONFIG,
|
|
@@ -8,10 +5,13 @@ import {
|
|
|
8
5
|
templatesByName,
|
|
9
6
|
withRenovatePinsFrom
|
|
10
7
|
} from "./chunk-SLFG7Y76.js";
|
|
8
|
+
import {
|
|
9
|
+
withRecipe
|
|
10
|
+
} from "./chunk-LUF52EIY.js";
|
|
11
11
|
import {
|
|
12
12
|
listTrackedFiles,
|
|
13
13
|
removeFromIndex
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WMF5S2MB.js";
|
|
15
15
|
|
|
16
16
|
// src/recipes/sync-configs.ts
|
|
17
17
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -229,4 +229,4 @@ export {
|
|
|
229
229
|
planTemplateDiffs,
|
|
230
230
|
syncConfigs
|
|
231
231
|
};
|
|
232
|
-
//# sourceMappingURL=chunk-
|
|
232
|
+
//# sourceMappingURL=chunk-3RLDX3Q2.js.map
|
|
@@ -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":"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","names":[]}
|