@reddoorla/maintenance 0.96.0 → 0.98.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 +5 -6
- package/dist/{announce-45HVP4J4.js → announce-2FH2KBQQ.js} +39 -31
- package/dist/announce-2FH2KBQQ.js.map +1 -0
- package/dist/attachments-JFGFQRXQ.js +10 -0
- package/dist/{blux-RUFMNOUJ.js → blux-NXQLMS4W.js} +5 -5
- package/dist/{bump-deps-EL3TKMIL.js → bump-deps-54K4ECP3.js} +6 -6
- package/dist/{chunk-IMYL67JV.js → chunk-3RLDX3Q2.js} +5 -5
- package/dist/{chunk-EQQ4H6L3.js → chunk-3YALLSBF.js} +2 -2
- package/dist/{chunk-LAJ3FF54.js → chunk-5UI4FERC.js} +16 -5
- package/dist/chunk-5UI4FERC.js.map +1 -0
- package/dist/{chunk-CA6ADGOC.js → chunk-6PGXKN6F.js} +2 -2
- package/dist/{chunk-Y5BVR4HW.js → chunk-6THEZQLA.js} +3 -3
- package/dist/chunk-ASLDCWBF.js +41 -0
- package/dist/chunk-ASLDCWBF.js.map +1 -0
- package/dist/{chunk-DVJNYPFR.js → chunk-AYDQQBNG.js} +2 -2
- package/dist/{chunk-OTEVKRAH.js → chunk-B2BRFWJY.js} +7 -16
- package/dist/chunk-B2BRFWJY.js.map +1 -0
- package/dist/{chunk-GEUC5VGM.js → chunk-BINFZQ2T.js} +49 -4
- package/dist/chunk-BINFZQ2T.js.map +1 -0
- package/dist/{chunk-T76MA2FH.js → chunk-CP6MVZ5X.js} +27 -1
- package/dist/{chunk-T76MA2FH.js.map → chunk-CP6MVZ5X.js.map} +1 -1
- package/dist/{chunk-72FJZ6LO.js → chunk-DRNZVORX.js} +26 -22
- package/dist/chunk-DRNZVORX.js.map +1 -0
- package/dist/{chunk-XY7YV2YQ.js → chunk-DXZRWW2F.js} +65 -10
- package/dist/chunk-DXZRWW2F.js.map +1 -0
- package/dist/{chunk-SHGIDTYF.js → chunk-DYTYPKFN.js} +2 -2
- package/dist/chunk-DZZ5KRTB.js +23 -0
- package/dist/chunk-DZZ5KRTB.js.map +1 -0
- package/dist/{chunk-NVLREB5R.js → chunk-EN6CTBUN.js} +7 -7
- package/dist/{chunk-ZH5LOQET.js → chunk-FHFXJ3EM.js} +7 -5
- package/dist/chunk-FHFXJ3EM.js.map +1 -0
- package/dist/{chunk-3EA43S3R.js → chunk-GRF2RYEO.js} +12 -12
- package/dist/{chunk-RRE74VWI.js → chunk-HSMN35LU.js} +2 -2
- package/dist/chunk-ICSRWW5Q.js +148 -0
- package/dist/chunk-ICSRWW5Q.js.map +1 -0
- package/dist/{chunk-XG2A6GIG.js → chunk-IDBPII6G.js} +4 -4
- package/dist/{chunk-XG2A6GIG.js.map → chunk-IDBPII6G.js.map} +1 -1
- package/dist/{chunk-ECDJDHKT.js → chunk-JHJNNJLG.js} +2 -2
- package/dist/{chunk-QFSL2L2B.js → chunk-L5F3WYIY.js} +81 -31
- package/dist/chunk-L5F3WYIY.js.map +1 -0
- package/dist/{chunk-XED2DNAI.js → chunk-LAG6IP7Y.js} +7 -7
- package/dist/{chunk-UIITN37J.js → chunk-LF2TJN4X.js} +2 -2
- package/dist/chunk-LF2TJN4X.js.map +1 -0
- package/dist/{chunk-SQ4FSNYL.js → chunk-LUF52EIY.js} +2 -2
- package/dist/{chunk-MZZDMBDT.js → chunk-LZMAG73U.js} +19 -19
- package/dist/chunk-LZMAG73U.js.map +1 -0
- package/dist/{chunk-6INOBNRO.js → chunk-MNRWHSFS.js} +2 -2
- package/dist/{chunk-CGQZFQAY.js → chunk-MVKNU4QD.js} +2 -3
- package/dist/chunk-MVKNU4QD.js.map +1 -0
- package/dist/{chunk-62YWOFVN.js → chunk-ORTOMEYR.js} +3 -3
- package/dist/{chunk-62YWOFVN.js.map → chunk-ORTOMEYR.js.map} +1 -1
- package/dist/{chunk-44SDGPGQ.js → chunk-PZZ6HM5T.js} +3 -3
- package/dist/chunk-RQJFGK3A.js +51 -0
- package/dist/chunk-RQJFGK3A.js.map +1 -0
- package/dist/{chunk-H445EH33.js → chunk-S2PLX4XK.js} +10 -7
- package/dist/chunk-S2PLX4XK.js.map +1 -0
- package/dist/{chunk-KN52PM63.js → chunk-TC4GANMX.js} +47 -149
- 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-4UTJ4G6P.js → chunk-TJLKWQOX.js} +2 -2
- package/dist/{chunk-A5QKNOXW.js → chunk-TTT6HYPW.js} +47 -10
- package/dist/chunk-TTT6HYPW.js.map +1 -0
- package/dist/{chunk-VEIIUCYL.js → chunk-TV76A6GN.js} +2 -2
- package/dist/{chunk-D3CTOCUR.js → chunk-TVZ6QCOE.js} +3 -3
- package/dist/chunk-UPE6D4AT.js +69 -0
- package/dist/chunk-UPE6D4AT.js.map +1 -0
- package/dist/{chunk-BIYQN7FH.js → chunk-VCPTOZ56.js} +31 -1
- package/dist/{chunk-BIYQN7FH.js.map → chunk-VCPTOZ56.js.map} +1 -1
- package/dist/chunk-VQMAAQ5M.js +53 -0
- package/dist/chunk-VQMAAQ5M.js.map +1 -0
- package/dist/{chunk-EASYCD6F.js → chunk-WMF5S2MB.js} +1 -1
- package/dist/{chunk-EASYCD6F.js.map → chunk-WMF5S2MB.js.map} +1 -1
- package/dist/{chunk-37B4LVB3.js → chunk-X56LAKPR.js} +3 -3
- package/dist/chunk-X56LAKPR.js.map +1 -0
- package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
- package/dist/chunk-YJBSB4LP.js.map +1 -0
- package/dist/{chunk-P4A6D6HO.js → chunk-ZEE5WQYU.js} +32 -18
- package/dist/chunk-ZEE5WQYU.js.map +1 -0
- package/dist/cli/bin.js +92 -50
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +33 -3
- package/dist/cli/commands/audit.js +26 -23
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-3XGSZMLU.js +11 -0
- package/dist/{convert-to-pnpm-4DON7IFS.js → convert-to-pnpm-U2CRNKNY.js} +6 -6
- package/dist/{db-5K4CDEGJ.js → db-7XYTBIPH.js} +25 -18
- package/dist/db-7XYTBIPH.js.map +1 -0
- package/dist/{deadletter-G4M3HSVM.js → deadletter-A7RQVNG3.js} +8 -4
- package/dist/{digest-5AJGWS33.js → digest-AMXRGWZA.js} +34 -33
- package/dist/digest-AMXRGWZA.js.map +1 -0
- package/dist/{digest-collectors-MC5NWEUA.js → digest-collectors-CX2NCGS7.js} +15 -12
- package/dist/ensure-site-F2WFCWWA.js +70 -0
- package/dist/ensure-site-F2WFCWWA.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-XESZHZEB.js → ensure-site-OO4UPWNQ.js} +19 -42
- package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
- package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
- package/dist/{fleet-state-GJWGKTLD.js → fleet-state-RJROYXKO.js} +17 -7
- package/dist/{forms-notify-target-4NPLX2UG.js → forms-notify-target-DK5R326N.js} +17 -13
- package/dist/forms-notify-target-DK5R326N.js.map +1 -0
- package/dist/{github-signals-DMU74SAX.js → github-signals-RGRPYTUB.js} +20 -15
- package/dist/github-signals-RGRPYTUB.js.map +1 -0
- package/dist/{header-image-BRTOLHJF.js → header-image-43ZIEJKP.js} +13 -17
- package/dist/header-image-43ZIEJKP.js.map +1 -0
- package/dist/{health-endpoint-UCLMSSCN.js → health-endpoint-K5LXDPGE.js} +7 -7
- package/dist/{health-mirror-ONRT4DQ5.js → health-mirror-WZUHWCCV.js} +10 -10
- package/dist/{import-airtable-IIK2QUUF.js → import-airtable-UBF5GYAL.js} +3 -3
- package/dist/index.d.ts +102 -494
- package/dist/index.js +62 -52
- package/dist/index.js.map +1 -1
- package/dist/{init-3LMZTCVD.js → init-OFITGMOP.js} +16 -16
- package/dist/{launch-XTDMC6LD.js → launch-AJY3TGTQ.js} +37 -35
- package/dist/launch-AJY3TGTQ.js.map +1 -0
- package/dist/{lighthouse-52WSPZ5D.js → lighthouse-RFMFREEX.js} +2 -2
- package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
- package/dist/{chunk-IHHAYXKY.js → match-harness-CFELJA57.js} +70 -4
- package/dist/match-harness-CFELJA57.js.map +1 -0
- package/dist/migrate-EZL55AWA.js +7 -0
- package/dist/notify-K3WVH3DM.js +1 -1
- package/dist/{onboard-CZYBWVWH.js → onboard-F7RTLMDZ.js} +6 -6
- package/dist/{orchestrate-TGDFGY52.js → orchestrate-SAIFXZIW.js} +13 -10
- package/dist/parity-2JGVUCZD.js +14 -0
- package/dist/{pipeline-UUPXOS2J.js → pipeline-TCSNVDQZ.js} +3 -3
- package/dist/{preflight-POZZI3GN.js → preflight-6XGVVO2U.js} +35 -11
- package/dist/preflight-6XGVVO2U.js.map +1 -0
- package/dist/{prismic-ci-AHY5ULJR.js → prismic-ci-XMX7A5E4.js} +11 -11
- package/dist/prismic-ci-XMX7A5E4.js.map +1 -0
- package/dist/{prismic-models-URYGODNY.js → prismic-models-7GKFAAFA.js} +19 -16
- package/dist/prismic-models-7GKFAAFA.js.map +1 -0
- package/dist/prospect/types.d.ts +44 -2
- package/dist/prospect/types.js +1 -1
- package/dist/{prospect-audit-AJEE3Z4L.js → prospect-audit-KWP5P5U2.js} +37 -5
- package/dist/prospect-audit-KWP5P5U2.js.map +1 -0
- package/dist/{protection-audit-3J3UEOVT.js → protection-audit-YXJKFU6O.js} +3 -3
- package/dist/{protection-audit-3J3UEOVT.js.map → protection-audit-YXJKFU6O.js.map} +1 -1
- package/dist/recipes/sync-configs.js +3 -3
- package/dist/{render-MHUUHPLN.js → render-NWUAX6RN.js} +3 -3
- package/dist/{render-from-row-65DC6ZP3.js → render-from-row-75JTED2Z.js} +6 -4
- package/dist/{renovate-dispatch-ONJMBUHJ.js → renovate-dispatch-ZZQXT7DA.js} +13 -8
- package/dist/{renovate-dispatch-ONJMBUHJ.js.map → renovate-dispatch-ZZQXT7DA.js.map} +1 -1
- package/dist/replay-HB44TAII.js +77 -0
- package/dist/replay-HB44TAII.js.map +1 -0
- package/dist/{report-RC5LGPU3.js → report-MVPWS7WP.js} +102 -58
- package/dist/report-MVPWS7WP.js.map +1 -0
- package/dist/{report-mirror-KKZZ6RMZ.js → report-mirror-O6IDFDWU.js} +28 -10
- package/dist/report-mirror-O6IDFDWU.js.map +1 -0
- package/dist/roster-EJ3ILR52.js +19 -0
- package/dist/roster-EJ3ILR52.js.map +1 -0
- package/dist/{screenouts-7P5GOZMV.js → screenouts-7NQUWHOJ.js} +14 -1
- package/dist/screenouts-7NQUWHOJ.js.map +1 -0
- package/dist/{self-updating-RKKIYNV4.js → self-updating-KX5FMGEO.js} +7 -7
- package/dist/{selftest-AFLR7YV7.js → selftest-KABY2CYJ.js} +21 -17
- package/dist/{selftest-AFLR7YV7.js.map → selftest-KABY2CYJ.js.map} +1 -1
- package/dist/site-create-ZU3ACBTO.js +45 -0
- package/dist/site-create-ZU3ACBTO.js.map +1 -0
- package/dist/{site-mirror-H2FYMN55.js → site-mirror-TKTDWYP3.js} +10 -10
- package/dist/site-mirror-TKTDWYP3.js.map +1 -0
- package/dist/{smoke-suite-BWHR3XX5.js → smoke-suite-QQMV7TNA.js} +7 -7
- package/dist/{submissions-JDZP35JM.js → submissions-4I66W6QK.js} +11 -1
- package/dist/{submissions-JDZP35JM.js.map → submissions-4I66W6QK.js.map} +1 -1
- package/dist/{submissions-II5J35QM.js → submissions-K5UXGZUN.js} +12 -7
- package/dist/submissions-K5UXGZUN.js.map +1 -0
- package/dist/{svelte-codemods-QWGIRNN6.js → svelte-codemods-YFN4TEGV.js} +6 -6
- package/dist/{sync-NKPDCMYW.js → sync-F4USL4K5.js} +6 -6
- package/dist/{sync-configs-HGLQV55H.js → sync-configs-HBXS3D7C.js} +6 -6
- package/dist/turso-KQKBBQCK.js +24 -0
- package/dist/turso-KQKBBQCK.js.map +1 -0
- package/dist/{upgrade-WZ44IARG.js → upgrade-XOJMDIT4.js} +6 -6
- package/dist/util/git.d.ts +1 -1
- package/dist/util/git.js +1 -1
- package/dist/{websites-6DHIHJ2D.js → websites-Y6FN2XFD.js} +26 -21
- package/dist/{write-audits-to-airtable-BIK6LNVC.js → write-audits-to-airtable-TI3JMWSY.js} +6 -4
- package/package.json +1 -1
- package/dist/airtable-SJV4ZSMS.js +0 -9
- package/dist/announce-45HVP4J4.js.map +0 -1
- package/dist/attachments-NLZIJ5NU.js +0 -9
- package/dist/chunk-37B4LVB3.js.map +0 -1
- package/dist/chunk-72FJZ6LO.js.map +0 -1
- package/dist/chunk-74QRWE6G.js +0 -53
- package/dist/chunk-74QRWE6G.js.map +0 -1
- package/dist/chunk-A5QKNOXW.js.map +0 -1
- package/dist/chunk-CGQZFQAY.js.map +0 -1
- package/dist/chunk-F3JWOPTG.js.map +0 -1
- package/dist/chunk-GEUC5VGM.js.map +0 -1
- package/dist/chunk-H445EH33.js.map +0 -1
- package/dist/chunk-IHHAYXKY.js.map +0 -1
- package/dist/chunk-IY6256GK.js.map +0 -1
- package/dist/chunk-KN52PM63.js.map +0 -1
- package/dist/chunk-LAJ3FF54.js.map +0 -1
- package/dist/chunk-MZZDMBDT.js.map +0 -1
- package/dist/chunk-OTEVKRAH.js.map +0 -1
- package/dist/chunk-P4A6D6HO.js.map +0 -1
- package/dist/chunk-QFSL2L2B.js.map +0 -1
- package/dist/chunk-UIITN37J.js.map +0 -1
- package/dist/chunk-XY7YV2YQ.js.map +0 -1
- package/dist/chunk-YFAXOD7Z.js +0 -42
- package/dist/chunk-YFAXOD7Z.js.map +0 -1
- package/dist/chunk-ZH5LOQET.js.map +0 -1
- package/dist/client-L7NAUG6J.js +0 -11
- package/dist/db-5K4CDEGJ.js.map +0 -1
- package/dist/digest-5AJGWS33.js.map +0 -1
- package/dist/ensure-site-XESZHZEB.js.map +0 -1
- package/dist/fleet-events-cF2Om88w.d.ts +0 -24
- package/dist/forms-notify-target-4NPLX2UG.js.map +0 -1
- package/dist/github-signals-DMU74SAX.js.map +0 -1
- package/dist/header-image-BRTOLHJF.js.map +0 -1
- package/dist/launch-XTDMC6LD.js.map +0 -1
- package/dist/lighthouse-airtable-3NOFB42B.js +0 -12
- package/dist/match-harness-OW45CB5H.js +0 -77
- package/dist/match-harness-OW45CB5H.js.map +0 -1
- package/dist/migrate-NE3DUMTM.js +0 -7
- package/dist/parity-AEKYSLN6.js +0 -14
- package/dist/preflight-POZZI3GN.js.map +0 -1
- package/dist/prismic-ci-AHY5ULJR.js.map +0 -1
- package/dist/prismic-models-URYGODNY.js.map +0 -1
- package/dist/prospect-audit-AJEE3Z4L.js.map +0 -1
- package/dist/replay-4FVFOMHH.js +0 -54
- package/dist/replay-4FVFOMHH.js.map +0 -1
- package/dist/report-RC5LGPU3.js.map +0 -1
- package/dist/report-mirror-KKZZ6RMZ.js.map +0 -1
- package/dist/screenouts-7P5GOZMV.js.map +0 -1
- package/dist/site-mirror-H2FYMN55.js.map +0 -1
- package/dist/submissions-II5J35QM.js.map +0 -1
- package/dist/write-audits-to-airtable-BIK6LNVC.js.map +0 -1
- /package/dist/{airtable-SJV4ZSMS.js.map → attachments-JFGFQRXQ.js.map} +0 -0
- /package/dist/{blux-RUFMNOUJ.js.map → blux-NXQLMS4W.js.map} +0 -0
- /package/dist/{bump-deps-EL3TKMIL.js.map → bump-deps-54K4ECP3.js.map} +0 -0
- /package/dist/{chunk-IMYL67JV.js.map → chunk-3RLDX3Q2.js.map} +0 -0
- /package/dist/{chunk-EQQ4H6L3.js.map → chunk-3YALLSBF.js.map} +0 -0
- /package/dist/{chunk-CA6ADGOC.js.map → chunk-6PGXKN6F.js.map} +0 -0
- /package/dist/{chunk-Y5BVR4HW.js.map → chunk-6THEZQLA.js.map} +0 -0
- /package/dist/{chunk-DVJNYPFR.js.map → chunk-AYDQQBNG.js.map} +0 -0
- /package/dist/{chunk-SHGIDTYF.js.map → chunk-DYTYPKFN.js.map} +0 -0
- /package/dist/{chunk-NVLREB5R.js.map → chunk-EN6CTBUN.js.map} +0 -0
- /package/dist/{chunk-3EA43S3R.js.map → chunk-GRF2RYEO.js.map} +0 -0
- /package/dist/{chunk-RRE74VWI.js.map → chunk-HSMN35LU.js.map} +0 -0
- /package/dist/{chunk-ECDJDHKT.js.map → chunk-JHJNNJLG.js.map} +0 -0
- /package/dist/{chunk-XED2DNAI.js.map → chunk-LAG6IP7Y.js.map} +0 -0
- /package/dist/{chunk-SQ4FSNYL.js.map → chunk-LUF52EIY.js.map} +0 -0
- /package/dist/{chunk-6INOBNRO.js.map → chunk-MNRWHSFS.js.map} +0 -0
- /package/dist/{chunk-44SDGPGQ.js.map → chunk-PZZ6HM5T.js.map} +0 -0
- /package/dist/{chunk-4UTJ4G6P.js.map → chunk-TJLKWQOX.js.map} +0 -0
- /package/dist/{chunk-VEIIUCYL.js.map → chunk-TV76A6GN.js.map} +0 -0
- /package/dist/{chunk-D3CTOCUR.js.map → chunk-TVZ6QCOE.js.map} +0 -0
- /package/dist/{attachments-NLZIJ5NU.js.map → client-3XGSZMLU.js.map} +0 -0
- /package/dist/{convert-to-pnpm-4DON7IFS.js.map → convert-to-pnpm-U2CRNKNY.js.map} +0 -0
- /package/dist/{client-L7NAUG6J.js.map → deadletter-A7RQVNG3.js.map} +0 -0
- /package/dist/{deadletter-G4M3HSVM.js.map → digest-collectors-CX2NCGS7.js.map} +0 -0
- /package/dist/{digest-collectors-MC5NWEUA.js.map → fleet-state-RJROYXKO.js.map} +0 -0
- /package/dist/{health-endpoint-UCLMSSCN.js.map → health-endpoint-K5LXDPGE.js.map} +0 -0
- /package/dist/{health-mirror-ONRT4DQ5.js.map → health-mirror-WZUHWCCV.js.map} +0 -0
- /package/dist/{fleet-state-GJWGKTLD.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
- /package/dist/{init-3LMZTCVD.js.map → init-OFITGMOP.js.map} +0 -0
- /package/dist/{import-airtable-IIK2QUUF.js.map → lighthouse-RFMFREEX.js.map} +0 -0
- /package/dist/{lighthouse-52WSPZ5D.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
- /package/dist/{lighthouse-airtable-3NOFB42B.js.map → migrate-EZL55AWA.js.map} +0 -0
- /package/dist/{onboard-CZYBWVWH.js.map → onboard-F7RTLMDZ.js.map} +0 -0
- /package/dist/{migrate-NE3DUMTM.js.map → orchestrate-SAIFXZIW.js.map} +0 -0
- /package/dist/{orchestrate-TGDFGY52.js.map → parity-2JGVUCZD.js.map} +0 -0
- /package/dist/{parity-AEKYSLN6.js.map → pipeline-TCSNVDQZ.js.map} +0 -0
- /package/dist/{render-MHUUHPLN.js.map → render-NWUAX6RN.js.map} +0 -0
- /package/dist/{pipeline-UUPXOS2J.js.map → render-from-row-75JTED2Z.js.map} +0 -0
- /package/dist/{self-updating-RKKIYNV4.js.map → self-updating-KX5FMGEO.js.map} +0 -0
- /package/dist/{smoke-suite-BWHR3XX5.js.map → smoke-suite-QQMV7TNA.js.map} +0 -0
- /package/dist/{svelte-codemods-QWGIRNN6.js.map → svelte-codemods-YFN4TEGV.js.map} +0 -0
- /package/dist/{sync-NKPDCMYW.js.map → sync-F4USL4K5.js.map} +0 -0
- /package/dist/{sync-configs-HGLQV55H.js.map → sync-configs-HBXS3D7C.js.map} +0 -0
- /package/dist/{upgrade-WZ44IARG.js.map → upgrade-XOJMDIT4.js.map} +0 -0
- /package/dist/{render-from-row-65DC6ZP3.js.map → websites-Y6FN2XFD.js.map} +0 -0
- /package/dist/{websites-6DHIHJ2D.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
|
@@ -2,31 +2,31 @@ import {
|
|
|
2
2
|
appendSkipNotice,
|
|
3
3
|
fleetWorkdir,
|
|
4
4
|
prepareFleetSites
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-DYTYPKFN.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveSites
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-S2PLX4XK.js";
|
|
9
9
|
import {
|
|
10
10
|
init
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-GRF2RYEO.js";
|
|
12
|
+
import "./chunk-EN6CTBUN.js";
|
|
13
|
+
import "./chunk-6PGXKN6F.js";
|
|
14
|
+
import "./chunk-AYDQQBNG.js";
|
|
15
|
+
import "./chunk-HSMN35LU.js";
|
|
15
16
|
import "./chunk-J7SZQUCW.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-LAG6IP7Y.js";
|
|
18
|
+
import "./chunk-TJLKWQOX.js";
|
|
18
19
|
import "./chunk-5CJVUPJU.js";
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-IMYL67JV.js";
|
|
21
|
-
import "./chunk-SQ4FSNYL.js";
|
|
20
|
+
import "./chunk-3RLDX3Q2.js";
|
|
22
21
|
import "./chunk-SLFG7Y76.js";
|
|
22
|
+
import "./chunk-LUF52EIY.js";
|
|
23
23
|
import "./chunk-OTGB6YQO.js";
|
|
24
|
-
import "./chunk-
|
|
24
|
+
import "./chunk-DXZRWW2F.js";
|
|
25
25
|
import "./chunk-LKGVSM2O.js";
|
|
26
|
-
import "./chunk-
|
|
26
|
+
import "./chunk-MVKNU4QD.js";
|
|
27
27
|
import "./chunk-43SW3FGW.js";
|
|
28
28
|
import "./chunk-Z3JXLULD.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-VCPTOZ56.js";
|
|
30
30
|
import "./chunk-UDRHAQT7.js";
|
|
31
31
|
import "./chunk-PTDMVGKL.js";
|
|
32
32
|
import "./chunk-LBYOLBW7.js";
|
|
@@ -36,7 +36,7 @@ import "./chunk-RARREDQE.js";
|
|
|
36
36
|
import {
|
|
37
37
|
siteLabel
|
|
38
38
|
} from "./chunk-XXTZBPUY.js";
|
|
39
|
-
import "./chunk-
|
|
39
|
+
import "./chunk-WMF5S2MB.js";
|
|
40
40
|
import "./chunk-NMAWXBJA.js";
|
|
41
41
|
import "./chunk-CXICCLNM.js";
|
|
42
42
|
|
|
@@ -111,4 +111,4 @@ async function runInitCommand(site, opts) {
|
|
|
111
111
|
export {
|
|
112
112
|
runInitCommand
|
|
113
113
|
};
|
|
114
|
-
//# sourceMappingURL=init-
|
|
114
|
+
//# sourceMappingURL=init-OFITGMOP.js.map
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
HARNESS_JSON_RELATIVE,
|
|
3
|
+
UNGUARDED_TWIN_TELL
|
|
4
|
+
} from "./chunk-Y6FCXRIC.js";
|
|
4
5
|
import {
|
|
5
6
|
selfUpdating
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IDBPII6G.js";
|
|
7
8
|
import "./chunk-MNDIED4M.js";
|
|
9
|
+
import {
|
|
10
|
+
writeAuditsToAirtable
|
|
11
|
+
} from "./chunk-FHFXJ3EM.js";
|
|
8
12
|
import {
|
|
9
13
|
hasRealScores,
|
|
10
14
|
lighthouseScoresFromResult
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-X56LAKPR.js";
|
|
12
16
|
import "./chunk-WQLNUEZL.js";
|
|
13
17
|
import "./chunk-5L3FPQIJ.js";
|
|
14
18
|
import {
|
|
15
19
|
resolveSites
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-S2PLX4XK.js";
|
|
17
21
|
import {
|
|
22
|
+
createReportDraft,
|
|
23
|
+
findReportForPeriod,
|
|
18
24
|
queueDraft
|
|
19
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UPE6D4AT.js";
|
|
26
|
+
import "./chunk-TJLKWQOX.js";
|
|
20
27
|
import {
|
|
21
|
-
HARNESS_JSON_RELATIVE,
|
|
22
|
-
UNGUARDED_TWIN_TELL
|
|
23
|
-
} from "./chunk-Y6FCXRIC.js";
|
|
24
|
-
import "./chunk-4UTJ4G6P.js";
|
|
25
|
-
import {
|
|
26
|
-
createDraft,
|
|
27
|
-
findReportByPeriod,
|
|
28
28
|
updateReportScores
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-TEMQPSCM.js";
|
|
30
|
+
import "./chunk-ASLDCWBF.js";
|
|
30
31
|
import "./chunk-VAD43MOR.js";
|
|
31
32
|
import {
|
|
32
33
|
uploadAttachment
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-YJBSB4LP.js";
|
|
34
35
|
import {
|
|
35
36
|
openBase,
|
|
36
37
|
readAirtableConfig
|
|
@@ -40,19 +41,20 @@ import {
|
|
|
40
41
|
resolveCopy
|
|
41
42
|
} from "./chunk-KJGKZLCT.js";
|
|
42
43
|
import "./chunk-BGTYPVLT.js";
|
|
44
|
+
import "./chunk-TC4GANMX.js";
|
|
43
45
|
import {
|
|
44
|
-
listWebsites,
|
|
45
46
|
siteSlug
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
48
|
+
import "./chunk-RQJFGK3A.js";
|
|
47
49
|
import "./chunk-SLFG7Y76.js";
|
|
48
50
|
import {
|
|
49
51
|
runAudits
|
|
50
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-DXZRWW2F.js";
|
|
51
53
|
import "./chunk-LKGVSM2O.js";
|
|
52
|
-
import "./chunk-
|
|
54
|
+
import "./chunk-MVKNU4QD.js";
|
|
53
55
|
import "./chunk-43SW3FGW.js";
|
|
54
56
|
import "./chunk-Z3JXLULD.js";
|
|
55
|
-
import "./chunk-
|
|
57
|
+
import "./chunk-VCPTOZ56.js";
|
|
56
58
|
import "./chunk-UDRHAQT7.js";
|
|
57
59
|
import "./chunk-PTDMVGKL.js";
|
|
58
60
|
import "./chunk-LBYOLBW7.js";
|
|
@@ -62,7 +64,7 @@ import "./chunk-RARREDQE.js";
|
|
|
62
64
|
import {
|
|
63
65
|
siteLabel
|
|
64
66
|
} from "./chunk-XXTZBPUY.js";
|
|
65
|
-
import "./chunk-
|
|
67
|
+
import "./chunk-WMF5S2MB.js";
|
|
66
68
|
import "./chunk-6VAL7XJT.js";
|
|
67
69
|
import "./chunk-NMAWXBJA.js";
|
|
68
70
|
import "./chunk-CXICCLNM.js";
|
|
@@ -368,7 +370,7 @@ var defaultProbe = async (url) => {
|
|
|
368
370
|
});
|
|
369
371
|
return { status: res.status, body: await res.text() };
|
|
370
372
|
};
|
|
371
|
-
async function launch(site, deps
|
|
373
|
+
async function launch(site, deps) {
|
|
372
374
|
const label = siteLabel(site);
|
|
373
375
|
const bootstrap = deps.bootstrap ?? selfUpdating;
|
|
374
376
|
const audit = deps.audit ?? runAudits;
|
|
@@ -413,12 +415,12 @@ async function launch(site, deps = {}) {
|
|
|
413
415
|
bestPractices: rawScores.bestPractices ?? 0,
|
|
414
416
|
seo: rawScores.seo ?? 0
|
|
415
417
|
};
|
|
416
|
-
const websites = await
|
|
418
|
+
const websites = await deps.roster();
|
|
417
419
|
const target = websites.find((w) => siteSlug(w.name) === siteSlug(label));
|
|
418
420
|
if (!target) {
|
|
419
421
|
steps.push({
|
|
420
422
|
name: "audit",
|
|
421
|
-
result: { kind: "error", message: `no
|
|
423
|
+
result: { kind: "error", message: `no site row matched site "${label}"` }
|
|
422
424
|
});
|
|
423
425
|
return stop();
|
|
424
426
|
}
|
|
@@ -493,10 +495,10 @@ async function launch(site, deps = {}) {
|
|
|
493
495
|
const slug = siteSlug(target.name);
|
|
494
496
|
let report;
|
|
495
497
|
try {
|
|
496
|
-
const existing = await
|
|
498
|
+
const existing = await findReportForPeriod(deps.reportMirror, target.id, "Launch", period);
|
|
497
499
|
if (existing) {
|
|
498
500
|
await updateReportScores(base, existing.id, scores, today);
|
|
499
|
-
await deps.reportMirror
|
|
501
|
+
await deps.reportMirror.patch(existing.id, {
|
|
500
502
|
lighthouse_performance: scores.performance,
|
|
501
503
|
lighthouse_accessibility: scores.accessibility,
|
|
502
504
|
lighthouse_best_practices: scores.bestPractices,
|
|
@@ -505,11 +507,9 @@ async function launch(site, deps = {}) {
|
|
|
505
507
|
});
|
|
506
508
|
report = existing;
|
|
507
509
|
} else {
|
|
508
|
-
report = await
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
deps.reportMirror?.created
|
|
512
|
-
);
|
|
510
|
+
report = await createReportDraft(draftInputFor(target, scores, today, period), {
|
|
511
|
+
create: deps.reportMirror.create
|
|
512
|
+
});
|
|
513
513
|
}
|
|
514
514
|
} catch (err) {
|
|
515
515
|
steps.push({ name: "draft", result: errorOf(err) });
|
|
@@ -535,7 +535,7 @@ async function launch(site, deps = {}) {
|
|
|
535
535
|
`${slug}-${today.toISOString().slice(0, 10)}.html`,
|
|
536
536
|
"text/html"
|
|
537
537
|
);
|
|
538
|
-
await deps.reportMirror
|
|
538
|
+
await deps.reportMirror.body(report.id, html);
|
|
539
539
|
} catch (uploadErr) {
|
|
540
540
|
console.warn(
|
|
541
541
|
`\u26A0 Launch preview upload skipped for ${target.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
@@ -602,9 +602,11 @@ async function runLaunchCommand(site, opts) {
|
|
|
602
602
|
if (!target) {
|
|
603
603
|
return { output: `No site resolved for "${site}".`, code: 1 };
|
|
604
604
|
}
|
|
605
|
-
const { makeReportMirror } = await import("./report-mirror-
|
|
606
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
605
|
+
const { makeReportMirror } = await import("./report-mirror-O6IDFDWU.js");
|
|
606
|
+
const { makeSiteMirror } = await import("./site-mirror-TKTDWYP3.js");
|
|
607
|
+
const { readFleetRoster } = await import("./roster-EJ3ILR52.js");
|
|
607
608
|
const result = await launch(target, {
|
|
609
|
+
roster: () => readFleetRoster(),
|
|
608
610
|
reportMirror: await makeReportMirror(),
|
|
609
611
|
siteMirror: await makeSiteMirror()
|
|
610
612
|
});
|
|
@@ -614,4 +616,4 @@ export {
|
|
|
614
616
|
formatStep,
|
|
615
617
|
runLaunchCommand
|
|
616
618
|
};
|
|
617
|
-
//# sourceMappingURL=launch-
|
|
619
|
+
//# sourceMappingURL=launch-AJY3TGTQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/launch.ts","../src/recipes/launch.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { launch, type LaunchResult, type LaunchStepResult } from \"../../recipes/launch.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\n\nexport type LaunchCommandOptions = {\n cwd?: string;\n};\n\nexport function formatStep(name: string, r: LaunchStepResult): string {\n if (r.kind === \"error\") return `${name.padEnd(20)} error: ${r.message}`;\n if (r.kind === \"audit\") {\n const s = r.scores;\n return `${name.padEnd(20)} audited (P=${s.performance} A=${s.accessibility} BP=${s.bestPractices} SEO=${s.seo})`;\n }\n if (r.kind === \"draft\") {\n return `${name.padEnd(20)} drafted ${r.report.reportId}`;\n }\n if (r.kind === \"probe\") return `${name.padEnd(20)} ok — ${r.message}`;\n const rec = r.result;\n if (rec.status === \"noop\") return `${name.padEnd(20)} noop${rec.notes ? ` — ${rec.notes}` : \"\"}`;\n if (rec.status === \"failed\")\n return `${name.padEnd(20)} failed${rec.notes ? ` — ${rec.notes}` : \"\"}`;\n return `${name.padEnd(20)} applied (${rec.commits.length} commit${rec.commits.length === 1 ? \"\" : \"s\"})${rec.notes ? ` — ${rec.notes}` : \"\"}`;\n}\n\nfunction formatResult(r: LaunchResult): string {\n const header = `[${r.site}] launch — ${r.complete ? \"drafted (awaiting approval)\" : \"STOPPED\"}`;\n const body = r.steps.map((s) => formatStep(s.name, s.result)).join(\"\\n\");\n return `${header}\\n${body}`;\n}\n\n/**\n * `launch <site>` — single-site only. Bootstrap (Renovate + protection), first-audit\n * the site, and DRAFT its launch email into the M3 approve queue. Never sends;\n * the operator approves the draft and the next send run delivers the go-live\n * email (flipping Status → maintenance + stamping Launched at).\n */\nexport async function runLaunchCommand(\n site: string,\n opts: LaunchCommandOptions,\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n const sites = await resolveSites({ site, cwd });\n const target = sites[0];\n if (!target) {\n return { output: `No site resolved for \"${site}\".`, code: 1 };\n }\n\n // #539 Phase 5: create-side Turso dual-write, wired at the composition root\n // (see runAnnounceCommand for why it is not defaulted inside the recipe).\n const { makeReportMirror } = await import(\"../../reports/report-mirror.js\");\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n // #646 step 4: the fleet roster comes from Turso — an Airtable roster cannot\n // see a `site_<ULID>` site, so launching one failed at \"no site row matched\".\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n const result = await launch(target, {\n roster: () => readFleetRoster(),\n reportMirror: await makeReportMirror(),\n siteMirror: await makeSiteMirror(),\n });\n return { output: formatResult(result), code: result.complete ? 0 : 1 };\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult, RecipeResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { selfUpdating } from \"./self-updating/index.js\";\nimport { HARNESS_JSON_RELATIVE, UNGUARDED_TWIN_TELL } from \"./match-harness/template.js\";\nimport { runAudits } from \"../audits/index.js\";\nimport { hasRealScores, lighthouseScoresFromResult } from \"../audits/lighthouse-airtable.js\";\nimport { writeAuditsToAirtable } from \"../audits/write-audits-to-airtable.js\";\nimport { openBase, readAirtableConfig } from \"../reports/airtable/client.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { siteSlug } from \"../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { updateReportScores } from \"../reports/airtable/reports.js\";\nimport { createReportDraft, findReportForPeriod } from \"../reports/create-report.js\";\nimport type { DraftInput } from \"../reports/draft-fields.js\";\nimport type { ReportRow } from \"../reports/airtable/reports.js\";\nimport type { ReportMirror } from \"../reports/report-mirror.js\";\nimport type { SiteMirror } from \"../db/site-mirror.js\";\nimport { queueDraft } from \"../reports/queue.js\";\nimport { uploadAttachment } from \"../reports/airtable/attachments.js\";\nimport { renderReportHtml } from \"../reports/render.js\";\nimport { resolveCopy } from \"../reports/copy.js\";\nimport type { LighthouseScores } from \"../reports/types.js\";\n\nexport type LaunchStepResult =\n | { kind: \"recipe\"; result: RecipeResult }\n | { kind: \"audit\"; results: AuditResult[]; scores: LighthouseScores }\n | { kind: \"draft\"; report: ReportRow }\n | { kind: \"probe\"; message: string }\n | { kind: \"error\"; message: string };\n\nexport type LaunchResult = {\n site: string;\n steps: Array<{ name: string; result: LaunchStepResult }>;\n /** True if every step ran (bootstrap + audit + draft); false if a step\n * errored or a recipe `failed` short-circuited the chain. */\n complete: boolean;\n};\n\nexport type LaunchDeps = {\n /** Bootstrap step (Renovate + protection; ci.yml comes from the starter). Defaults to the real `selfUpdating`. */\n bootstrap?: (site: Site) => Promise<RecipeResult>;\n /** Audit step. Defaults to the real `runAudits`. */\n audit?: (site: Site) => Promise<AuditResult[]>;\n /** Airtable handle. Defaults to opening the live base from credentials. */\n base?: AirtableBase;\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 launching one\n * failed at \"no Websites row matched\". Required, not defaulted: the unit\n * suite calls `launch` with a fake base and must not open a real libSQL\n * handle. The CLI composition root wires `readFleetRoster`. */\n roster: () => Promise<WebsiteRow[]>;\n /** #539 Phase 5, and since #646 step 4 the store that MINTS and holds the\n * Launch row: the row itself (or a re-run's refreshed scores), the rendered\n * body, and the queue flag. Required for the same reason `roster` is. */\n reportMirror: ReportMirror;\n /** #539 Phase 5: the Websites-row twin — launch writes the site's FIRST audit\n * results and (on send) its launched status. Injected at the CLI root. */\n siteMirror?: SiteMirror;\n /** HTTP probe for `dev-guard`. Defaults to global fetch. INJECTED in tests:\n * the suite stubs `global.fetch` for the Airtable attachment upload and that\n * stub answers 200 to everything — which is precisely the state this step\n * exists to fail on. */\n probe?: (url: string) => Promise<{ status: number; body: string }>;\n};\n\n/** The site's OWN 404. `src/routes/+error.svelte` renders `<h1>{page.status}</h1>`,\n * so a real SvelteKit 404 from this app carries an `<h1>404</h1>`; a parked\n * domain, a CDN 404 and a dead host do not. \"The route is gone\" and \"the site\n * is gone\" must not look the same to this gate. */\nconst SITE_404_MARKER = /<h1[^>]*>\\s*404\\s*<\\/h1>/i;\n\n/** The UNGUARDED twin's OWN refusal. `src/routes/dev/match/[uid]/+page.server.ts`\n * 404s for any uid absent from its assembly map — and that message renders\n * through the very same `+error.svelte` the guard's 404 uses, so it carries an\n * `<h1>404</h1>` too. On a site whose uid set lacks \"home\", a LIVE twin and a\n * guarded one are byte-indistinguishable to SITE_404_MARKER, and the check\n * could never fail. Verified on disk: beachfront-dentistry's copy of that route\n * imports `$app/environment` zero times.\n *\n * The contract is the MACHINE tell `UNGUARDED_TWIN_TELL`, imported from the\n * template that installs the route rather than re-typed here: the installed\n * twin puts it ahead of its human-readable message. Matching the prose was\n * the defect of #719 — reword the message (\"no document for\", \"unknown uid\")\n * and the guard silently stopped denying, i.e. failed OPEN with fixtures\n * public. The two prose wordings are STILL accepted, on purpose and for now:\n * beachfront-dentistry's on-disk twin predates the tell and says \"no matching\n * assembly for\", and any site installed before the tell says \"no assembly\n * for\". Drop the prose alternates once beachfront's twin has been\n * re-installed from the harness; until then they only ever widen the deny.\n *\n * This is a DENY clause and only ever a deny — it can refuse a green, never\n * grant one. Widening it can only ever refuse a launch that would otherwise\n * have proceeded, so widening is always the safe direction here. */\nexport const UNGUARDED_TWIN_MARKER = new RegExp(\n `${UNGUARDED_TWIN_TELL.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}|no (matching )?assembly for`,\n \"i\",\n);\n\nconst MATCH_ROUTE_DIR = \"src/routes/dev/match\";\n\n/** Positive evidence that `sitePath` is a SvelteKit checkout at all. Without it,\n * the absence of MATCH_ROUTE_DIR is evidence of nothing: a path never cloned, a\n * stale clone, a mistyped site name and the wrong working directory all produce\n * exactly the same `false`, and `resolveSites` builds the path with\n * `localPath(resolve(cwd, site))` and no existence check of its own. This is\n * the filesystem twin of the `/health` control on the deployed gate — an absent\n * error must never be allowed to grant a green. */\nconst CHECKOUT_MARKER = \"src/routes\";\n\n/** Tokens after which a `/` opens a REGEX literal rather than a division. This\n * is the usual JS ambiguity, answered by lookback at the last significant\n * character — a scanner's answer, not a parser's.\n *\n * `)` is deliberately NOT here. `if (x) /re/.test(y)` is legal and is read as\n * division, which costs nothing (the `/` is copied through unchanged, exactly\n * as before this function existed) and keeps `(a + b) / 2` right, which is far\n * commoner in that position. That is the residual: a regex literal opening\n * directly after `)` AND containing a quote falls back to the old behaviour and\n * therefore DENIES. Deny is the safe direction here — see `carriesGuard`. */\nconst REGEX_PRECEDERS = /[(,=:[!&|?{};+\\-*%~^<>]/;\nconst REGEX_KEYWORDS = new Set([\n \"return\",\n \"typeof\",\n \"case\",\n \"in\",\n \"of\",\n \"new\",\n \"delete\",\n \"void\",\n \"instanceof\",\n \"do\",\n \"else\",\n \"yield\",\n \"await\",\n]);\n\nfunction opensRegex(prevChar: string, prevWord: string): boolean {\n if (prevChar === \"\") return true;\n if (REGEX_PRECEDERS.test(prevChar)) return true;\n return REGEX_KEYWORDS.has(prevWord);\n}\n\n/** The last significant character emitted so far, and the identifier ending at\n * it. Both scanners look back over their own OUTPUT, which is why masking is\n * safe to interleave: masked bodies are spaces and the delimiters survive, so\n * the structural lookback reads the same either way. */\nfunction lookback(out: string): { char: string; word: string } {\n let i = out.length - 1;\n while (i >= 0 && /\\s/.test(out[i]!)) i--;\n if (i < 0) return { char: \"\", word: \"\" };\n let j = i;\n while (j >= 0 && /[A-Za-z0-9_$]/.test(out[j]!)) j--;\n return { char: out[i]!, word: out.slice(j + 1, i + 1) };\n}\n\n/** Index of a regex literal's CLOSING `/`, or -1 if `source[at]` does not in\n * fact open one. `[...]` character classes are tracked because a `/` inside one\n * does not close the literal (`/[a-z/]/`). A newline or an unterminated run\n * means this was never a regex, and the caller then treats the `/` as an\n * ordinary character — the behaviour that predates regex-awareness. */\nfunction regexLiteralEnd(source: string, at: number): number {\n let inClass = false;\n for (let i = at + 1; i < source.length; i++) {\n const c = source[i]!;\n if (c === \"\\\\\") {\n i++;\n continue;\n }\n if (c === \"\\n\") return -1;\n if (inClass) {\n if (c === \"]\") inClass = false;\n continue;\n }\n if (c === \"[\") {\n inClass = true;\n continue;\n }\n if (c === \"/\") return i;\n }\n return -1;\n}\n\n/** Strip line and block comments, so the COMMON commented-out guard cannot\n * satisfy the match — an import left in place above `// if (!dev) error(404);`\n * is the likeliest real-world state, and it used to pass. String and template\n * literals are skipped so a `//` inside a URL is not read as a comment.\n *\n * Regex literals are recognised (#726). Without that, a quote character inside\n * one — `/[a-z0-9']+/` — opened a string that never closed, and everything to\n * the next matching quote was preserved as if it were string content. On a\n * CORRECTLY guarded file that swallowed the live `if (!dev) error(404)` and the\n * twin read as unguarded, so a launch was blocked on a site that was fine. The\n * literal is now copied through verbatim, which is what a comment stripper\n * should do with one. */\nfunction stripComments(source: string): string {\n let out = \"\";\n for (let i = 0; i < source.length;) {\n const ch = source[i]!;\n const next = source[i + 1];\n if (ch === \"/\" && next === \"/\") {\n while (i < source.length && source[i] !== \"\\n\") i++;\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n i += 2;\n while (i < source.length && !(source[i] === \"*\" && source[i + 1] === \"/\")) i++;\n i += 2;\n continue;\n }\n if (ch === \"/\") {\n const { char, word } = lookback(out);\n if (opensRegex(char, word)) {\n const end = regexLiteralEnd(source, i);\n if (end !== -1) {\n out += source.slice(i, end + 1);\n i = end + 1;\n continue;\n }\n }\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n out += ch;\n i++;\n while (i < source.length) {\n if (source[i] === \"\\\\\") {\n out += source.slice(i, i + 2);\n i += 2;\n continue;\n }\n out += source[i]!;\n i++;\n if (source[i - 1] === ch) break;\n }\n continue;\n }\n out += ch;\n i++;\n }\n return out;\n}\n\n/** Blank the CONTENTS of string and template literals, keeping the quote\n * characters, the newlines and the overall length. Only the guard scan uses\n * this — never the `$app/environment` check, whose whole subject is an import\n * specifier and therefore is itself a string literal.\n *\n * It exists because `if (!dev) console.warn(\"this should throw error(404)\")`\n * read as a refusal. Masking can only ever REMOVE candidate refusal tokens, so\n * it can only ever deny.\n * Regex BODIES are masked too, for the same reason and with the same effect: a\n * regex is data, so `if (!dev) RE = /throw/` must not be read as a refusal. It\n * was, before #726. */\nfunction maskLiterals(code: string): string {\n let out = \"\";\n for (let i = 0; i < code.length;) {\n const ch = code[i]!;\n if (ch === \"/\") {\n const { char, word } = lookback(out);\n if (opensRegex(char, word)) {\n const end = regexLiteralEnd(code, i);\n if (end !== -1) {\n // Delimiters and length kept, body blanked — exactly the string rule.\n out += \"/\" + \" \".repeat(end - i - 1) + \"/\";\n i = end + 1;\n continue;\n }\n }\n }\n if (ch !== '\"' && ch !== \"'\" && ch !== \"`\") {\n out += ch;\n i++;\n continue;\n }\n out += ch;\n i++;\n while (i < code.length) {\n const c = code[i]!;\n if (c === \"\\\\\") {\n out += \" \";\n i += 2;\n continue;\n }\n i++;\n if (c === ch) {\n out += c;\n break;\n }\n out += c === \"\\n\" ? \"\\n\" : \" \";\n }\n }\n return out;\n}\n\n/** The statement or block the `if (...)` ending at `from` actually governs: a\n * braced block, or the single statement up to its terminating `;` — or, under\n * ASI, the end of its line. Bracket depth is tracked so\n * `error(404, {\\n message: \"…\"\\n});` is ONE statement rather than three\n * fragments. Expects `code` to be comment-stripped and literal-masked, so every\n * bracket it sees is a real one. */\nfunction consequentAt(code: string, from: number): string {\n let i = from;\n while (i < code.length && /\\s/.test(code[i]!)) i++;\n const start = i;\n if (code[i] === \"{\") {\n let depth = 0;\n for (; i < code.length; i++) {\n if (code[i] === \"{\") depth++;\n else if (code[i] === \"}\" && --depth === 0) return code.slice(start, i + 1);\n }\n return code.slice(start);\n }\n let depth = 0;\n for (; i < code.length; i++) {\n const ch = code[i]!;\n if (ch === \"(\" || ch === \"[\" || ch === \"{\") depth++;\n else if (ch === \")\" || ch === \"]\" || ch === \"}\") {\n if (depth === 0) return code.slice(start, i);\n depth--;\n } else if (depth === 0 && (ch === \";\" || ch === \"\\n\")) return code.slice(start, i);\n }\n return code.slice(start);\n}\n\n/** An actual refusal: SvelteKit's `error(404, …)`, or any `throw`. */\nconst REFUSAL = /\\berror\\s*\\(\\s*404|\\bthrow\\b/;\n\n/** A guard is `$app/environment` + an `if (!dev)` **whose own branch refuses**.\n *\n * The three conditions used to be ANDed across the WHOLE file, with nothing\n * tying the refusal to the `if`. Two shapes passed that should not have: a\n * warn-only `if (!dev)` alongside the route's own unrelated `throw` (the exact\n * half-fix this comment used to claim was closed), and a refusal occurring only\n * inside a string. Reading the guard's consequent, on literal-masked source, is\n * strictly narrower than the old whole-file grep — every input it rejects, the\n * old predicate also had to reject on `$app/environment` or `if (!dev)`, or was\n * a false pass. It can therefore only deny, never grant.\n *\n * It is a contract with plan BC's `ROUTE_SERVER` template, which BC installs\n * and this pre-flight must accept; tests/recipes/launch.test.ts lifts that\n * template out of the plan and runs this predicate over it rather than\n * restating the pattern. */\nfunction carriesGuard(source: string): boolean {\n const code = stripComments(source);\n if (!code.includes(\"$app/environment\")) return false;\n const masked = maskLiterals(code);\n const guards = /if\\s*\\(\\s*!\\s*dev\\s*\\)/g;\n for (let m = guards.exec(masked); m; m = guards.exec(masked)) {\n if (REFUSAL.test(consequentAt(masked, m.index + m[0].length))) return true;\n }\n return false;\n}\n\nconst DEV_ROUTE_DIR = \"src/routes/dev\";\n\n/** Every SvelteKit route file at or below `relDir`, site-root-relative. The\n * gates used to read a FIXED list of three paths, which is why a `+server.ts`\n * endpoint and a `/dev/match/frozen` sibling were inspected by neither of them\n * (#723). What exists under the twin is a question for the filesystem, not for\n * a constant. */\nasync function routeFilesUnder(sitePath: string, relDir: string): Promise<string[]> {\n let entries;\n try {\n entries = await readdir(join(sitePath, relDir), { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const e of entries) {\n const rel = `${relDir}/${e.name}`;\n if (e.isDirectory()) out.push(...(await routeFilesUnder(sitePath, rel)));\n else if (e.name.startsWith(\"+\")) out.push(rel);\n }\n return out;\n}\n\n/** `carriesGuard` for one file, memoised — a layout is asked about once per page\n * directory below it. An unreadable file is not a guard. */\nasync function guardAt(\n sitePath: string,\n rel: string,\n cache: Map<string, boolean>,\n): Promise<boolean> {\n const hit = cache.get(rel);\n if (hit !== undefined) return hit;\n let ok: boolean;\n try {\n ok = carriesGuard(await readFile(join(sitePath, rel), \"utf-8\"));\n } catch {\n ok = false;\n }\n cache.set(rel, ok);\n return ok;\n}\n\n/** The nearest guarded `+layout.server.ts` at or above `dir`, up to and\n * including `src/routes/dev`, or null.\n *\n * `src/routes/dev/+layout.server.ts` is the STRONGEST placement and the\n * fleet-wide class-fix for \"dev routes ship in production\" (#717): one `load`\n * doing `if (!dev) error(404)` runs for every `/dev/*` page, `/dev/match/[uid]`\n * included. An earlier version read only inside `/dev/match` and so reported\n * such a site as \"the matching twin would ship\" — untrue, and it penalised the\n * better fix. It also contradicted the deployed half of this feature, which was\n * deliberately redesigned to survive that class-fix: see the `/health` comment\n * at the dev-guard step for why the 200 control moved off `/dev/a11y-fixtures`.\n *\n * Layouts cover PAGES only. A `+server.ts` endpoint runs no layout `load` at\n * all, which is why `matchingDisposition` checks endpoints separately and never\n * lets a layout vouch for one (#723). */\nasync function coveringLayout(\n sitePath: string,\n dir: string,\n cache: Map<string, boolean>,\n): Promise<string | null> {\n let d = dir;\n for (;;) {\n const rel = `${d}/+layout.server.ts`;\n if (await guardAt(sitePath, rel, cache)) return rel;\n if (d === DEV_ROUTE_DIR) return null;\n const up = d.slice(0, d.lastIndexOf(\"/\"));\n if (up.length < DEV_ROUTE_DIR.length) return null;\n d = up;\n }\n}\n\n/**\n * Pre-flight: a checkout that still carries the matching twin must carry the\n * dev guard the `match-harness` recipe installs with it. Nothing is deleted at\n * launch — the harness stays for the next round — so the guard is the whole\n * mechanism keeping `/dev/match/*` off the production build.\n *\n * Filesystem only, and it runs BEFORE any GitHub write: a site that will fail\n * the deployed check should not first acquire branch protection and an audit.\n *\n * Liveness first. `src/routes` has to be there before a missing\n * `src/routes/dev/match` may be read as \"this site has no twin\" — otherwise the\n * pass is granted by an absence, and four separate operator errors produce that\n * same absence. See CHECKOUT_MARKER.\n */\nexport async function matchingDisposition(\n sitePath: string,\n): Promise<{ ok: boolean; message: string }> {\n if (!existsSync(join(sitePath, CHECKOUT_MARKER))) {\n return {\n ok: false,\n message: `${sitePath} has no ${CHECKOUT_MARKER} — this is not a SvelteKit checkout (never cloned, a stale clone, a mistyped site name, or the wrong working directory), so the twin's disposition cannot be established`,\n };\n }\n if (!existsSync(join(sitePath, MATCH_ROUTE_DIR))) {\n return {\n ok: true,\n message: `no ${MATCH_ROUTE_DIR} in this checkout (${CHECKOUT_MARKER} is present, so the absence is a real one)`,\n };\n }\n const files = await routeFilesUnder(sitePath, MATCH_ROUTE_DIR);\n if (files.length === 0) {\n return {\n ok: false,\n message: `${MATCH_ROUTE_DIR} exists but carries no route files (+page/+server/+layout) — the twin's disposition cannot be established`,\n };\n }\n\n const cache = new Map<string, boolean>();\n const unguarded: string[] = [];\n const covers: string[] = [];\n\n // Endpoints FIRST, and never covered by a layout: SvelteKit layout `load` does\n // not run for `+server.ts`, so the #717 class-fix at src/routes/dev that\n // legitimately covers every page leaves an endpoint fully live (#723).\n for (const ep of files.filter((f) => f.endsWith(\"/+server.ts\"))) {\n if (await guardAt(sitePath, ep, cache)) covers.push(ep);\n else\n unguarded.push(\n `${ep} (a \\`+server.ts\\` endpoint — layout \\`load\\` does not run for it, so it needs its own guard)`,\n );\n }\n\n // Then every directory holding a page, each covered by its own guarded\n // +page.server.ts or by a +layout.server.ts at or above it.\n const pageDirs = [\n ...new Set(\n files\n .filter((f) => /\\/\\+page\\.(server\\.ts|ts|svelte)$/.test(f))\n .map((f) => f.slice(0, f.lastIndexOf(\"/\"))),\n ),\n ];\n for (const dir of pageDirs) {\n const layout = await coveringLayout(sitePath, dir, cache);\n if (layout) {\n covers.push(layout);\n continue;\n }\n const own = `${dir}/+page.server.ts`;\n if (await guardAt(sitePath, own, cache)) {\n covers.push(own);\n continue;\n }\n unguarded.push(`${dir}/ (no guarded +page.server.ts and no covering +layout.server.ts)`);\n }\n\n if (unguarded.length > 0) {\n return {\n ok: false,\n message: `${MATCH_ROUTE_DIR} is not fully guarded — ${unguarded.join(\"; \")} — an \\`if (!dev)\\` guard on \\`$app/environment\\` whose own branch refuses (\\`error(404\\` or \\`throw\\`) is required, once comments are stripped and string and regex contents masked; the matching twin would ship`,\n };\n }\n // Deliberately NOT \"the twin is guarded in production\". This function reads\n // source text on disk; it cannot observe the deployed build, and a field that\n // can only see configuration must not be named after the thing it cannot see.\n // The deployed behaviour is the `dev-guard` step's job.\n return {\n ok: true,\n message: `every route under ${MATCH_ROUTE_DIR} is covered by dev guard source — ${[...new Set(covers)].join(\", \")} (source text only; the deployed build is checked at the dev-guard step)`,\n };\n}\n\n/** The uids whose `/dev/match` twin must be proved dead, read from the harness's\n * OWN page table — the file `match-harness` installs and every gate already\n * reads — so the gate and the harness cannot disagree about which pages exist.\n * Probing a hardcoded \"home\" left every sibling uid unprobed (#723).\n *\n * A page whose `uid` is null has no twin (harness.mjs says so) and is skipped.\n * An absent or unparseable harness.json falls back to \"home\": not every\n * launched site carries the harness, so a missing file must not become a\n * refusal — and must not yield an EMPTY list either, which would prove nothing\n * while looking like a pass. */\nasync function twinUids(sitePath: string): Promise<string[]> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(join(sitePath, HARNESS_JSON_RELATIVE), \"utf-8\"));\n } catch {\n return [\"home\"];\n }\n const pages = (parsed as { pages?: Record<string, { uid?: unknown } | null> })?.pages;\n if (!pages || typeof pages !== \"object\") return [\"home\"];\n const uids = Object.values(pages)\n .map((p) => p?.uid)\n .filter((u): u is string => typeof u === \"string\" && u.length > 0);\n return uids.length > 0 ? [...new Set(uids)] : [\"home\"];\n}\n\n/** Undici's defaults are 300s headers + 300s body, and the body timeout is an\n * INACTIVITY timer — so a trickling origin can hold this open for ~10 minutes,\n * AFTER the GitHub writes and a full Lighthouse audit have already run.\n *\n * 15s is NOT the house number, and this comment used to imply it was by citing\n * `audits/function-health.ts`, which is 10s. The 15s call sites are\n * `audits/netlify-deploy.ts`, `github/gh-rest.ts`, `prismic/models/remote.ts`\n * and the inline ones in `prospect/pipeline.ts` and `prospect/http-probes.ts`;\n * `audits/function-health.ts`, `audits/form-e2e.ts`, `audits/browser.ts` and\n * `prospect/dns.ts` are 10s, `prospect/ownership.ts` 12s, `prospect/crawl.ts`\n * 20s. Each is set for its own workload — see the note on PRISMIC_TIMEOUT_MS.\n * 15s here is a bound on a two-request pre-send check, not a convention.\n *\n * An abort rejects, and dev-guard treats a rejection as a refusal, which is the\n * correct reading: an origin that will not answer has proved nothing. */\nconst PROBE_TIMEOUT_MS = 15_000;\n\nconst defaultProbe = async (url: string): Promise<{ status: number; body: string }> => {\n const res = await fetch(url, {\n redirect: \"follow\",\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n return { status: res.status, body: await res.text() };\n};\n\n/**\n * Launch a site: bootstrap → first-audit → DRAFT a launch email. The M3\n * approve loop is what actually sends; `launch` never sends — it stops at a\n * dashboard-queued draft (`reportType: \"Launch\"`) carrying the just-audited\n * Lighthouse scores, so `sendOne`'s `report.lighthouse` guard passes.\n *\n * EXECUTION order, stopping on the first error or `failed` recipe:\n * 0. matchingDisposition — filesystem pre-flight, BEFORE any GitHub write.\n * 1. selfUpdating — Renovate + protection (platform auto-merge OFF; ci.yml is the starter's).\n * 2. audit — the Lighthouse scores that feed the draft are collected here.\n * 3. the Websites-row lookup, which supplies the url the next step probes.\n * 3b. dev-guard — the same twin, checked against the DEPLOYED url. It sits\n * BETWEEN collecting the scores and writing them, so a site that fails it\n * has been audited but leaves its Websites row untouched.\n * 4. writeAuditsToAirtable — the `audit --write-back` writer.\n * 5. createDraft — reportType \"Launch\", today's period, the audited scores.\n *\n * The REPORTED chain is deliberately not that order. The `audit` step is only\n * pushed once its write succeeds (:261), so the emitted steps read\n * `matching-disposition -> self-updating -> dev-guard -> audit -> draft`, which\n * is what the CLI prints and what tests/recipes/launch.test.ts asserts. Do not\n * renumber either list to match the other: one says when work happens, the\n * other says what has been proved. Only step 0 precedes a GitHub write —\n * dev-guard cannot, because it needs the row from step 3 to know the url.\n */\nexport async function launch(site: Site, deps: LaunchDeps): Promise<LaunchResult> {\n const label = siteLabel(site);\n const bootstrap = deps.bootstrap ?? selfUpdating;\n const audit = deps.audit ?? runAudits;\n const base = deps.base ?? openBase(readAirtableConfig());\n const probe = deps.probe ?? defaultProbe;\n\n const steps: Array<{ name: string; result: LaunchStepResult }> = [];\n const stop = (): LaunchResult => ({ site: label, steps, complete: false });\n\n // 0. Matching disposition — filesystem, before any GitHub write.\n const disposition = await matchingDisposition(site.path);\n steps.push({\n name: \"matching-disposition\",\n result: disposition.ok\n ? { kind: \"probe\", message: disposition.message }\n : { kind: \"error\", message: disposition.message },\n });\n if (!disposition.ok) return stop();\n\n // 1. Bootstrap.\n let recipe: RecipeResult;\n try {\n recipe = await bootstrap(site);\n } catch (err) {\n steps.push({ name: \"self-updating\", result: errorOf(err) });\n return stop();\n }\n steps.push({ name: \"self-updating\", result: { kind: \"recipe\", result: recipe } });\n if (recipe.status === \"failed\") return stop();\n\n // 2. Audit + write scores back to Airtable.\n let results: AuditResult[];\n try {\n results = await audit(site);\n } catch (err) {\n steps.push({ name: \"audit\", result: errorOf(err) });\n return stop();\n }\n const lhResult = results.find((r) => r.audit === \"lighthouse\");\n if (!lhResult || !hasRealScores(lhResult)) {\n steps.push({\n name: \"audit\",\n result: { kind: \"error\", message: \"lighthouse audit produced no real scores\" },\n });\n return stop();\n }\n // The launch announcement renders a numeric score per category; a metric that\n // errored this run (now null from lighthouseScoresFromResult) keeps the prior\n // 0 behavior here rather than propagating null into the launch-email path. The\n // Airtable write path (write-audits-to-airtable) keeps the null → shows \"—\".\n const rawScores = lighthouseScoresFromResult(lhResult);\n const scores: LighthouseScores = {\n performance: rawScores.performance ?? 0,\n accessibility: rawScores.accessibility ?? 0,\n bestPractices: rawScores.bestPractices ?? 0,\n seo: rawScores.seo ?? 0,\n };\n\n const websites = await deps.roster();\n const target = websites.find((w) => siteSlug(w.name) === siteSlug(label));\n if (!target) {\n steps.push({\n name: \"audit\",\n result: { kind: \"error\", message: `no site row matched site \"${label}\"` },\n });\n return stop();\n }\n\n // 2b. The dev guard, on the DEPLOYED build. Two halves, both required:\n // /dev/match/home must 404 WITH this site's own error page, and /health\n // must answer 200. Without the second, a dead host, a wrong url and a\n // parked domain all \"pass\" the first — an absent error granting a green,\n // which is the one shape this fleet does not allow. Reading the deployed\n // url makes this a production-build check by construction; no local build\n // can substitute.\n //\n // The control is /health, NOT a /dev/* route. It used to be\n // /dev/a11y-fixtures, which made this gate require an unguarded dev page\n // to be publicly reachable in production — so the obvious class-fix for\n // \"dev routes ship in production\" (one src/routes/dev/+layout.server.ts\n // doing `if (!dev) error(404)`) would have taken the control down with it\n // and failed every correctly-guarded site. /health is a real production\n // endpoint shipped by the starter and present in all 23 starter-derived\n // repos, each declaring `prerender = false` — so a 200 from it is still a\n // live render/function path, not a static file served by the CDN.\n const origin = target.url.replace(/\\/+$/, \"\");\n const uids = await twinUids(site.path);\n const twins: Array<{ uid: string; res: { status: number; body: string } }> = [];\n let control: { status: number; body: string };\n try {\n for (const uid of uids) twins.push({ uid, res: await probe(`${origin}/dev/match/${uid}`) });\n control = await probe(`${origin}/health`);\n } catch (err) {\n steps.push({ name: \"dev-guard\", result: errorOf(err) });\n return stop();\n }\n if (control.status !== 200) {\n steps.push({\n name: \"dev-guard\",\n result: {\n kind: \"error\",\n message: `${origin}/health answered ${control.status}, not 200 — the twin's 404 proves nothing (host down, wrong url, or the site's /health function is not deploying, in which case the function-health audit is failing too)`,\n },\n });\n return stop();\n }\n for (const { uid, res } of twins) {\n const unguardedTell = res.body.match(UNGUARDED_TWIN_MARKER);\n if (unguardedTell) {\n steps.push({\n name: \"dev-guard\",\n result: {\n kind: \"error\",\n message: `${origin}/dev/match/${uid} answered ${res.status} carrying the twin route's OWN \"${unguardedTell[0]}\" message — the twin is LIVE and merely has no assembly for the uid \"${uid}\". That is not evidence of a dev guard.`,\n },\n });\n return stop();\n }\n if (res.status !== 404 || !SITE_404_MARKER.test(res.body)) {\n steps.push({\n name: \"dev-guard\",\n result: {\n kind: \"error\",\n message:\n res.status === 404\n ? `${origin}/dev/match/${uid} 404s, but not with this site's own error page — cannot tell the guard from a dead route`\n : `${origin}/dev/match/${uid} answered ${res.status} — the matching twin is live in production`,\n },\n });\n return stop();\n }\n }\n steps.push({\n name: \"dev-guard\",\n result: {\n kind: \"probe\",\n message: `${uids.map((u) => `${origin}/dev/match/${u}`).join(\", \")} 404 (site error page), /health 200`,\n },\n });\n\n try {\n const auditWrite = await writeAuditsToAirtable({\n base,\n websites,\n slug: siteSlug(target.name),\n results,\n });\n // #539 Phase 5: mirror the first-audit write-back. Launch is the ONE path\n // that writes a brand-new site's health, so without this its row reads empty\n // in the console until the next hourly sync.\n if (auditWrite.siteId && auditWrite.fields) {\n await deps.siteMirror?.health(auditWrite.siteId, auditWrite.fields);\n }\n } catch (err) {\n steps.push({ name: \"audit\", result: errorOf(err) });\n return stop();\n }\n steps.push({ name: \"audit\", result: { kind: \"audit\", results, scores } });\n\n // 3. Draft the launch email (reuses draft.ts's reportId/period scheme). DRAFTS\n // ONLY — the M3 approve loop sends it and flips Status on send.\n const today = new Date();\n const period = today.toISOString().slice(0, 7);\n const slug = siteSlug(target.name);\n\n let report: ReportRow;\n try {\n // Re-run dedupe: reuse an existing Launch row for this (site, period) instead\n // of stacking a second draft. findReportByPeriod is the same idempotency\n // lookup draft.ts documents (dashboard/digest point lookup).\n const existing = await findReportForPeriod(deps.reportMirror, target.id, \"Launch\", period);\n if (existing) {\n // Reuse path: the row was created on a prior run with THAT run's scores. This\n // re-run just produced fresh audit scores AND will re-render the preview from\n // them — so refresh the row's Lighthouse cells (+ Completed on) to match,\n // otherwise the sent email (which reads the row) ships stale scores. The\n // create path already writes fresh scores via createDraft.\n await updateReportScores(base, existing.id, scores, today);\n // Mirror the same refresh — see the announce reuse path for why.\n await deps.reportMirror.patch(existing.id, {\n lighthouse_performance: scores.performance,\n lighthouse_accessibility: scores.accessibility,\n lighthouse_best_practices: scores.bestPractices,\n lighthouse_seo: scores.seo,\n completed_on: today.toISOString().slice(0, 10),\n });\n report = existing;\n } else {\n // #646 step 4: minted and written in Turso (`report_<ULID>`). The Airtable\n // Reports row this replaces is skipped, not written — see `createReportDraft`.\n report = await createReportDraft(draftInputFor(target, scores, today, period), {\n create: deps.reportMirror.create,\n });\n }\n } catch (err) {\n steps.push({ name: \"draft\", result: errorOf(err) });\n return stop();\n }\n\n // Mirror draft.ts:135-154 — render → upload \"Rendered HTML\" preview → flip\n // Draft ready. Without setDraftReady the draft never enters the approve queue\n // (every pending-approval gate requires draftReady true), so it can never be\n // approved or sent. The upload is a review convenience; the ready flag is the\n // critical step.\n try {\n const { html } = await renderReportHtml({\n siteName: target.name,\n siteUrl: target.url,\n reportType: \"Launch\",\n completedOn: today,\n lighthouse: scores,\n lastTestedDate: null,\n commentary: null,\n copy: resolveCopy(target),\n headerImageCid: `${slug}-header`,\n });\n // A preview-upload hiccup must NOT fail the launch — log and continue.\n try {\n await uploadAttachment(\n report.id,\n \"Rendered HTML\",\n html,\n `${slug}-${today.toISOString().slice(0, 10)}.html`,\n \"text/html\",\n );\n // The console preview reads the body from Turso, not the attachment.\n await deps.reportMirror.body(report.id, html);\n } catch (uploadErr) {\n console.warn(\n `⚠ Launch preview upload skipped for ${target.name}: ${\n uploadErr instanceof Error ? uploadErr.message : String(uploadErr)\n }`,\n );\n }\n // Critical: NOT wrapped — a failure here must surface as a failed launch. queueDraft\n // supersedes any lower-tier (Maintenance/Testing) drafts queued for this site; a Launch is\n // top tier so it only stands down if another Launch/Announcement is already queued.\n await queueDraft(\n base,\n { id: report.id, siteId: target.id, reportType: \"Launch\" },\n deps.reportMirror,\n );\n } catch (err) {\n steps.push({ name: \"draft\", result: errorOf(err) });\n return stop();\n }\n\n steps.push({ name: \"draft\", result: { kind: \"draft\", report } });\n\n return { site: label, steps, complete: true };\n}\n\n/** Build the Launch `DraftInput`. reportId/period mirror `draftReportForSite`\n * (draft.ts) — do not invent a new id scheme. `today`/`period` are threaded in\n * from `launch()` so a single timestamp drives the render, the draft, the\n * dedupe lookup, and the preview filename. Launch reports have no period window\n * and no prior maintenance test, so periodStart/periodEnd/completedOn all\n * collapse to \"today\" and `lastTestedDate` is null. */\nfunction draftInputFor(\n target: WebsiteRow,\n scores: LighthouseScores,\n today: Date,\n period: string,\n): DraftInput {\n const reportType = \"Launch\" as const;\n const reportId = `${target.name} — ${reportType} — ${today.toISOString().slice(0, 10)}`;\n return {\n reportId,\n siteId: target.id,\n reportType,\n period,\n periodStart: today,\n periodEnd: today,\n completedOn: today,\n lighthouse: scores,\n lastTestedDate: null,\n };\n}\n\nfunction errorOf(err: unknown): LaunchStepResult {\n return { kind: \"error\", message: err instanceof Error ? err.message : String(err) };\n}\n"],"mappings":"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","names":["depth"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lighthouseAudit
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VCPTOZ56.js";
|
|
4
4
|
import "./chunk-PTDMVGKL.js";
|
|
5
5
|
import "./chunk-LBYOLBW7.js";
|
|
6
6
|
import "./chunk-XXTZBPUY.js";
|
|
@@ -8,4 +8,4 @@ import "./chunk-NMAWXBJA.js";
|
|
|
8
8
|
export {
|
|
9
9
|
lighthouseAudit
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=lighthouse-
|
|
11
|
+
//# sourceMappingURL=lighthouse-RFMFREEX.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasRealScores,
|
|
3
|
+
lighthouseScoresFromResult,
|
|
4
|
+
resolveSlugFromCwd
|
|
5
|
+
} from "./chunk-X56LAKPR.js";
|
|
6
|
+
import "./chunk-TC4GANMX.js";
|
|
7
|
+
import "./chunk-ICSRWW5Q.js";
|
|
8
|
+
import "./chunk-RQJFGK3A.js";
|
|
9
|
+
export {
|
|
10
|
+
hasRealScores,
|
|
11
|
+
lighthouseScoresFromResult,
|
|
12
|
+
resolveSlugFromCwd
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=lighthouse-airtable-M45OPVRV.js.map
|
|
@@ -10,6 +10,17 @@ import {
|
|
|
10
10
|
PRETTIERIGNORE_BLOCK,
|
|
11
11
|
PRETTIERIGNORE_MARKER
|
|
12
12
|
} from "./chunk-Y6FCXRIC.js";
|
|
13
|
+
import {
|
|
14
|
+
runRecipeOverSites
|
|
15
|
+
} from "./chunk-VPJ7N6P7.js";
|
|
16
|
+
import {
|
|
17
|
+
appendSkipNotice,
|
|
18
|
+
fleetWorkdir,
|
|
19
|
+
prepareFleetSites
|
|
20
|
+
} from "./chunk-DYTYPKFN.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveSites
|
|
23
|
+
} from "./chunk-S2PLX4XK.js";
|
|
13
24
|
import {
|
|
14
25
|
PRETTIER_FLAG_NOTE,
|
|
15
26
|
formatWithPrettier,
|
|
@@ -17,14 +28,20 @@ import {
|
|
|
17
28
|
} from "./chunk-5CJVUPJU.js";
|
|
18
29
|
import {
|
|
19
30
|
withRecipe
|
|
20
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-LUF52EIY.js";
|
|
32
|
+
import "./chunk-Z3JXLULD.js";
|
|
21
33
|
import {
|
|
22
34
|
defaultSpawn
|
|
23
35
|
} from "./chunk-LBYOLBW7.js";
|
|
36
|
+
import "./chunk-RARREDQE.js";
|
|
37
|
+
import "./chunk-XXTZBPUY.js";
|
|
24
38
|
import {
|
|
25
39
|
ignoreRulesFor,
|
|
26
40
|
pathsMissingFromHead
|
|
27
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-WMF5S2MB.js";
|
|
42
|
+
|
|
43
|
+
// src/cli/commands/match-harness.ts
|
|
44
|
+
import { resolve } from "path";
|
|
28
45
|
|
|
29
46
|
// src/recipes/match-harness/index.ts
|
|
30
47
|
import { mkdir, readFile, rm, rmdir, writeFile } from "fs/promises";
|
|
@@ -345,7 +362,56 @@ async function matchHarness(site, opts, deps = { spawn: defaultSpawn }) {
|
|
|
345
362
|
});
|
|
346
363
|
}
|
|
347
364
|
|
|
365
|
+
// src/cli/commands/match-harness.ts
|
|
366
|
+
function formatResult(r) {
|
|
367
|
+
if (r.status === "noop") return `[${r.site}] noop: ${r.notes ?? ""}`;
|
|
368
|
+
if (r.status === "failed") return `[${r.site}] failed: ${r.notes ?? ""}`;
|
|
369
|
+
return `[${r.site}] applied: ${r.commits.length} commit(s)
|
|
370
|
+
${r.notes ?? ""}`;
|
|
371
|
+
}
|
|
372
|
+
async function runMatchHarnessCommand(site, opts) {
|
|
373
|
+
const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
|
|
374
|
+
let sites = await resolveSites({
|
|
375
|
+
...site !== void 0 ? { site } : {},
|
|
376
|
+
...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
|
|
377
|
+
...opts.workdir !== void 0 ? { workdir: opts.workdir } : {},
|
|
378
|
+
cwd
|
|
379
|
+
});
|
|
380
|
+
if (sites.length === 0) {
|
|
381
|
+
return {
|
|
382
|
+
output: `the inventory resolved NO SITES, so the matching harness was installed nowhere. This is not an installed fleet \u2014 check the inventory (an Airtable view filter, an empty JSON file, a dynamic inventory returning []). Do NOT read this exit as a rollout.`,
|
|
383
|
+
code: 1
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
let skipped = [];
|
|
387
|
+
if (opts.fleet) {
|
|
388
|
+
const workdir = opts.workdir ?? fleetWorkdir();
|
|
389
|
+
const prep = await prepareFleetSites(sites, { workdir });
|
|
390
|
+
sites = prep.prepared;
|
|
391
|
+
skipped = prep.skipped;
|
|
392
|
+
}
|
|
393
|
+
const rawMatrix = opts.matrix === void 0 ? void 0 : String(opts.matrix);
|
|
394
|
+
const matrix = rawMatrix === void 0 ? void 0 : rawMatrix.split(",").map((v) => Number(v.trim()));
|
|
395
|
+
if (matrix !== void 0 && matrix.some((v) => !Number.isFinite(v) || v <= 0)) {
|
|
396
|
+
return {
|
|
397
|
+
output: `match-harness: --matrix "${rawMatrix}" is not a list of viewports`,
|
|
398
|
+
code: 1
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const results = await runRecipeOverSites(
|
|
402
|
+
"match-harness",
|
|
403
|
+
sites,
|
|
404
|
+
(s) => matchHarness(s, {
|
|
405
|
+
ref: opts.ref ?? "",
|
|
406
|
+
...opts.cand !== void 0 ? { cand: opts.cand } : {},
|
|
407
|
+
...matrix !== void 0 ? { matrix } : {}
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
const output = results.map(formatResult).join("\n");
|
|
411
|
+
const code = results.some((r) => r.status === "failed") ? 1 : 0;
|
|
412
|
+
return { output: appendSkipNotice(output, skipped), code };
|
|
413
|
+
}
|
|
348
414
|
export {
|
|
349
|
-
|
|
415
|
+
runMatchHarnessCommand
|
|
350
416
|
};
|
|
351
|
-
//# sourceMappingURL=
|
|
417
|
+
//# sourceMappingURL=match-harness-CFELJA57.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/match-harness.ts","../src/recipes/match-harness/index.ts","../src/recipes/match-harness/previous.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { matchHarness } from \"../../recipes/match-harness/index.js\";\nimport type { RecipeResult } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport { prepareFleetSites, appendSkipNotice, type SkippedSite } from \"../fleet/prepare-sites.js\";\nimport { runRecipeOverSites } from \"../fleet/run-recipe-over-sites.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\n\nexport type MatchHarnessCommandOptions = {\n ref?: string;\n cand?: string;\n /** cac type-coerces a numeric option value, so this arrives as a NUMBER for\n * `--matrix 1440` and a string only when it contains a comma or a letter. */\n matrix?: string | number;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n};\n\nfunction formatResult(r: RecipeResult): string {\n if (r.status === \"noop\") return `[${r.site}] noop: ${r.notes ?? \"\"}`;\n if (r.status === \"failed\") return `[${r.site}] failed: ${r.notes ?? \"\"}`;\n return `[${r.site}] applied: ${r.commits.length} commit(s)\\n${r.notes ?? \"\"}`;\n}\n\nexport async function runMatchHarnessCommand(\n site: string | undefined,\n opts: MatchHarnessCommandOptions,\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n // NOTHING RESOLVED IS NOT AN INSTALLED FLEET — the same refusal\n // `prismic-ci` opens with, for the same reason. `runRecipeOverSites` over an\n // empty list joins zero rows into the empty string and this command returned\n // it with code 0: a blank line and a success exit, which is indistinguishable\n // from \"every site already had the harness\". Measured on this branch before\n // the fix, `match-harness --ref … --fleet <inventory naming nobody>` printed\n // one empty line and exited 0.\n //\n // An Airtable view filter, an empty JSON file, or a dynamic inventory\n // returning [] all produce exactly that, and none of them is a run.\n if (sites.length === 0) {\n return {\n output:\n `the inventory resolved NO SITES, so the matching harness was installed nowhere.` +\n ` This is not an installed fleet — check the inventory (an Airtable view filter, an` +\n ` empty JSON file, a dynamic inventory returning []). Do NOT read this exit as a` +\n ` rollout.`,\n code: 1,\n };\n }\n\n let skipped: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, { workdir });\n sites = prep.prepared;\n skipped = prep.skipped;\n }\n\n // A bad --matrix must not silently become [NaN]: the matrix is the gate's\n // denominator, and a NaN viewport would produce a score nobody could read.\n //\n // Two things force the shape below. cac COERCES a numeric option value, so\n // `--matrix 1440` arrives as the number 1440 (`.split` would throw) and\n // `--matrix 0` as the number 0 — which is FALSY, so a truthiness test drops it\n // and the recipe silently applies the default [1440,834,390] and exits 0. So:\n // normalise to a string, and branch on PRESENCE, never on truthiness.\n const rawMatrix = opts.matrix === undefined ? undefined : String(opts.matrix);\n const matrix =\n rawMatrix === undefined ? undefined : rawMatrix.split(\",\").map((v) => Number(v.trim()));\n if (matrix !== undefined && matrix.some((v) => !Number.isFinite(v) || v <= 0)) {\n return {\n output: `match-harness: --matrix \"${rawMatrix}\" is not a list of viewports`,\n code: 1,\n };\n }\n\n const results = await runRecipeOverSites(\"match-harness\", sites, (s) =>\n matchHarness(s, {\n ref: opts.ref ?? \"\",\n ...(opts.cand !== undefined ? { cand: opts.cand } : {}),\n ...(matrix !== undefined ? { matrix } : {}),\n }),\n );\n\n const output = results.map(formatResult).join(\"\\n\");\n const code = results.some((r) => r.status === \"failed\") ? 1 : 0;\n return { output: appendSkipNotice(output, skipped), code };\n}\n","import { mkdir, readFile, rm, rmdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { ignoreRulesFor, pathsMissingFromHead } from \"../../util/git.js\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { formatWithPrettier, resolveTargetPrettier, PRETTIER_FLAG_NOTE } from \"../_prettier.js\";\nimport {\n MATCH_HARNESS_FILES,\n MATCH_HARNESS_PREVIOUS,\n MATCH_HARNESS_COUPLED,\n HARNESS_JSON_RELATIVE,\n GITIGNORE_MARKER,\n GITIGNORE_BLOCK,\n PRETTIERIGNORE_MARKER,\n PRETTIERIGNORE_BLOCK,\n CLAUDE_MD_MARKER,\n CLAUDE_MD_BLOCK,\n} from \"./template.js\";\nimport { MATCH_HARNESS_BLOCK_PREVIOUS } from \"./previous.js\";\n\n/** How long the target's own prettier gets. Also what makes the fleet's default\n * spawn detach the child, so the kill reaches prettier and not just a wrapper.\n * Same budget as `prismic-ci` and the Prismic pull-down path. */\nconst PRETTIER_TIMEOUT_MS = 60_000;\n\nexport type MatchHarnessOptions = {\n /** The live reference URL the harness gates against. Required — a harness\n * with no reference cannot produce evidence, only an absence of errors. */\n ref: string;\n /** Candidate origin. Defaults to the SvelteKit dev server. */\n cand?: string;\n /** Breakpoint matrix. Defaults to the fleet's 1440 / 834 / 390. */\n matrix?: number[];\n};\n\nexport type MatchHarnessDeps = {\n spawn: SpawnFn;\n /** Previously shipped renders per relative path; injectable so the\n * safe-replace path is testable before a second version exists. */\n previous?: Readonly<Record<string, readonly string[]>>;\n /** Previously shipped BLOCK bodies per target file; injectable for the same\n * reason, and empty in the shipped table until a body is superseded. */\n blockPrevious?: Readonly<Record<string, readonly string[]>>;\n /** Resolve the TARGET repo's own prettier. Injected so a test can assert the\n * absolute-path spawn without a populated `node_modules`. */\n resolvePrettier?: (repoRoot: string) => Promise<string | null>;\n};\n\ntype Plan = { ref: string; cand: string; matrix: number[] };\n\nasync function readIfExists(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** CRLF and trailing whitespace are the only differences a site's formatter is\n * allowed to introduce before a file stops counting as \"ours\". */\nfunction normalize(s: string): string {\n return s\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n}\n\n/** What to do with one marked block region. `flag` and `skip` never write.\n * One member per action, so `content` narrows on the discriminant alone. */\nexport type BlockPlan =\n | { action: \"write\"; content: string }\n | { action: \"replace\"; content: string }\n | { action: \"terminate\"; content: string }\n | { action: \"skip\" }\n | { action: \"flag\" };\n\n/**\n * Plan one marked block region. The only append-to-existing-file idiom in this\n * repo is mergeGitignore, and it cannot express a negated whitelist\n * (`matching/*` then `!matching/*.sh`) as one unit: it compares entries by\n * normalized presence, so the ordering that makes the block work would be lost.\n * So the block is kept whole — and, being whole, it needs an END as well as a\n * start if it is ever to be CORRECTED rather than only appended.\n *\n * That was #739. The predecessor returned \"already done\" the instant the marker\n * was present, which is right for \"never append twice\" and wrong for everything\n * else: the block's CONTENTS could then never change on a site that had already\n * installed. On `.gitignore` that is not staleness but a brick — the block is a\n * negated whitelist, so a harness file at a path it does not re-include is\n * absent from the commit, `pathsMissingFromHead` refuses the install, and the\n * remedy (widen the whitelist) is the one edit that cannot land.\n *\n * Four states, and the third is the one that made this worth doing properly:\n *\n * - No marker: append the region, terminator included.\n * - Marker AND terminator: the extent is KNOWN. Body still current -> skip.\n * Body byte-matches something we shipped before -> replace IN PLACE, keeping\n * everything before and after the region. Anything else -> flag, write\n * nothing.\n * - Marker, NO terminator — every site installed from 0.95.0: the extent is not\n * known, so it is never guessed. A candidate qualifies only by matching\n * EXACTLY, byte-for-byte, at the exact offset the body starts; what follows\n * is the site's own and is preserved verbatim. Guessing end-of-file instead\n * would eat whatever mergeGitignore appended after us.\n * - Anything unrecognised: flag. Anchoring is on the FIRST occurrence of the\n * marker, so a marker quoted in the site's own prose mis-anchors into text\n * that matches no candidate — which degrades to a flag, never to a write.\n */\nexport function planBlockWrite(\n existing: string | null,\n marker: string,\n endMarker: string,\n block: string,\n previous: readonly string[],\n): BlockPlan {\n const region = `${marker}\\n${block}\\n${endMarker}`;\n if (existing === null) return { action: \"write\", content: `${region}\\n` };\n\n const start = existing.indexOf(marker);\n if (start === -1) {\n const base = existing.endsWith(\"\\n\") ? existing : `${existing}\\n`;\n return { action: \"write\", content: `${base}\\n${region}\\n` };\n }\n\n const head = existing.slice(0, start);\n const bodyStart = start + marker.length + 1;\n\n const endAt = existing.indexOf(endMarker, bodyStart);\n if (endAt !== -1) {\n const body = existing.slice(bodyStart, endAt);\n if (normalize(body) === normalize(block)) return { action: \"skip\" };\n if (previous.some((p) => normalize(body) === normalize(p)))\n return {\n action: \"replace\",\n content: head + region + existing.slice(endAt + endMarker.length),\n };\n return { action: \"flag\" };\n }\n\n for (const candidate of [block, ...previous]) {\n if (!existing.startsWith(candidate, bodyStart)) continue;\n const content = `${head}${region}\\n${existing.slice(bodyStart + candidate.length)}`;\n // Matching the CURRENT block means the bytes do not change at all and only\n // the terminator is added — the migration write is content-neutral.\n return candidate === block ? { action: \"terminate\", content } : { action: \"replace\", content };\n }\n return { action: \"flag\" };\n}\n\n/**\n * The commit message says what the run DID. Every run used to commit as\n * `feat: install …`, including one that installed nothing — measured on 29-navy:\n * three scripts upgraded, three block regions terminated, 6 files and 436\n * lines, labelled a first install (#760). The notes distinguished the two the\n * whole time; the message is what a reader bisecting `git log` sees.\n *\n * `write` anywhere is an install; only replace/terminate is an upgrade; both is\n * both. A pristine install keeps the historical one-line message byte-for-byte.\n * An upgrade names its paths in the body, so `git log` answers \"when did this\n * site's gate.sh change\" without a diff.\n */\nexport function commitMessage(installed: readonly string[], upgraded: readonly string[]): string {\n const what = \"(/dev/match route + matching/ gate scripts)\";\n if (upgraded.length === 0) return `feat: install the matching harness ${what}`;\n const subject =\n installed.length > 0\n ? `feat: install and upgrade the matching harness ${what}`\n : `chore: upgrade the matching harness (${upgraded.length} file${upgraded.length === 1 ? \"\" : \"s\"} from a previously shipped version)`;\n const body = [\n installed.length > 0 ? `installed: ${installed.join(\", \")}` : \"\",\n `upgraded from a previously shipped version: ${upgraded.join(\", \")}`,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n return `${subject}\\n\\n${body}`;\n}\n\n/** What to do with one installed file. `flag` never writes. */\nexport function planFileWrite(\n existing: string | null,\n template: string,\n owner: \"recipe\" | \"site\",\n previous: readonly string[],\n): \"write\" | \"replace\" | \"skip\" | \"flag\" {\n if (existing === null) return \"write\";\n if (normalize(existing) === normalize(template)) return \"skip\";\n if (owner === \"site\") return \"skip\"; // records are never touched again\n if (previous.some((p) => normalize(existing) === normalize(p))) return \"replace\";\n return \"flag\";\n}\n\n/**\n * Where each block region ENDS. Deliberately NOT in template.ts beside the\n * start markers, and the split is a real smell being paid for on purpose:\n * template.ts is GENERATED, and regenerating it today drags in undeclared drift\n * from the source site and would flag three already-installed scripts forever.\n * Reuniting them belongs to #753, which makes the generator's\n * `MATCH_HARNESS_PREVIOUS` emission trustworthy enough to regenerate against.\n * That table stays in template.ts and stays POPULATED in the meantime: it is\n * what upgrades an installed site's gate.sh past #744, so pointing this recipe\n * at an empty one un-ships that fix to every site already carrying the v1 gate.\n *\n * None of the three contains its own start marker as a substring — `# end\n * reddoor-maint …` never yields `# reddoor-maint …` — which is what keeps\n * `indexOf(marker)` from anchoring the region on its own terminator. A test\n * asserts it in both directions.\n */\nexport const GITIGNORE_END_MARKER = \"# end reddoor-maint match-harness\";\nexport const PRETTIERIGNORE_END_MARKER = \"# end reddoor-maint match-harness\";\nexport const CLAUDE_MD_END_MARKER = \"<!-- end reddoor-maint match-harness -->\";\n\n/** The three files the recipe APPENDS a marked block to rather than writing whole. */\nconst APPENDED_BLOCKS = [\n [\".gitignore\", GITIGNORE_MARKER, GITIGNORE_END_MARKER, GITIGNORE_BLOCK],\n [\".prettierignore\", PRETTIERIGNORE_MARKER, PRETTIERIGNORE_END_MARKER, PRETTIERIGNORE_BLOCK],\n [\"CLAUDE.md\", CLAUDE_MD_MARKER, CLAUDE_MD_END_MARKER, CLAUDE_MD_BLOCK],\n] as const;\n\n/**\n * Every path the installed harness requires to be IN THE COMMIT — deliberately\n * the whole manifest, NOT the paths written on this run. A re-run skips every\n * file already byte-correct on disk, so a check over the written delta would\n * find nothing missing and report a second hollow \"applied\" over the same\n * half-install.\n */\nexport const MATCH_HARNESS_INSTALLED_PATHS: readonly string[] = [\n ...MATCH_HARNESS_FILES.map((f) => f.rel),\n ...APPENDED_BLOCKS.map(([rel]) => rel),\n];\n\n/**\n * Installs the matching harness into a site: the dev-guarded `/dev/match/[uid]`\n * route, a `site-pages.js` scaffold and its fixture-vs-model test,\n * `matching/harness.json` + the read layer, the round scripts, the site records,\n * and the `.gitignore` / `.prettierignore` / `CLAUDE.md` blocks.\n *\n * Install-if-absent throughout. Files a site edits are never rewritten. A\n * recipe-owned script that matches a previously shipped render is safe-replaced;\n * one that has been hand-edited is FLAGGED in the notes and left alone.\n *\n * ONE EXCEPTION TO PER-FILE INDEPENDENCE: the scripts in MATCH_HARNESS_COUPLED\n * upgrade as a SET. If any one of them is hand-edited, none of them is written,\n * because gate.sh calls `harness.mjs --check-run` and next.mjs imports from\n * harness.mjs — upgrading the others around a pinned one leaves a harness that\n * cannot run at all. See the measurement on MATCH_HARNESS_COUPLED.\n */\nexport async function matchHarness(\n site: Site,\n opts: MatchHarnessOptions,\n deps: MatchHarnessDeps = { spawn: defaultSpawn },\n): Promise<RecipeResult> {\n return withRecipe<Plan>({\n name: \"match-harness\",\n site,\n plan: async () => {\n if (!opts.ref || !/^https?:\\/\\//.test(opts.ref)) {\n return {\n kind: \"failed\",\n notes:\n \"--ref <url> is required: the harness gates against a live reference, and a harness with no reference can only ever report an absence of errors\",\n };\n }\n return {\n kind: \"apply\",\n plan: {\n ref: opts.ref.replace(/\\/$/, \"\"),\n cand: opts.cand ?? \"http://localhost:5173\",\n matrix: opts.matrix ?? [1440, 834, 390],\n },\n };\n },\n apply: async (planned, { commit, cwd }) => {\n const notes: string[] = [];\n const written: string[] = [];\n /** What this run did, for the commit message: paths written for the\n * FIRST time, and paths replaced or terminated from a previously shipped\n * render. The notes already tell them apart; the message is what\n * survives into `git log` (#760). */\n const installed: string[] = [];\n const upgraded: string[] = [];\n /** For every path this run writes: what was there BEFORE (null = the file\n * did not exist). The refusal below restores from this, so a refused run\n * leaves the checkout byte-identical to how it found it. */\n const before = new Map<string, string | null>();\n const previousAll = deps.previous ?? MATCH_HARNESS_PREVIOUS;\n const blockPreviousAll = deps.blockPrevious ?? MATCH_HARNESS_BLOCK_PREVIOUS;\n\n // PASS 1 — decide everything, write nothing. The coupled-set demotion\n // below needs the whole picture before the first byte is written.\n type Planned = {\n f: (typeof MATCH_HARNESS_FILES)[number];\n template: string;\n existing: string | null;\n action: ReturnType<typeof planFileWrite>;\n };\n const plans: Planned[] = [];\n for (const f of MATCH_HARNESS_FILES) {\n const target = join(cwd, f.rel);\n let template = f.template;\n if (f.rel === HARNESS_JSON_RELATIVE) {\n // Patch the PARSED object, never the text. The seed is pretty-printed\n // two-space JSON, so `matrix` renders as a four-line exploded array\n // and the literal \"[1440, 834, 390]\" does not occur — a string\n // replace on it is a no-op that drops --matrix with no error.\n const seed = JSON.parse(template) as Record<string, unknown>;\n seed.ref = planned.ref;\n seed.cand = planned.cand;\n seed.matrix = planned.matrix;\n template = JSON.stringify(seed, null, 2) + \"\\n\";\n }\n const existing = await readIfExists(target);\n plans.push({\n f,\n template,\n existing,\n action: planFileWrite(existing, template, f.owner, previousAll[f.rel] ?? []),\n });\n }\n\n // The coupled set moves together or not at all. Two deliberate limits,\n // both of them the difference between a guard and an outage:\n //\n // ONLY `flag` demotes. `skip` means the file is already byte-correct, and\n // treating that as a flag would make every re-run refuse itself and the\n // recipe permanently inert — the demotion's own failure mode is\n // OVER-refusal, so its test has to GRANT an upgrade, not merely deny one.\n //\n // ONLY `replace` is demoted. A file that is ABSENT cannot be \"left\n // alone\": there is nothing to preserve, and skipping the write leaves the\n // site with no harness at all rather than an old one. The hazard being\n // guarded is upgrading HALF of an installed set past a pinned member;\n // writing a missing file is the only action that makes the set exist.\n // (Measured: demoting `write` too broke a fresh install alongside one\n // hand-edited script — 16 files silently unwritten, then a failed run.)\n const coupled = new Set(MATCH_HARNESS_COUPLED);\n const blockedBy = plans\n .filter((p) => coupled.has(p.f.rel) && p.action === \"flag\")\n .map((p) => p.f.rel);\n const demoted = plans.filter(\n (p) => coupled.has(p.f.rel) && p.action === \"replace\" && !blockedBy.includes(p.f.rel),\n );\n if (blockedBy.length > 0 && demoted.length > 0) {\n for (const p of demoted) p.action = \"flag\";\n notes.push(\n `${blockedBy.join(\", \")} differs from the shipped template, so the whole coupled set ` +\n `was left alone: ${MATCH_HARNESS_COUPLED.join(\", \")} upgrade together. ` +\n `matching/gate.sh calls \\`harness.mjs --check-run\\` and matching/next.mjs imports from ` +\n `harness.mjs, so upgrading one without the others leaves a harness that cannot run.`,\n );\n }\n\n // PASS 2 — write.\n const demotedRels = new Set(demoted.map((p) => p.f.rel));\n for (const { f, template, existing, action } of plans) {\n if (action === \"flag\") {\n if (demotedRels.has(f.rel)) continue; // named in the set note above\n notes.push(\n `${f.rel} differs from the shipped template and was left alone (hand-edited?)`,\n );\n continue;\n }\n if (action === \"skip\") continue;\n const target = join(cwd, f.rel);\n await mkdir(dirname(target), { recursive: true });\n before.set(f.rel, existing);\n await writeFile(target, template, \"utf-8\");\n written.push(f.rel);\n (action === \"write\" ? installed : upgraded).push(f.rel);\n if (action === \"replace\") notes.push(`${f.rel} upgraded from a previous version`);\n }\n\n // Three appended blocks, each a delimited region that is replaced in\n // place when its body is one this recipe shipped, and flagged — never\n // overwritten — when it is anything else.\n for (const [rel, marker, endMarker, block] of APPENDED_BLOCKS) {\n const path = join(cwd, rel);\n const existing = await readIfExists(path);\n const plan = planBlockWrite(\n existing,\n marker,\n endMarker,\n block,\n blockPreviousAll[rel] ?? [],\n );\n if (plan.action === \"skip\") continue;\n if (plan.action === \"flag\") {\n // Never enters `written`: not formatted, and not restored by the\n // refusal path below, because this run did not touch it.\n notes.push(\n `${rel} carries a match-harness block that differs from every block this recipe has shipped, and was left alone (hand-edited?)`,\n );\n continue;\n }\n before.set(rel, existing);\n await writeFile(path, plan.content, \"utf-8\");\n written.push(rel);\n (plan.action === \"write\" ? installed : upgraded).push(`${rel} (match-harness block)`);\n if (plan.action === \"replace\")\n notes.push(`${rel}'s match-harness block upgraded from a previous version`);\n if (plan.action === \"terminate\")\n notes.push(\n `${rel}'s match-harness block region was terminated so a future version can update it`,\n );\n }\n\n // Format only what the SITE owns, decided by OWNERSHIP and never by path.\n // A recipe-owned file that a site's prettier reformats is indistinguishable\n // from a hand edit on the next install, so planFileWrite flags it and the\n // recipe can never upgrade it again. A path prefix cannot express that:\n // three recipe-owned files live under src/. This is an ALLOW-list on\n // purpose — a file added to MATCH_HARNESS_FILES is not formatted until its\n // owner says \"site\", because the unsafe default is silent and permanent.\n // The same files are in the .prettierignore block written above, which\n // stops the site's OWN `prettier --write .` from doing the identical\n // damage; that block and this filter must agree, and a test asserts it.\n // CLAUDE.md is not in the file table — it is an append into a file the\n // site owns — so it is named here.\n const siteOwned = new Set(\n MATCH_HARNESS_FILES.filter((f) => f.owner === \"site\").map((f) => f.rel),\n );\n const toFormat = written.filter((p) => siteOwned.has(p) || p === \"CLAUDE.md\");\n\n // Run the SITE's own prettier, resolved POSITIVELY and invoked by absolute\n // path. `pnpm exec prettier` here would first run a full, unbounded\n // `pnpm install` in the client's checkout — which on `--fleet` is the\n // NORMAL path, because `prepareFleetSites` clones and never installs — and\n // would then, in a repo whose install left no prettier of its own, fall\n // through to the CALLING repo's binary and exit 0, reporting success for a\n // format the target never did. A `true` from here must mean the target's\n // own prettier ran, so nothing about which binary runs is left to\n // resolution.\n if (toFormat.length > 0) {\n const bin = await (deps.resolvePrettier ?? resolveTargetPrettier)(cwd);\n if (bin === null) {\n notes.push(PRETTIER_FLAG_NOTE);\n } else if (\n !(await formatWithPrettier(deps.spawn, cwd, toFormat, {\n bin,\n timeoutMs: PRETTIER_TIMEOUT_MS,\n }))\n ) {\n notes.push(PRETTIER_FLAG_NOTE);\n }\n }\n\n await commit(commitMessage(installed, upgraded));\n\n // POSITIVE EVIDENCE that the install is real: every installed path is\n // FOUND in HEAD's tree. `git add -A` honours the site's .gitignore and\n // exits 0 either way, and git cannot re-include a file whose PARENT\n // DIRECTORY is excluded — so a site that already ignores `matching/`\n // (or `**/matching/`, `/matching`, `src/lib/site-pages.js`, …) gets the\n // files on disk, nothing in the commit, and — before this check — a\n // result of \"applied\", plus 83 lines of CLAUDE.md rules naming scripts\n // that exist on no other machine.\n const missing = await pathsMissingFromHead(cwd, MATCH_HARNESS_INSTALLED_PATHS);\n if (missing.length > 0) {\n const rules = await ignoreRulesFor(cwd, missing);\n const sources = [...new Set(rules.map((r) => r.split(\"\\t\")[0]!))];\n const explained = new Set(rules.map((r) => r.split(\"\\t\")[1] ?? \"\"));\n const unexplained = missing.filter((p) => !explained.has(p));\n\n // Put the checkout back exactly as we found it. Only paths THIS RUN\n // wrote and git then refused are touched: one that reached the commit\n // is left to withRecipe's force-checkout, and one we never wrote is\n // never ours. `git checkout -f` cannot do this job — these paths are\n // ignored, so git does not know they exist.\n const removed: string[] = [];\n for (const rel of written.filter((p) => missing.includes(p))) {\n const prev = before.get(rel);\n if (prev === undefined) continue;\n if (prev === null) {\n await rm(join(cwd, rel), { force: true });\n removed.push(rel);\n } else {\n await writeFile(join(cwd, rel), prev, \"utf-8\");\n }\n }\n // Deepest-first, so `matching/spec-sections` goes before `matching`.\n // rmdir refuses a non-empty directory, which is exactly the guard wanted.\n for (const d of [...new Set(removed.map(dirname))].sort((a, b) => b.length - a.length)) {\n if (d !== \".\") await rmdir(join(cwd, d)).catch(() => undefined);\n }\n\n return {\n kind: \"failed\",\n notes:\n `the harness was NOT installed: ${missing.length} of ${MATCH_HARNESS_INSTALLED_PATHS.length} paths git refused to track, so a fresh clone, CI and the next agent get a harness that does not exist. ` +\n `Absent from the commit: ${missing.join(\", \")}. ` +\n (sources.length > 0\n ? `Excluded by ${sources.join(\", \")} — narrow or remove those rules and re-run. `\n : \"\") +\n (unexplained.length > 0\n ? `No ignore rule matched ${unexplained.join(\", \")} — check .git/info/exclude, core.excludesFile, and whether the directory is a nested repository. `\n : \"\") +\n \"Everything this run wrote to those paths has been put back as it was.\",\n };\n }\n\n return notes.length > 0 ? { kind: \"ok\", notes: notes.join(\"; \") } : { kind: \"ok\" };\n },\n });\n}\n","/**\n * Block BODIES this recipe has previously shipped — the block half of what lets\n * it tell \"stale, but ours\" from \"hand-edited by the site\". `planBlockWrite`\n * flags every region it cannot recognise, so without a record of what was\n * shipped, install-once is install-forever for the three marked regions.\n *\n * The whole-FILE table is NOT here. It lives in the generated `template.ts` as\n * `MATCH_HARNESS_PREVIOUS`, it is POPULATED, and it is what upgrades an\n * installed site's `matching/gate.sh` to the one that stopped saying ALL DONE\n * over failed runs (#744). Re-pointing that import at an empty table here would\n * silently un-ship that fix to every site already carrying the v1 gate — which\n * is exactly what this file did on its first draft, and what\n * `tests/recipes/match-harness.test.ts`'s \"UPGRADES a site running the shipped\n * v1 gate.sh rather than flagging it\" caught. The deprecation note and the\n * import switch were written together; the data migration they both depend on\n * is #753, and had not happened. Whatever replaces the generated table has to\n * carry the bodies across in the same commit that moves the import.\n *\n * Two rules for what goes below, and they are why this table is HAND-AUTHORED:\n *\n * 1. An entry is added ONLY when a block body has been SUPERSEDED — at the\n * moment the shipped body changes, in the same commit that changes it.\n * Recording a body that is still current makes the table lie in the one\n * direction that matters: it would let a stale render be replaced by itself.\n *\n * 2. A body's source is a PUBLISHED GIT TAG — `git show v0.95.0:<path>` — never\n * the working tree. Reading the tree is how a mutant gets banked as a\n * legitimate prior release: mutate, regenerate, revert, and the mutation is\n * now a body this recipe will happily install over a real site. Measured on\n * this branch: one mutate/regenerate/revert cycle added 693 lines and two\n * copies of the same predicate.\n *\n * EMPTY at 0.95.1 on purpose. 0.95.1 changes no block body at all: its whole\n * job is to install the region terminators, which is what makes the first\n * exercise of the block replace path provably content-neutral.\n *\n * First entry after 0.95.1: the CLAUDE.md block, superseded when rule 4 and the\n * round protocol gained `census.sh` (#736). Sourced from `git show v0.95.1`,\n * per rule 2, and identical at v0.95.0.\n */\n\n/** The CLAUDE.md block as shipped at v0.95.0 and v0.95.1 — `git show\n * v0.95.1:src/recipes/match-harness/template.ts` → `CLAUDE_MD_BLOCK`, byte-\n * identical at both tags. Superseded by the block that names `census.sh`\n * (#736): the recipe installed the Phase 3 style gate and the rules that would\n * make anyone run it did not mention it. Exported so a test can seed exactly\n * this body and prove the shipped table upgrades it. */\nexport const CLAUDE_MD_BLOCK_0_95_1 = `\nThe \\`matching-a-page\\` skill governs a live-reference rebuild. These five rules\nexist because the skill alone did not hold on the project this harness came\nfrom — each one is a drift that actually happened, with the mechanical check\nthat now catches it. \\`matching/harness.json\\` is this site's configuration;\n\\`matching/LEDGER.md\\` is the dated record of every deviation, floor and mask.\n\n### 1. Source prescribes, rects only verify\n\nEvery geometry fix must cite the rule it came from: a line in the captured\nreference stylesheet under \\`matching/spec/\\`, or the reference's HTML. \"The probe\nsays the gap is 40px\" is not a source. If you cannot name the line you are\nguessing — go read the stylesheet first.\n\n**Check:** the commit body must name the file:line for each fix.\n**Operator's challenge:** _\"which line of the reference stylesheet says that?\"_\n\n### 2. Phase 1 before Phase 4\n\nA page gets its section census and per-section spec in \\`matching/SPEC.md\\` BEFORE\nits geometry is touched. No SPEC section, no geometry round. The census is the\ncoverage denominator; skipping it is how a reference's root-font ladder and its\nper-component height ladders get discovered reactively, after the region has\nalready failed several rounds.\n\n**Check:** \\`matching/gate.sh\\` refuses to run a page with no \\`SPEC.md\\` section.\n**Operator's challenge:** _\"show me the SPEC section for that region.\"_\n\n### 3. Three strikes, then stop\n\nA failing region that has not improved across 3+ gate runs does not get a fourth\nattempt. Present the attempts. Never widen the threshold, add a mask, or\nreclassify it as a floor to make it go away.\n\n**Check:** \\`node matching/strikes.mjs <page>\\` — exits 1 while any region is\nstalled, and is the first thing a geometry round runs.\n**Operator's challenge:** _\"how many runs has that region been flat?\"_\n\n### 4. A gate closes an item, nothing else\n\nNo fix is \"done\" because the code changed. Paste the gate header — it is\nself-describing, so a nonstandard threshold or an undisclosed mask is visible.\nThe threshold and the matrix are whatever \\`matching/harness.json\\` says, on every\npage, never a subset.\n\n**Operator's challenge:** _\"paste the gate header.\"_\n\n### 5. A commit is a checkpoint, not a stopping point\n\nDo not hand control back between rounds. After committing, run\n\\`node matching/next.mjs\\`: while it exits 1 there is a named next action, and the\nround continues. Report when the backlog is empty, when a decision is genuinely\nthe operator's (three strikes, a novel floor, a threshold change), or when\nasked — not because a commit felt like a natural place to summarise.\n\nThis exists because the failure was habitual, not deliberate: a turn ends when\nuser-facing prose gets written, and \"I just committed something good\" is the\nmoment that invites writing it. The gate stops incorrect work; this stops\npremature stopping.\n\n**Pausing:** create \\`matching/PAUSED\\` holding a note that says what the pause\nmeans; \\`next.mjs\\` and \\`strikes.mjs\\` then exit 0 while it exists. This rule says\n\"do not stop while work is named\"; it never said \"find work to name\". An empty\nagenda and a paused one end the round the same way. Deleting the switch is the\noperator's call.\n\n**Check:** \\`node matching/next.mjs\\` — exits 1 while any non-floor region fails.\n**Operator's challenge:** _\"what does next.mjs say?\"_\n\n### Round protocol\n\n1. \\`node matching/strikes.mjs <page>\\` — if it exits 1, the stalled regions are\n the agenda, and stalled ones get escalated rather than re-attempted.\n2. Confirm the page has a \\`matching/SPEC.md\\` section; write\n \\`matching/spec-sections/<page>.md\\` and run \\`node matching/build-spec.mjs\\` if\n not.\n3. Fix, each change citing its source line.\n4. \\`bash matching/gate.sh <tag> <page>\\` — paste the header.\n5. Append to \\`matching/LEDGER.md\\` at the moment a deviation, floor or mask is\n decided, not reconstructed at the end.\n6. \\`pnpm verify\\`, then commit and push.\n`;\n\n/** Block BODIES previously shipped per target file, for the three marked\n * regions in `.gitignore`, `.prettierignore` and `CLAUDE.md`. */\nexport const MATCH_HARNESS_BLOCK_PREVIOUS: Readonly<Record<string, readonly string[]>> = {\n \"CLAUDE.md\": [CLAUDE_MD_BLOCK_0_95_1],\n};\n"],"mappings":"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","names":[]}
|
package/dist/notify-K3WVH3DM.js
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
resolveRecipients
|
|
8
8
|
} from "./chunk-PXRN2MER.js";
|
|
9
9
|
import "./chunk-Z7FDJ35L.js";
|
|
10
|
-
import "./chunk-5ML7FMBD.js";
|
|
11
10
|
import "./chunk-NO74DXDP.js";
|
|
11
|
+
import "./chunk-5ML7FMBD.js";
|
|
12
12
|
import "./chunk-I7NHLVVI.js";
|
|
13
13
|
import "./chunk-BGTYPVLT.js";
|
|
14
14
|
import "./chunk-2OP4JKIQ.js";
|