@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
|
@@ -109,7 +109,7 @@ async function runProspectAuditCommand(url, opts) {
|
|
|
109
109
|
"No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials."
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
-
const { runProspectAudit } = await import("./pipeline-
|
|
112
|
+
const { runProspectAudit } = await import("./pipeline-UUPXOS2J.js");
|
|
113
113
|
const onStage = (name, status, detail) => {
|
|
114
114
|
if (status === "start") console.error(`\u2026 ${name}`);
|
|
115
115
|
else if (status === "ok") console.error(`\u2713 ${name}`);
|
|
@@ -117,6 +117,9 @@ async function runProspectAuditCommand(url, opts) {
|
|
|
117
117
|
};
|
|
118
118
|
const business = opts.business?.trim();
|
|
119
119
|
const goal = opts.goal?.trim();
|
|
120
|
+
const splitList = (v) => (v ?? "").split(",").map((x) => x.trim()).filter((x) => x !== "");
|
|
121
|
+
const chosenTerms = splitList(opts.terms);
|
|
122
|
+
const chosenQuestions = splitList(opts.questions);
|
|
120
123
|
if (goal !== void 0 && goal !== "" && !isSiteGoal(goal)) {
|
|
121
124
|
return fail(`--goal must be one of: ${GOALS.join(", ")}`);
|
|
122
125
|
}
|
|
@@ -131,11 +134,13 @@ async function runProspectAuditCommand(url, opts) {
|
|
|
131
134
|
...opts.competitors ? {
|
|
132
135
|
competitors: opts.competitors.split(",").map((c) => c.trim()).filter(Boolean)
|
|
133
136
|
} : {},
|
|
134
|
-
...opts.probes === false ? { probes: false } : {}
|
|
137
|
+
...opts.probes === false ? { probes: false } : {},
|
|
138
|
+
...chosenTerms.length > 0 ? { terms: chosenTerms } : {},
|
|
139
|
+
...chosenQuestions.length > 0 ? { questions: chosenQuestions } : {}
|
|
135
140
|
},
|
|
136
141
|
{ ...opts.deps ?? {}, onStage }
|
|
137
142
|
);
|
|
138
|
-
const { renderProspectReport } = await import("./render-
|
|
143
|
+
const { renderProspectReport } = await import("./render-MHUUHPLN.js");
|
|
139
144
|
const html = renderProspectReport(result);
|
|
140
145
|
const warnings = [];
|
|
141
146
|
let file = null;
|
|
@@ -152,8 +157,8 @@ async function runProspectAuditCommand(url, opts) {
|
|
|
152
157
|
let auditId = null;
|
|
153
158
|
if (canPersist) {
|
|
154
159
|
try {
|
|
155
|
-
const { openDb, readDbConfig } = await import("./client-
|
|
156
|
-
const { createProspectAudit } = await import("./prospect-audits-
|
|
160
|
+
const { openDb, readDbConfig } = await import("./client-DGTO65WB.js");
|
|
161
|
+
const { createProspectAudit } = await import("./prospect-audits-IZVQY3OQ.js");
|
|
157
162
|
const db = await openDb(readDbConfig());
|
|
158
163
|
const created = await createProspectAudit(db, {
|
|
159
164
|
url: result.url,
|
|
@@ -163,7 +168,11 @@ async function runProspectAuditCommand(url, opts) {
|
|
|
163
168
|
// renaming a column needs its own migration for no benefit here.
|
|
164
169
|
business: result.businessName,
|
|
165
170
|
status: auditStatus(result),
|
|
166
|
-
resultJson: JSON.stringify(result)
|
|
171
|
+
resultJson: JSON.stringify(result),
|
|
172
|
+
// #676: on the ROW, not only in result_json — a re-run reuses them and
|
|
173
|
+
// the /audits listing marks them without reading the blob.
|
|
174
|
+
chosenTerms: chosenTerms.length > 0 ? chosenTerms : null,
|
|
175
|
+
chosenQuestions: chosenQuestions.length > 0 ? chosenQuestions : null
|
|
167
176
|
});
|
|
168
177
|
auditId = created.id;
|
|
169
178
|
token = created.token;
|
|
@@ -213,4 +222,4 @@ export {
|
|
|
213
222
|
GOALS,
|
|
214
223
|
runProspectAuditCommand
|
|
215
224
|
};
|
|
216
|
-
//# sourceMappingURL=prospect-audit-
|
|
225
|
+
//# sourceMappingURL=prospect-audit-FMPGQIRQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/prospect-audit.ts","../src/prospect/report-url.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { hostnameOf, isHttpUrl, isPrivateOrLoopbackHost } from \"../../util/url.js\";\nimport { reportUrl, reportPrintUrl } from \"../../prospect/report-url.js\";\nimport type { ProspectAuditStatus } from \"../../db/prospect-audits.js\";\nimport type { PipelineDeps, StageName } from \"../../prospect/pipeline.js\";\nimport type { ProspectAuditResult } from \"../../prospect/types.js\";\nimport type { SendAuditEmailResult } from \"../../prospect/email.js\";\nimport type { SiteGoal } from \"../../prospect/goals.js\";\n\n/** The operator-selectable goals, in the order the dispatch dropdown lists\n * them. `unknown` is deliberately absent: it is a finding the audit can reach\n * on its own, never something an operator would choose. */\nexport const GOALS = [\n \"book\",\n \"enquire\",\n \"call\",\n \"visit\",\n \"buy\",\n \"demo\",\n \"partner\",\n] as const satisfies readonly SiteGoal[];\n\nfunction isSiteGoal(value: string): value is SiteGoal {\n return (GOALS as readonly string[]).includes(value);\n}\n\nexport type ProspectAuditCliOptions = {\n business?: string;\n /**\n * The one action the site should produce from a visitor, chosen by the\n * operator instead of inferred from the site.\n *\n * Worth overriding because the operator knows things the site does not say.\n * A practice that wants online bookings but has never built one reads as\n * \"call\" from its own pages, and grading it as \"call\" would score it well for\n * doing the thing they are trying to stop doing.\n */\n goal?: string;\n /** Comma-separated competitor domains. */\n competitors?: string;\n /** cac sets this false for `--no-probes`. */\n probes?: boolean;\n out?: string;\n json?: boolean;\n /**\n * Email the internal sheet (scores, what wasn't measured and why, top\n * fixes, and the shareable link) to `PROSPECT_AUDIT_RECIPIENTS`, once the\n * report is rendered and the `--out`/Turso delivery attempts finish. A\n * missing/empty recipients var, or a Resend failure, is a warning — never a\n * crash — because by the time this runs the audit is already delivered (or\n * recovered) by the code above it.\n */\n email?: boolean;\n /**\n * #676. Search terms and buyer questions chosen by hand, comma-separated.\n *\n * Blank generates them, exactly as before — this is an override for a client\n * we already know, and for a comparison over time, where the terms have to\n * stay fixed between runs.\n */\n terms?: string;\n questions?: string;\n /** Test seam: injected pipeline deps. Never set from the CLI. */\n deps?: PipelineDeps;\n};\n\ntype RecoveryWrite = { htmlPath: string; jsonPath: string };\n\nfunction fail(message: string): { output: string; code: number } {\n return { output: message, code: 2 };\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction scoreLine(label: string, value: number | null): string {\n return `${label.padEnd(14)} ${value === null ? \"not measured\" : String(value).padStart(3)}`;\n}\n\n/** Item 3: the `status` column exists to answer, without deserializing the\n * whole result_json, whether this was a clean run or a degraded one. crawl\n * is excluded — it's the one fatal stage (see pipeline.ts), so by the time a\n * ProspectAuditResult exists it was always ok. Every other stage's\n * StageResult already collapses \"failed\" and \"deliberately skipped\"\n * (--no-probes, the checks→analyze cascade) into the same `ok: false` — both\n * mean the report has a \"not measured\" section, so both count as partial\n * here too. */\nfunction auditStatus(result: ProspectAuditResult): ProspectAuditStatus {\n const allStagesOk =\n result.checks.ok && result.lighthouse.ok && result.analyze.ok && result.probes.ok;\n return allStagesOk ? \"complete\" : \"partial\";\n}\n\n/** Filesystem-safe, never empty. Collapses anything outside [a-z0-9.-] to a\n * single dash so a hostname becomes a sane filename fragment. */\nfunction slugify(s: string): string {\n const slug = s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"prospect\";\n}\n\n/**\n * Last-resort write when NEITHER `--out` nor the database persist made it —\n * the one situation a paid, already-run audit must never end in silently. Lands\n * in `reports/`, mirroring header-image.ts's own convention for CLI-written\n * artifacts (mkdir -p, resolved against cwd), rather than a bare file dropped\n * into whatever directory the operator happened to be standing in when they\n * ran the command — often unrelated to this repo at all in fleet-tool usage.\n */\nasync function writeRecoveryFiles(\n result: ProspectAuditResult,\n html: string,\n): Promise<RecoveryWrite> {\n const dir = resolve(\"reports\");\n await mkdir(dir, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const base = `prospect-audit-${slugify(hostnameOf(result.url))}-${stamp}`;\n const htmlPath = resolve(dir, `${base}.html`);\n const jsonPath = resolve(dir, `${base}.json`);\n await writeFile(htmlPath, html, \"utf-8\");\n await writeFile(jsonPath, JSON.stringify(result, null, 2), \"utf-8\");\n return { htmlPath, jsonPath };\n}\n\nfunction summarize(\n result: ProspectAuditResult,\n link: string | null,\n file: string | null,\n warnings: string[],\n recovery: RecoveryWrite | null,\n email: SendAuditEmailResult | null,\n): string {\n const lines = [\n `Prospect audit — ${result.businessName ?? result.url}`,\n \"\",\n scoreLine(\"Findability\", result.scores.findability),\n scoreLine(\"Readability\", result.scores.readability),\n scoreLine(\"Answers\", result.scores.answers),\n // No AI Visibility line: the score was removed from every presentation\n // surface (web, print, email, here) — the measurement lives on as the\n // report's receipts section, not as a number that reads as ours to move.\n ];\n for (const [name, stage] of [\n [\"checks\", result.checks],\n [\"lighthouse\", result.lighthouse],\n [\"analyze\", result.analyze],\n [\"probes\", result.probes],\n ] as const) {\n if (!stage.ok) lines.push(` ! ${name} not measured — ${stage.error}`);\n }\n if (file) lines.push(\"\", `Report written to ${file}`);\n if (link) lines.push(\"\", `Shareable link: ${link}`);\n if (email?.sent) {\n lines.push(\"\", `Emailed to ${email.recipients.join(\", \")} (${email.messageId})`);\n }\n for (const w of warnings) lines.push(\"\", `! ${w}`);\n if (recovery) {\n lines.push(\n \"\",\n \"Neither --out nor the database took this report — wrote a recovery copy instead:\",\n ` ${recovery.htmlPath}`,\n ` ${recovery.jsonPath}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Run one prospect audit end to end. Progress goes to stderr so `--json` stdout\n * stays pipeable. Persistence needs Turso; without it `--out` is mandatory,\n * because an audit nobody can read afterwards is just a bill.\n *\n * By the time `runProspectAudit` resolves, the money is already spent — only\n * the crawl is fatal, every other stage degrades rather than throwing. So the\n * `--out` write and the database persist below are each wrapped in their own\n * try/catch and attempted independently: a bad `--out` path must not cost a\n * working Turso persist (or vice versa), and if BOTH fail — or persist fails\n * with no `--out` given at all — a recovery copy goes to `reports/` rather\n * than the run ending with nothing anywhere. `--json` only changes the SHAPE\n * of stdout; it still runs the same `--out`/persist/recovery logic beneath it.\n */\nexport async function runProspectAuditCommand(\n url: string,\n opts: ProspectAuditCliOptions,\n): Promise<{ output: string; code: number }> {\n if (!isHttpUrl(url)) {\n return fail(`\"${url}\" is not a URL. Pass the full address, e.g. https://example.com`);\n }\n // #612 review: this validated only the SHAPE. The sole private-address guard\n // was in `triggerProspectAudit` — one layer at the far end of the chain — so\n // anyone running the CLI directly, or any future second caller, had none.\n // `crawlSite` now refuses too; this keeps the refusal a clean message instead\n // of a thrown crawl error.\n if (isPrivateOrLoopbackHost(new URL(url).hostname)) {\n return fail(`\"${url}\" is a private or loopback address — refusing to audit it.`);\n }\n const canPersist = Boolean(process.env.TURSO_DATABASE_URL);\n if (!canPersist && !opts.out) {\n return fail(\n \"No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials.\",\n );\n }\n\n const { runProspectAudit } = await import(\"../../prospect/pipeline.js\");\n const onStage = (name: StageName, status: \"start\" | \"ok\" | \"fail\", detail?: string): void => {\n if (status === \"start\") console.error(`… ${name}`);\n else if (status === \"ok\") console.error(`✓ ${name}`);\n else console.error(`! ${name} — ${detail ?? \"failed\"}`);\n };\n\n const business = opts.business?.trim();\n const goal = opts.goal?.trim();\n // #676. Comma-separated on a flag (a newline is not typeable in one); the\n // cockpit's textareas split on newlines instead. Both drop blanks, and both\n // treat \"nothing usable\" as \"generate\".\n const splitList = (v: string | undefined): string[] =>\n (v ?? \"\")\n .split(\",\")\n .map((x) => x.trim())\n .filter((x) => x !== \"\");\n const chosenTerms = splitList(opts.terms);\n const chosenQuestions = splitList(opts.questions);\n if (goal !== undefined && goal !== \"\" && !isSiteGoal(goal)) {\n return fail(`--goal must be one of: ${GOALS.join(\", \")}`);\n }\n\n const result = await runProspectAudit(\n url,\n {\n ...(business ? { business } : {}),\n // An empty string is what a blank dispatch field sends; it must read as\n // \"not supplied\" so the model's own inference still runs, rather than\n // overriding it with nothing.\n ...(goal ? { goal } : {}),\n ...(opts.competitors\n ? {\n competitors: opts.competitors\n .split(\",\")\n .map((c) => c.trim())\n .filter(Boolean),\n }\n : {}),\n ...(opts.probes === false ? { probes: false } : {}),\n ...(chosenTerms.length > 0 ? { terms: chosenTerms } : {}),\n ...(chosenQuestions.length > 0 ? { questions: chosenQuestions } : {}),\n },\n { ...(opts.deps ?? {}), onStage },\n );\n\n const { renderProspectReport } = await import(\"../../prospect/render.js\");\n const html = renderProspectReport(result);\n\n const warnings: string[] = [];\n\n let file: string | null = null;\n if (opts.out) {\n try {\n await writeFile(opts.out, html, \"utf-8\");\n file = opts.out;\n } catch (err) {\n warnings.push(`Could not write --out (${opts.out}): ${errorMessage(err)}`);\n }\n }\n\n let link: string | null = null;\n let token: string | null = null;\n let auditId: string | null = null;\n if (canPersist) {\n try {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n const { createProspectAudit } = await import(\"../../db/prospect-audits.js\");\n const db = await openDb(readDbConfig());\n const created = await createProspectAudit(db, {\n url: result.url,\n // Map at the boundary: ProspectAuditResult.businessName is the field\n // name (Item 2 — it's a resolved NAME, not a description); the\n // `prospect_audits.business` column keeps its existing name, since\n // renaming a column needs its own migration for no benefit here.\n business: result.businessName,\n status: auditStatus(result),\n resultJson: JSON.stringify(result),\n // #676: on the ROW, not only in result_json — a re-run reuses them and\n // the /audits listing marks them without reading the blob.\n chosenTerms: chosenTerms.length > 0 ? chosenTerms : null,\n chosenQuestions: chosenQuestions.length > 0 ? chosenQuestions : null,\n });\n auditId = created.id;\n token = created.token;\n link = reportUrl(token);\n } catch (err) {\n warnings.push(`Could not save to the database: ${errorMessage(err)}`);\n }\n }\n\n const delivered = file !== null || link !== null;\n const recovery = delivered ? null : await writeRecoveryFiles(result, html);\n const code = delivered ? 0 : 1;\n\n // The audit is already delivered (or recovered) above — an email failure\n // must never take that away. Same independent try/catch shape as the\n // --out write and the Turso persist: record a warning, keep going, never\n // touch `code`. Dynamically imported for the same reason db/client.js and\n // prospect/render.js are above: it reaches `resend` (a devDependency) and\n // must stay out of bin.js's static import graph (see bin.ts's\n // central-dep-blocker comment).\n // The PDF leave-behind, printed from the website's print route — a document\n // designed for paper, not the interactive report with its evidence folded\n // away behind disclosures.\n //\n // Best-effort by design. Every other stage in this pipeline degrades rather\n // than throwing, and an attachment is not worth losing a delivered report\n // over: if this fails the email still goes, with the link, and a warning says\n // the PDF is missing. It also needs a token — with no persisted report there\n // is no page to print.\n let pdf: Buffer | null = null;\n if (opts.email && token) {\n try {\n const { renderReportPdf } = await import(\"../../prospect/pdf.js\");\n pdf = await renderReportPdf(reportPrintUrl(token));\n } catch (err) {\n warnings.push(`Could not attach the PDF: ${errorMessage(err)}`);\n }\n }\n\n let email: SendAuditEmailResult | null = null;\n if (opts.email) {\n try {\n const { sendAuditEmail, parseProspectAuditRecipients } =\n await import(\"../../prospect/email.js\");\n const recipients = parseProspectAuditRecipients(process.env.PROSPECT_AUDIT_RECIPIENTS);\n email = await sendAuditEmail(result, { link, recipients, auditId, pdf });\n if (!email.sent) {\n warnings.push(`Could not email the audit sheet: ${email.reason}`);\n }\n } catch (err) {\n warnings.push(`Could not email the audit sheet: ${errorMessage(err)}`);\n }\n }\n\n if (opts.json) {\n return {\n output: JSON.stringify(\n { result, out: file, link, token, recovery, warnings, email },\n null,\n 2,\n ),\n code,\n };\n }\n\n return { output: summarize(result, link, file, warnings, recovery, email), code };\n}\n","/** Where the prospect-facing report lives now: a real, branded route on the\n * marketing site rather than generated HTML on the ops app's domain. */\nexport const DEFAULT_REPORT_BASE_URL = \"https://reddoorla.com\";\n\n/**\n * Resolve the public report origin.\n *\n * Deliberately its own variable rather than reusing `DASHBOARD_BASE_URL`. That\n * one addresses operators and points at the ops app; this one addresses a\n * prospect and points at reddoorla.com. They are different audiences on\n * different domains, and sharing a variable would mean one of them silently\n * moving the day the other is repointed.\n */\nexport function resolveReportBaseUrl(raw: string | undefined): string {\n return (raw?.trim() || DEFAULT_REPORT_BASE_URL).replace(/\\/$/, \"\");\n}\n\n/** The link a prospect opens. */\nexport function reportUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${resolveReportBaseUrl(raw)}/audit/${token}`;\n}\n\n/** The print-designed variant, which the runner renders to a PDF. Same token,\n * same guards — a separate document, not a stylesheet over the page. */\nexport function reportPrintUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${reportUrl(token, raw)}/print`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MAX_RECENT_PROSPECT_AUDITS,
|
|
3
|
+
OPENED_AT_COALESCE_MS,
|
|
3
4
|
OVERRIDES_MAX_LEN,
|
|
4
5
|
createProspectAudit,
|
|
5
6
|
generateToken,
|
|
@@ -10,9 +11,10 @@ import {
|
|
|
10
11
|
setProspectAuditOverrides,
|
|
11
12
|
siteKey,
|
|
12
13
|
touchProspectAuditOpened
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-P7323YVW.js";
|
|
14
15
|
export {
|
|
15
16
|
MAX_RECENT_PROSPECT_AUDITS,
|
|
17
|
+
OPENED_AT_COALESCE_MS,
|
|
16
18
|
OVERRIDES_MAX_LEN,
|
|
17
19
|
createProspectAudit,
|
|
18
20
|
generateToken,
|
|
@@ -24,4 +26,4 @@ export {
|
|
|
24
26
|
siteKey,
|
|
25
27
|
touchProspectAuditOpened
|
|
26
28
|
};
|
|
27
|
-
//# sourceMappingURL=prospect-audits-
|
|
29
|
+
//# sourceMappingURL=prospect-audits-IZVQY3OQ.js.map
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import {
|
|
2
|
+
rulesetGaps
|
|
3
|
+
} from "./chunk-MNDIED4M.js";
|
|
4
|
+
import {
|
|
5
|
+
makeGitHub
|
|
6
|
+
} from "./chunk-43SW3FGW.js";
|
|
7
|
+
import "./chunk-LBYOLBW7.js";
|
|
8
|
+
|
|
9
|
+
// src/audits/protection-coverage.ts
|
|
10
|
+
var RENOVATE_WORKFLOW_FILE = "renovate.yml";
|
|
11
|
+
var RENOVATE_STALE_AFTER_DAYS = 3;
|
|
12
|
+
var ACCEPTED_GAPS = [];
|
|
13
|
+
function partitionAcceptedGaps(repo, gaps, now, accepted = ACCEPTED_GAPS) {
|
|
14
|
+
const live = [];
|
|
15
|
+
const acceptedOut = [];
|
|
16
|
+
for (const gap of gaps) {
|
|
17
|
+
const match = accepted.find(
|
|
18
|
+
(a) => a.repo === repo && gap.startsWith(a.detailPrefix) && now < new Date(a.until)
|
|
19
|
+
);
|
|
20
|
+
if (match) acceptedOut.push(`${gap} [accepted until ${match.until}: ${match.reason}]`);
|
|
21
|
+
else live.push(gap);
|
|
22
|
+
}
|
|
23
|
+
return { live, accepted: acceptedOut };
|
|
24
|
+
}
|
|
25
|
+
function secretScanningGaps(repo, state, openAlerts) {
|
|
26
|
+
const gaps = [];
|
|
27
|
+
if (state.secretScanning !== "enabled")
|
|
28
|
+
gaps.push(`secret scanning ${state.secretScanning} (fleet floor is enabled)`);
|
|
29
|
+
if (state.pushProtection !== "enabled")
|
|
30
|
+
gaps.push(`push protection ${state.pushProtection} (fleet floor is enabled)`);
|
|
31
|
+
if (state.secretScanning === "enabled") {
|
|
32
|
+
if (openAlerts === "unavailable")
|
|
33
|
+
gaps.push(
|
|
34
|
+
`open secret-scanning alerts unreadable on ${repo} (token lacks security_events/admin read \u2014 unverified, not clean)`
|
|
35
|
+
);
|
|
36
|
+
else if (openAlerts > 0)
|
|
37
|
+
gaps.push(
|
|
38
|
+
`${openAlerts} open secret-scanning alert${openAlerts === 1 ? "" : "s"} on ${repo} \u2014 a leaked credential nobody has triaged. Fix: rotate or restrict the secret, then close the alert with a resolution at https://github.com/${repo}/security/secret-scanning (redacting the value at HEAD does NOT close it \u2014 the history is still public).`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return gaps;
|
|
42
|
+
}
|
|
43
|
+
function renovateGaps(health, now) {
|
|
44
|
+
if (!health.present) return ["no renovate workflow (dependency updates never run here)"];
|
|
45
|
+
if (health.state !== "active")
|
|
46
|
+
return [
|
|
47
|
+
// A disabled workflow REJECTS workflow_dispatch (HTTP 403) — it must be
|
|
48
|
+
// re-enabled first; "just dispatch it" is a dead-end runbook line here.
|
|
49
|
+
`renovate workflow is ${health.state} (re-enable it: \`gh workflow enable renovate.yml -R <repo>\`, then dispatch once)`
|
|
50
|
+
];
|
|
51
|
+
if (health.lastSuccessAt === null)
|
|
52
|
+
return [
|
|
53
|
+
"renovate workflow has never succeeded (dispatch it once; if this gap returns, the schedule never registered \u2014 push a commit touching the workflow file)"
|
|
54
|
+
];
|
|
55
|
+
const succeededAt = Date.parse(health.lastSuccessAt);
|
|
56
|
+
if (Number.isNaN(succeededAt))
|
|
57
|
+
return [`renovate last-success timestamp unreadable: ${health.lastSuccessAt}`];
|
|
58
|
+
const days = (now.getTime() - succeededAt) / 864e5;
|
|
59
|
+
if (days > RENOVATE_STALE_AFTER_DAYS)
|
|
60
|
+
return [`renovate last succeeded ${Math.floor(days)}d ago (cron is twice daily)`];
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
function renovateBlockedGaps(blocked) {
|
|
64
|
+
const orphaned = blocked.filter((b) => b.orphaned);
|
|
65
|
+
if (orphaned.length === 0) return [];
|
|
66
|
+
return [
|
|
67
|
+
`renovate is BLOCKED on ${orphaned.length} branch(es) \u2014 ${orphaned.map((b) => b.branch).join(", ")} \u2014 it runs and succeeds but refuses to update them, so every dependency grouped there is frozen. Usual cause: the branch tip was authored by a retired bot identity, which reads to Renovate as a human edit. Fix: confirm the tip author is a bot, then delete the branch and let Renovate recreate it (\`gh api -X DELETE repos/<repo>/git/refs/heads/<branch>\`).`
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
function dashboardVocabularyGaps(dash) {
|
|
71
|
+
if (!dash.present || dash.unknownSections.length === 0) return [];
|
|
72
|
+
return [
|
|
73
|
+
`renovate dashboard has ${dash.unknownSections.length} section(s) this audit does not recognise \u2014 ${dash.unknownSections.join(", ")}. Nothing is necessarily wrong with the repo, but the blocked-branch check reads Renovate's own headings, so drift there means it can no longer be trusted. Fix: reconcile KNOWN_DASHBOARD_SECTIONS in src/github/gh.ts against Renovate's dependency-dashboard.ts.`
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function isFeatureUpdateBranch(headRef) {
|
|
77
|
+
if (!headRef.startsWith("renovate/")) return false;
|
|
78
|
+
if (headRef === "renovate/lock-file-maintenance") return false;
|
|
79
|
+
return !/^renovate\/npm-.+-vulnerability$/.test(headRef);
|
|
80
|
+
}
|
|
81
|
+
var RENOVATE_DROUGHT_WARN_DAYS = 21;
|
|
82
|
+
function renovateOutcome(window, now) {
|
|
83
|
+
const last = window.merges.filter((m) => isFeatureUpdateBranch(m.headRef))[0];
|
|
84
|
+
if (!last) {
|
|
85
|
+
return {
|
|
86
|
+
state: "unmeasured",
|
|
87
|
+
reason: window.truncated ? "no feature-update Renovate merge inside the scanned window, and the window is FULL (older history not read \u2014 this repo merges too many PRs to see back that far)" : "this repo has never merged a feature-update Renovate PR (no baseline to measure from)"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const days = Math.floor((now.getTime() - new Date(last.mergedAt).getTime()) / 864e5);
|
|
91
|
+
return {
|
|
92
|
+
state: days > RENOVATE_DROUGHT_WARN_DAYS ? "drought" : "delivering",
|
|
93
|
+
days,
|
|
94
|
+
lastBranch: last.headRef,
|
|
95
|
+
lastMergedAt: last.mergedAt
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function renovateOutcomeLine(outcome) {
|
|
99
|
+
if (outcome.state === "delivering") return null;
|
|
100
|
+
if (outcome.state === "unmeasured") return `renovate outcome UNMEASURED \u2014 ${outcome.reason}`;
|
|
101
|
+
return `no feature update has LANDED in ${outcome.days}d (warns past ${RENOVATE_DROUGHT_WARN_DAYS}d; last was ${outcome.lastBranch} merged ${outcome.lastMergedAt.slice(0, 10)}). Security + lockfile Renovate PRs are excluded, so this is feature-version drift, not a security signal.`;
|
|
102
|
+
}
|
|
103
|
+
function renovateOutcomeSummary(outcomes) {
|
|
104
|
+
const n = (state) => outcomes.filter((o) => o.state === state).length;
|
|
105
|
+
return `RENOVATE_OUTCOME drought=${n("drought")} delivering=${n("delivering")} unmeasured=${n("unmeasured")} threshold=${RENOVATE_DROUGHT_WARN_DAYS}d`;
|
|
106
|
+
}
|
|
107
|
+
var BLOCKED_STALE_DAYS = 5;
|
|
108
|
+
async function resolveBlockedBranches(repo, dash, deps, now) {
|
|
109
|
+
if (!dash.present) return [];
|
|
110
|
+
const states = [];
|
|
111
|
+
for (const branch of dash.blockedBranches) {
|
|
112
|
+
const tip = await deps.branchTip(repo, branch);
|
|
113
|
+
if (!tip) continue;
|
|
114
|
+
const ageDays = (now.getTime() - new Date(tip.committedAt).getTime()) / 864e5;
|
|
115
|
+
states.push({ branch, orphaned: tip.authorIsMachine || ageDays > BLOCKED_STALE_DAYS });
|
|
116
|
+
}
|
|
117
|
+
return states;
|
|
118
|
+
}
|
|
119
|
+
async function collectProtectionCoverage(org, deps, now = /* @__PURE__ */ new Date(), accepted = ACCEPTED_GAPS) {
|
|
120
|
+
const rows = [];
|
|
121
|
+
for (const r of await deps.listOrgRepos(org)) {
|
|
122
|
+
const repo = `${org}/${r.name}`;
|
|
123
|
+
if (r.archived || r.visibility !== "public") {
|
|
124
|
+
rows.push({
|
|
125
|
+
repo,
|
|
126
|
+
status: "skipped",
|
|
127
|
+
detail: r.archived ? "archived" : `${r.visibility} (rulesets need a paid plan)`
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const gaps = [];
|
|
133
|
+
let coveredDetail = "";
|
|
134
|
+
const rulesets = await deps.listRepoRulesets(repo);
|
|
135
|
+
if (rulesets.length === 0) {
|
|
136
|
+
gaps.push("no repo rulesets at all");
|
|
137
|
+
} else {
|
|
138
|
+
const judged = await Promise.all(
|
|
139
|
+
rulesets.map(async (rs) => {
|
|
140
|
+
const full = await deps.getRuleset(repo, rs.id);
|
|
141
|
+
return { name: rs.name, full, gaps: rulesetGaps(full, null) };
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
const covering = judged.find((j) => j.gaps.length === 0);
|
|
145
|
+
if (covering) {
|
|
146
|
+
const ciGated = (covering.full.rules ?? []).some(
|
|
147
|
+
(rule) => rule.type === "required_status_checks"
|
|
148
|
+
);
|
|
149
|
+
coveredDetail = `"${covering.name}"${ciGated ? "" : " \u2014 NO CI gate (refs rules only)"}`;
|
|
150
|
+
} else {
|
|
151
|
+
gaps.push(judged.map((j) => `"${j.name}": ${j.gaps.join("; ")}`).join(" | "));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const openAlerts = r.secretScanning === "enabled" ? await deps.openSecretAlerts(repo) : "unavailable";
|
|
155
|
+
gaps.push(...secretScanningGaps(repo, r, openAlerts));
|
|
156
|
+
gaps.push(...renovateGaps(await deps.workflowHealth(repo, RENOVATE_WORKFLOW_FILE), now));
|
|
157
|
+
const dash = await deps.dependencyDashboard(repo);
|
|
158
|
+
gaps.push(...dashboardVocabularyGaps(dash));
|
|
159
|
+
gaps.push(...renovateBlockedGaps(await resolveBlockedBranches(repo, dash, deps, now)));
|
|
160
|
+
const outcome = renovateOutcome(await deps.renovateMergeWindow(repo), now);
|
|
161
|
+
const { live, accepted: acked } = partitionAcceptedGaps(repo, gaps, now, accepted);
|
|
162
|
+
if (live.length > 0) {
|
|
163
|
+
rows.push({
|
|
164
|
+
repo,
|
|
165
|
+
status: "gap",
|
|
166
|
+
detail: [...live, ...acked].join(" | "),
|
|
167
|
+
renovateOutcome: outcome
|
|
168
|
+
});
|
|
169
|
+
} else if (acked.length > 0) {
|
|
170
|
+
rows.push({
|
|
171
|
+
repo,
|
|
172
|
+
status: "skipped",
|
|
173
|
+
detail: acked.join(" | "),
|
|
174
|
+
renovateOutcome: outcome
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
rows.push({
|
|
178
|
+
repo,
|
|
179
|
+
status: "covered",
|
|
180
|
+
detail: coveredDetail,
|
|
181
|
+
renovateOutcome: outcome
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
rows.push({
|
|
186
|
+
repo,
|
|
187
|
+
status: "gap",
|
|
188
|
+
detail: `probe failed: ${e instanceof Error ? e.message : String(e)}`
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return rows;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/github/package-manager-pin.ts
|
|
196
|
+
var PACKAGE_MANAGER_ACCEPTED_GAPS = [];
|
|
197
|
+
function parsePackageManagerField(packageJson) {
|
|
198
|
+
let parsed;
|
|
199
|
+
try {
|
|
200
|
+
parsed = JSON.parse(packageJson);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return {
|
|
203
|
+
state: "unreadable",
|
|
204
|
+
reason: `package.json is not valid JSON (${e instanceof Error ? e.message : String(e)})`
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
208
|
+
return { state: "unreadable", reason: "package.json is not a JSON object" };
|
|
209
|
+
}
|
|
210
|
+
const value = parsed.packageManager;
|
|
211
|
+
if (value === void 0) return { state: "absent" };
|
|
212
|
+
if (typeof value !== "string") {
|
|
213
|
+
return { state: "unreadable", reason: `packageManager is ${typeof value}, not a string` };
|
|
214
|
+
}
|
|
215
|
+
return { state: "present", value };
|
|
216
|
+
}
|
|
217
|
+
function parsePnpmActionSetupPins(workflow) {
|
|
218
|
+
const lines = workflow.split("\n");
|
|
219
|
+
const pins = [];
|
|
220
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
221
|
+
const isBlank = (line) => line.trim().length === 0 || /^\s*#/.test(line);
|
|
222
|
+
for (let i = 0; i < lines.length; i++) {
|
|
223
|
+
const line = lines[i];
|
|
224
|
+
const uses = /^(\s*)(-\s+)?uses:\s*['"]?pnpm\/action-setup(?=[@'"\s]|$)/.exec(line);
|
|
225
|
+
if (!uses) continue;
|
|
226
|
+
const contentIndent = uses[1].length + (uses[2] ? uses[2].length : 0);
|
|
227
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
228
|
+
const body = lines[j];
|
|
229
|
+
if (isBlank(body)) continue;
|
|
230
|
+
const indent = indentOf(body);
|
|
231
|
+
if (indent < contentIndent) break;
|
|
232
|
+
if (indent === contentIndent && /^\s*-\s/.test(body)) break;
|
|
233
|
+
if (indent !== contentIndent || !/^\s*with:\s*$/.test(body)) continue;
|
|
234
|
+
for (let k = j + 1; k < lines.length; k++) {
|
|
235
|
+
const input = lines[k];
|
|
236
|
+
if (isBlank(input)) continue;
|
|
237
|
+
if (indentOf(input) <= contentIndent) break;
|
|
238
|
+
const m = /^\s*version:\s*['"]?([^'"#\s]+)/.exec(input);
|
|
239
|
+
if (m) pins.push(m[1]);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return pins;
|
|
245
|
+
}
|
|
246
|
+
function fleetPin(pins) {
|
|
247
|
+
if (pins.length === 0) return { state: "none" };
|
|
248
|
+
const tally = /* @__PURE__ */ new Map();
|
|
249
|
+
for (const p of pins) tally.set(p, (tally.get(p) ?? 0) + 1);
|
|
250
|
+
const counts = [...tally.entries()].map(([pin, count]) => ({ pin, count })).sort((a, b) => b.count - a.count || a.pin.localeCompare(b.pin));
|
|
251
|
+
const top = counts[0];
|
|
252
|
+
if (top.count * 2 > pins.length) {
|
|
253
|
+
return { state: "majority", pin: top.pin, count: top.count, total: pins.length };
|
|
254
|
+
}
|
|
255
|
+
return { state: "split", counts, total: pins.length };
|
|
256
|
+
}
|
|
257
|
+
function packageManagerGaps(facts, fleet) {
|
|
258
|
+
if (facts.packageJson === null) return [];
|
|
259
|
+
const field = parsePackageManagerField(facts.packageJson);
|
|
260
|
+
const gaps = [];
|
|
261
|
+
const pinnedWorkflows = facts.workflowPins.map((w) => `${w.file} (version: ${w.version})`).join(", ");
|
|
262
|
+
if (field.state === "unreadable") {
|
|
263
|
+
gaps.push(`packageManager unreadable \u2014 ${field.reason} (unverified, not clean)`);
|
|
264
|
+
return gaps;
|
|
265
|
+
}
|
|
266
|
+
if (field.state === "absent") {
|
|
267
|
+
const fleetSays = fleet.state === "majority" ? ` (the fleet pin is ${fleet.pin})` : " (fleet pin unsettled)";
|
|
268
|
+
gaps.push(
|
|
269
|
+
facts.workflowPins.length > 0 ? `no packageManager field in package.json${fleetSays}, and ${pinnedWorkflows} pins pnpm in a pnpm/action-setup step instead. Fix: add the field AND delete the workflow's version input in the SAME change \u2014 adding the field alone makes action-setup hard-error on the mismatch.` : `no packageManager field in package.json${fleetSays} \u2014 CI installs whatever pnpm the runner defaults to, so the version resolving this lockfile is GitHub's choice, not the repo's. Fix: add "packageManager" to package.json.`
|
|
270
|
+
);
|
|
271
|
+
return gaps;
|
|
272
|
+
}
|
|
273
|
+
if (fleet.state === "majority" && field.value !== fleet.pin) {
|
|
274
|
+
gaps.push(
|
|
275
|
+
`packageManager is ${field.value} but the fleet pin is ${fleet.pin} (${fleet.count}/${fleet.total} repos agree). Fix: bump the field, or say why this repo is deliberately apart.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (facts.workflowPins.length > 0) {
|
|
279
|
+
const agree = facts.workflowPins.every((w) => `pnpm@${w.version}` === field.value);
|
|
280
|
+
gaps.push(
|
|
281
|
+
`${pinnedWorkflows} pins a pnpm version input while package.json pins ${field.value}` + (agree ? " \u2014 they agree today, but nothing keeps them in step" : " \u2014 THEY DISAGREE") + `. pnpm/action-setup does not prefer either side, it hard-errors on a mismatch. Fix: delete the workflow's version input; packageManager is the single source of truth.`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return gaps;
|
|
285
|
+
}
|
|
286
|
+
async function collectPackageManagerPins(org, deps, now = /* @__PURE__ */ new Date(), accepted = PACKAGE_MANAGER_ACCEPTED_GAPS) {
|
|
287
|
+
const rows = [];
|
|
288
|
+
const facts = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const r of await deps.listOrgRepos(org)) {
|
|
290
|
+
const repo = `${org}/${r.name}`;
|
|
291
|
+
if (r.archived || r.visibility !== "public") {
|
|
292
|
+
rows.push({
|
|
293
|
+
repo,
|
|
294
|
+
scope: "skipped",
|
|
295
|
+
gaps: [],
|
|
296
|
+
accepted: [],
|
|
297
|
+
detail: r.archived ? "archived" : `${r.visibility}`
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const packageJson = await deps.repoTextFile(repo, "package.json");
|
|
303
|
+
if (packageJson === null) {
|
|
304
|
+
rows.push({
|
|
305
|
+
repo,
|
|
306
|
+
scope: "out-of-scope",
|
|
307
|
+
gaps: [],
|
|
308
|
+
accepted: [],
|
|
309
|
+
detail: "no package.json"
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const workflowPins = [];
|
|
314
|
+
for (const path of await deps.listWorkflowPaths(repo)) {
|
|
315
|
+
const contents = await deps.repoTextFile(repo, path);
|
|
316
|
+
if (contents === null) continue;
|
|
317
|
+
for (const version of parsePnpmActionSetupPins(contents)) {
|
|
318
|
+
workflowPins.push({ file: path.replace(/^\.github\/workflows\//, ""), version });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
facts.set(repo, { packageJson, workflowPins });
|
|
322
|
+
rows.push({ repo, scope: "judged", gaps: [], accepted: [], detail: "" });
|
|
323
|
+
} catch (e) {
|
|
324
|
+
rows.push({
|
|
325
|
+
repo,
|
|
326
|
+
scope: "judged",
|
|
327
|
+
gaps: [`pnpm pin probe failed: ${e instanceof Error ? e.message : String(e)}`],
|
|
328
|
+
accepted: [],
|
|
329
|
+
detail: "probe failed"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const pinned = [];
|
|
334
|
+
for (const f of facts.values()) {
|
|
335
|
+
const field = parsePackageManagerField(f.packageJson);
|
|
336
|
+
if (field.state === "present") pinned.push(field.value);
|
|
337
|
+
}
|
|
338
|
+
const fleet = fleetPin(pinned);
|
|
339
|
+
return rows.map((row) => {
|
|
340
|
+
const f = facts.get(row.repo);
|
|
341
|
+
if (!f) return row;
|
|
342
|
+
const all = packageManagerGaps(f, fleet);
|
|
343
|
+
const split = partitionAcceptedGaps(row.repo, all, now, accepted);
|
|
344
|
+
const field = parsePackageManagerField(f.packageJson);
|
|
345
|
+
return {
|
|
346
|
+
...row,
|
|
347
|
+
gaps: split.live,
|
|
348
|
+
accepted: split.accepted,
|
|
349
|
+
detail: field.state === "present" ? field.value : row.detail
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function packageManagerPinSummary(rows) {
|
|
354
|
+
const judged = rows.filter((r) => r.scope === "judged");
|
|
355
|
+
const pins = judged.map((r) => r.detail).filter((d) => d.startsWith("pnpm@"));
|
|
356
|
+
const fleet = fleetPin(pins);
|
|
357
|
+
const pin = fleet.state === "majority" ? fleet.pin : fleet.state === "split" ? `SPLIT(${fleet.counts.map((c) => `${c.pin}x${c.count}`).join(",")})` : "none";
|
|
358
|
+
return `PACKAGE_MANAGER_PIN gaps=${judged.filter((r) => r.gaps.length > 0).length} pinned=${pins.length} judged=${judged.length} out-of-scope=${rows.filter((r) => r.scope === "out-of-scope").length} skipped=${rows.filter((r) => r.scope === "skipped").length} fleetPin=${pin}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/cli/commands/protection-audit.ts
|
|
362
|
+
function protectionAuditExitCode(gaps) {
|
|
363
|
+
return gaps > 0 ? 1 : 0;
|
|
364
|
+
}
|
|
365
|
+
async function runProtectionAuditCommand(opts, depsOverride) {
|
|
366
|
+
const org = opts.org?.trim();
|
|
367
|
+
if (!org) {
|
|
368
|
+
return { output: "protection-audit requires --org <org>", code: 2 };
|
|
369
|
+
}
|
|
370
|
+
const token = process.env.GH_TOKEN?.trim();
|
|
371
|
+
if (!depsOverride && !token) {
|
|
372
|
+
return {
|
|
373
|
+
output: "protection-audit skipped: no GH_TOKEN (fleet read) configured.",
|
|
374
|
+
code: 0
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const deps = depsOverride ?? makeGitHub({ token });
|
|
378
|
+
const coverage = await collectProtectionCoverage(org, deps);
|
|
379
|
+
const pinRows = await collectPackageManagerPins(org, deps);
|
|
380
|
+
const pinByRepo = new Map(pinRows.map((p) => [p.repo, p]));
|
|
381
|
+
const rows = coverage.map((row) => {
|
|
382
|
+
const pin = pinByRepo.get(row.repo);
|
|
383
|
+
if (!pin || pin.gaps.length === 0) return row;
|
|
384
|
+
return {
|
|
385
|
+
...row,
|
|
386
|
+
status: "gap",
|
|
387
|
+
detail: [row.detail, ...pin.gaps].filter((d) => d.length > 0).join(" | ")
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
const gaps = rows.filter((r) => r.status === "gap");
|
|
391
|
+
const covered = rows.filter((r) => r.status === "covered");
|
|
392
|
+
const skipped = rows.filter((r) => r.status === "skipped");
|
|
393
|
+
const lines = rows.map((r) => `${r.status.toUpperCase().padEnd(7)} ${r.repo} \u2014 ${r.detail}`);
|
|
394
|
+
const measured = rows.map((r) => r.renovateOutcome).filter((o) => o !== void 0);
|
|
395
|
+
for (const r of rows) {
|
|
396
|
+
const line = r.renovateOutcome && renovateOutcomeLine(r.renovateOutcome);
|
|
397
|
+
if (line) lines.push(`WARN ${r.repo} \u2014 ${line}`);
|
|
398
|
+
}
|
|
399
|
+
for (const p of pinRows) {
|
|
400
|
+
for (const note of p.accepted) lines.push(`WARN ${p.repo} \u2014 ${note}`);
|
|
401
|
+
}
|
|
402
|
+
lines.push(renovateOutcomeSummary(measured));
|
|
403
|
+
lines.push(packageManagerPinSummary(pinRows));
|
|
404
|
+
lines.push(
|
|
405
|
+
`PROTECTION_AUDIT gaps=${gaps.length} covered=${covered.length} skipped=${skipped.length} total=${rows.length}`
|
|
406
|
+
);
|
|
407
|
+
return { output: lines.join("\n"), code: protectionAuditExitCode(gaps.length) };
|
|
408
|
+
}
|
|
409
|
+
export {
|
|
410
|
+
protectionAuditExitCode,
|
|
411
|
+
runProtectionAuditCommand
|
|
412
|
+
};
|
|
413
|
+
//# sourceMappingURL=protection-audit-YXJKFU6O.js.map
|