@reddoorla/maintenance 0.95.1 → 0.97.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -7
- package/dist/{announce-CLQYX5I4.js → announce-5ZD2SBGD.js} +39 -31
- package/dist/announce-5ZD2SBGD.js.map +1 -0
- package/dist/attachments-JFGFQRXQ.js +10 -0
- package/dist/{blux-4KNXUP2K.js → blux-NXQLMS4W.js} +1 -1
- package/dist/{bump-deps-C5G2EFBB.js → bump-deps-775VLDYB.js} +6 -6
- package/dist/{chunk-FUWFLAWQ.js → chunk-3RLDX3Q2.js} +5 -5
- package/dist/{chunk-XHHMM5HX.js → chunk-43SW3FGW.js} +63 -1
- package/dist/chunk-43SW3FGW.js.map +1 -0
- package/dist/{chunk-R53VL3RI.js → chunk-5CJVUPJU.js} +1 -1
- package/dist/{chunk-R53VL3RI.js.map → chunk-5CJVUPJU.js.map} +1 -1
- package/dist/{chunk-S7RYFUIK.js → chunk-6PGXKN6F.js} +2 -2
- package/dist/{chunk-HTABUCF4.js → chunk-6THEZQLA.js} +3 -3
- package/dist/chunk-A5QKNOXW.js +72 -0
- package/dist/chunk-A5QKNOXW.js.map +1 -0
- package/dist/chunk-ASLDCWBF.js +41 -0
- package/dist/chunk-ASLDCWBF.js.map +1 -0
- package/dist/{chunk-CWXTNPNZ.js → chunk-AYDQQBNG.js} +3 -3
- package/dist/chunk-AYDQQBNG.js.map +1 -0
- package/dist/{chunk-WVNDEYXL.js → chunk-BINFZQ2T.js} +57 -6
- package/dist/chunk-BINFZQ2T.js.map +1 -0
- package/dist/{chunk-2NQUCS7O.js → chunk-BIYQN7FH.js} +3 -1
- package/dist/{chunk-2NQUCS7O.js.map → chunk-BIYQN7FH.js.map} +1 -1
- package/dist/{chunk-YLYXW5PH.js → chunk-CXICCLNM.js} +34 -4
- package/dist/chunk-CXICCLNM.js.map +1 -0
- package/dist/{chunk-BYGPUUCT.js → chunk-DRNZVORX.js} +66 -30
- package/dist/chunk-DRNZVORX.js.map +1 -0
- package/dist/{chunk-LM2NHRCJ.js → chunk-DYTYPKFN.js} +4 -4
- package/dist/chunk-DYTYPKFN.js.map +1 -0
- package/dist/{chunk-DFKBTBO3.js → chunk-EN6CTBUN.js} +35 -6
- package/dist/chunk-EN6CTBUN.js.map +1 -0
- package/dist/{chunk-5EEMTJMT.js → chunk-FHFXJ3EM.js} +8 -6
- package/dist/chunk-FHFXJ3EM.js.map +1 -0
- package/dist/{chunk-Z2K4JPQ3.js → chunk-FP6HOW6J.js} +10 -7
- package/dist/chunk-FP6HOW6J.js.map +1 -0
- package/dist/{chunk-2LYOAB2E.js → chunk-HSMN35LU.js} +2 -2
- package/dist/chunk-ICSRWW5Q.js +148 -0
- package/dist/chunk-ICSRWW5Q.js.map +1 -0
- package/dist/{chunk-FSGICFXB.js → chunk-IDBPII6G.js} +17 -5
- package/dist/chunk-IDBPII6G.js.map +1 -0
- package/dist/{chunk-C3IQQKXL.js → chunk-IOTOOTII.js} +121 -1
- package/dist/{chunk-C3IQQKXL.js.map → chunk-IOTOOTII.js.map} +1 -1
- package/dist/{chunk-BCKEP5S6.js → chunk-JHJNNJLG.js} +2 -2
- package/dist/{chunk-TO7DJYLZ.js → chunk-L3S3QZXL.js} +178 -39
- package/dist/chunk-L3S3QZXL.js.map +1 -0
- package/dist/{chunk-WSJ7RO54.js → chunk-LAG6IP7Y.js} +23 -5
- package/dist/chunk-LAG6IP7Y.js.map +1 -0
- package/dist/{chunk-WJMPCVOR.js → chunk-LAJ3FF54.js} +12 -4
- package/dist/chunk-LAJ3FF54.js.map +1 -0
- package/dist/{chunk-2ZAZ7ETT.js → chunk-LDHRIQUW.js} +32 -18
- package/dist/chunk-LDHRIQUW.js.map +1 -0
- package/dist/{chunk-6R56OKJZ.js → chunk-LF2TJN4X.js} +2 -2
- package/dist/chunk-LF2TJN4X.js.map +1 -0
- package/dist/{chunk-ZQV7UQNE.js → chunk-LUF52EIY.js} +2 -2
- package/dist/{chunk-5UINJNAJ.js → chunk-LZMAG73U.js} +34 -21
- package/dist/chunk-LZMAG73U.js.map +1 -0
- package/dist/{chunk-VSGMCCGQ.js → chunk-MMSMZC7S.js} +2 -2
- package/dist/chunk-MVKNU4QD.js +37 -0
- package/dist/chunk-MVKNU4QD.js.map +1 -0
- package/dist/{chunk-XPJQXSC4.js → chunk-P7323YVW.js} +27 -5
- package/dist/chunk-P7323YVW.js.map +1 -0
- package/dist/{chunk-KJRVMMM3.js → chunk-PARKXDXI.js} +36 -5
- package/dist/chunk-PARKXDXI.js.map +1 -0
- package/dist/{chunk-4763KQ6O.js → chunk-PXRN2MER.js} +2 -2
- package/dist/{chunk-WMBLR5VA.js → chunk-PZZ6HM5T.js} +3 -3
- package/dist/{chunk-TR3HFH6J.js → chunk-QFSL2L2B.js} +67 -10
- package/dist/chunk-QFSL2L2B.js.map +1 -0
- package/dist/chunk-RQJFGK3A.js +51 -0
- package/dist/chunk-RQJFGK3A.js.map +1 -0
- package/dist/{chunk-3Z6X3BHU.js → chunk-TC4GANMX.js} +53 -150
- package/dist/chunk-TC4GANMX.js.map +1 -0
- package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
- package/dist/chunk-TEMQPSCM.js.map +1 -0
- package/dist/chunk-TJLKWQOX.js +52 -0
- package/dist/chunk-TJLKWQOX.js.map +1 -0
- package/dist/{chunk-CJ2RMTT7.js → chunk-TV76A6GN.js} +2 -2
- package/dist/{chunk-KZRR4NFG.js → chunk-TVZ6QCOE.js} +3 -3
- package/dist/{chunk-BVEDKMNG.js → chunk-U2ZI6G7H.js} +29 -11
- package/dist/chunk-U2ZI6G7H.js.map +1 -0
- package/dist/chunk-UPE6D4AT.js +69 -0
- package/dist/chunk-UPE6D4AT.js.map +1 -0
- package/dist/{chunk-R5IMFAFR.js → chunk-VMGHQUTV.js} +7 -16
- package/dist/chunk-VMGHQUTV.js.map +1 -0
- package/dist/chunk-VQMAAQ5M.js +53 -0
- package/dist/chunk-VQMAAQ5M.js.map +1 -0
- package/dist/{chunk-AVEQUR44.js → chunk-WMF5S2MB.js} +60 -17
- package/dist/chunk-WMF5S2MB.js.map +1 -0
- package/dist/{chunk-SVAAONYB.js → chunk-WQLNUEZL.js} +12 -2
- package/dist/{chunk-SVAAONYB.js.map → chunk-WQLNUEZL.js.map} +1 -1
- package/dist/{chunk-XN37VTDB.js → chunk-X56LAKPR.js} +3 -3
- package/dist/chunk-X56LAKPR.js.map +1 -0
- package/dist/{chunk-ZCL7C5LH.js → chunk-Y6FCXRIC.js} +1661 -387
- package/dist/chunk-Y6FCXRIC.js.map +1 -0
- package/dist/{chunk-2SLED3NB.js → chunk-YDZ7XGPZ.js} +1 -1
- package/dist/{chunk-2SLED3NB.js.map → chunk-YDZ7XGPZ.js.map} +1 -1
- package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
- package/dist/chunk-YJBSB4LP.js.map +1 -0
- package/dist/{chunk-BI2VI6EM.js → chunk-YXWBM26P.js} +2 -2
- package/dist/{chunk-QF6IQQFP.js → chunk-Z7FDJ35L.js} +18 -12
- package/dist/chunk-Z7FDJ35L.js.map +1 -0
- package/dist/cli/bin.js +111 -51
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +33 -3
- package/dist/cli/commands/audit.js +28 -25
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-DGTO65WB.js +11 -0
- package/dist/configs/playwright-a11y.d.ts +11 -1
- package/dist/configs/playwright-a11y.js +3 -1
- package/dist/{convert-to-pnpm-YTFREUJK.js → convert-to-pnpm-KLWA53N5.js} +6 -6
- package/dist/{db-XKZYDCKM.js → db-II5WAK4D.js} +45 -20
- package/dist/db-II5WAK4D.js.map +1 -0
- package/dist/deadletter-G4M3HSVM.js +17 -0
- package/dist/{digest-7UBGUBRK.js → digest-CWKK2DMR.js} +56 -32
- package/dist/digest-CWKK2DMR.js.map +1 -0
- package/dist/{digest-collectors-DJFN5ZDK.js → digest-collectors-CYTN2EUX.js} +17 -12
- package/dist/{digest-state-3KHZF7GD.js → digest-state-ZDPKR7MW.js} +18 -2
- package/dist/digest-state-ZDPKR7MW.js.map +1 -0
- package/dist/ensure-site-2QZHFVQ3.js +70 -0
- package/dist/ensure-site-2QZHFVQ3.js.map +1 -0
- package/dist/ensure-site-JEKV2ODE.js +105 -0
- package/dist/ensure-site-JEKV2ODE.js.map +1 -0
- package/dist/{ensure-site-OAAE5YE2.js → ensure-site-OO4UPWNQ.js} +49 -44
- package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
- package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
- package/dist/{fleet-state-UEVZ3X3J.js → fleet-state-RJROYXKO.js} +19 -7
- package/dist/{forms-notify-target-UC6PF3AE.js → forms-notify-target-X6UEOI2M.js} +19 -15
- package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
- package/dist/{freeze-A6MUCZU4.js → freeze-GUQ43EAU.js} +2 -2
- package/dist/{github-signals-TX5FPYP6.js → github-signals-QIRTHHET.js} +22 -17
- package/dist/github-signals-QIRTHHET.js.map +1 -0
- package/dist/{header-image-PSPT7CUM.js → header-image-H7ZJAV26.js} +15 -18
- package/dist/header-image-H7ZJAV26.js.map +1 -0
- package/dist/{health-endpoint-KVCJYWET.js → health-endpoint-BJ6BKRHF.js} +8 -7
- package/dist/{health-endpoint-KVCJYWET.js.map → health-endpoint-BJ6BKRHF.js.map} +1 -1
- package/dist/{health-mirror-XJAKA3EY.js → health-mirror-KU3G7V2Y.js} +10 -10
- package/dist/{import-airtable-TAKSN4NV.js → import-airtable-UBF5GYAL.js} +3 -3
- package/dist/index.d.ts +158 -419
- package/dist/index.js +91 -46
- package/dist/index.js.map +1 -1
- package/dist/{init-FFE4FCO2.js → init-GOWDA5QS.js} +19 -18
- package/dist/{init-FFE4FCO2.js.map → init-GOWDA5QS.js.map} +1 -1
- package/dist/{launch-UD36AHUN.js → launch-7N3X2MXB.js} +229 -79
- package/dist/launch-7N3X2MXB.js.map +1 -0
- package/dist/{lighthouse-VHI77PFB.js → lighthouse-52WSPZ5D.js} +2 -2
- package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
- package/dist/match-harness-GBCDSVPS.js +417 -0
- package/dist/match-harness-GBCDSVPS.js.map +1 -0
- package/dist/migrate-XZ7U5DFC.js +7 -0
- package/dist/{notify-IISTHNBR.js → notify-K3WVH3DM.js} +4 -4
- package/dist/{onboard-TM2KESGF.js → onboard-JQQIMQW7.js} +6 -6
- package/dist/{orchestrate-HYVQPRCE.js → orchestrate-L2A5BZFY.js} +13 -10
- package/dist/parity-2JGVUCZD.js +14 -0
- package/dist/{pipeline-YROAY6DT.js → pipeline-UUPXOS2J.js} +3 -3
- package/dist/{preflight-RYIP54NY.js → preflight-O7Q6FWHU.js} +35 -11
- package/dist/preflight-O7Q6FWHU.js.map +1 -0
- package/dist/{prismic-ci-6GKKZGB5.js → prismic-ci-NAPWOXXD.js} +39 -9
- package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
- package/dist/{prismic-models-P6FWWVRS.js → prismic-models-LL6ORTQ2.js} +20 -17
- package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
- package/dist/prospect/types.d.ts +72 -0
- package/dist/prospect/types.js +1 -1
- package/dist/{prospect-audit-PRHU53HS.js → prospect-audit-FMPGQIRQ.js} +16 -7
- package/dist/prospect-audit-FMPGQIRQ.js.map +1 -0
- package/dist/{prospect-audits-R3LWF6U7.js → prospect-audits-IZVQY3OQ.js} +4 -2
- package/dist/protection-audit-YXJKFU6O.js +413 -0
- package/dist/protection-audit-YXJKFU6O.js.map +1 -0
- package/dist/recipes/sync-configs.js +3 -3
- package/dist/{render-YGNGTX3W.js → render-MHUUHPLN.js} +36 -9
- package/dist/render-MHUUHPLN.js.map +1 -0
- package/dist/{render-from-row-IFSBHWDH.js → render-from-row-75JTED2Z.js} +6 -4
- package/dist/{renovate-dispatch-U5Q52SKT.js → renovate-dispatch-2IHAZDVA.js} +14 -9
- package/dist/{renovate-dispatch-U5Q52SKT.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
- package/dist/{replay-PCSQOGCZ.js → replay-4FVFOMHH.js} +10 -23
- package/dist/replay-4FVFOMHH.js.map +1 -0
- package/dist/{report-LQS4ECTM.js → report-IAHXN5CW.js} +103 -57
- package/dist/report-IAHXN5CW.js.map +1 -0
- package/dist/{report-mirror-HDTOJE64.js → report-mirror-4QM57LMS.js} +34 -12
- package/dist/report-mirror-4QM57LMS.js.map +1 -0
- package/dist/roster-GDNSIRAK.js +19 -0
- package/dist/roster-GDNSIRAK.js.map +1 -0
- package/dist/{schema-A4WCPIF4.js → schema-KWUQEZ5O.js} +1 -1
- package/dist/schema-KWUQEZ5O.js.map +1 -0
- package/dist/{self-updating-MWXIZLL7.js → self-updating-ZUWPGYUP.js} +8 -7
- package/dist/{self-updating-MWXIZLL7.js.map → self-updating-ZUWPGYUP.js.map} +1 -1
- package/dist/{selftest-VXTHRPRN.js → selftest-G4W3LIBQ.js} +21 -17
- package/dist/{selftest-VXTHRPRN.js.map → selftest-G4W3LIBQ.js.map} +1 -1
- package/dist/site-create-ZU3ACBTO.js +45 -0
- package/dist/site-create-ZU3ACBTO.js.map +1 -0
- package/dist/site-lookup-DTOLJ4EA.js +27 -0
- package/dist/site-lookup-DTOLJ4EA.js.map +1 -0
- package/dist/{site-mirror-WON4FTJ2.js → site-mirror-WR7ACXOV.js} +26 -12
- package/dist/site-mirror-WR7ACXOV.js.map +1 -0
- package/dist/{smoke-suite-7DGITCTZ.js → smoke-suite-WAUPI6EJ.js} +8 -7
- package/dist/{smoke-suite-7DGITCTZ.js.map → smoke-suite-WAUPI6EJ.js.map} +1 -1
- package/dist/{submissions-OVZPVBYG.js → submissions-2SHCFOKD.js} +12 -7
- package/dist/submissions-2SHCFOKD.js.map +1 -0
- package/dist/{submissions-3I3GAQAX.js → submissions-JDZP35JM.js} +49 -9
- package/dist/submissions-JDZP35JM.js.map +1 -0
- package/dist/{svelte-codemods-WSV2FGNG.js → svelte-codemods-VOLOKKFI.js} +6 -6
- package/dist/{sync-K3PKHVLH.js → sync-F4USL4K5.js} +6 -6
- package/dist/{sync-configs-RRIHNLIJ.js → sync-configs-OXSWKVZ2.js} +6 -6
- package/dist/turso-KQKBBQCK.js +24 -0
- package/dist/turso-KQKBBQCK.js.map +1 -0
- package/dist/{upgrade-VTD6V4HS.js → upgrade-EVAL3JTO.js} +6 -6
- package/dist/util/git.d.ts +49 -10
- package/dist/util/git.js +1 -1
- package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
- package/dist/{websites-TB4TCAWM.js → websites-Y6FN2XFD.js} +27 -20
- package/dist/{write-audits-to-airtable-53EVXKMU.js → write-audits-to-airtable-TI3JMWSY.js} +7 -5
- package/package.json +3 -2
- package/dist/airtable-JGCPS7JH.js +0 -9
- package/dist/announce-CLQYX5I4.js.map +0 -1
- package/dist/attachments-NLZIJ5NU.js +0 -9
- package/dist/chunk-2ZAZ7ETT.js.map +0 -1
- package/dist/chunk-3Z6X3BHU.js.map +0 -1
- package/dist/chunk-5EEMTJMT.js.map +0 -1
- package/dist/chunk-5GUJ36TL.js +0 -53
- package/dist/chunk-5GUJ36TL.js.map +0 -1
- package/dist/chunk-5UINJNAJ.js.map +0 -1
- package/dist/chunk-6R56OKJZ.js.map +0 -1
- package/dist/chunk-AVEQUR44.js.map +0 -1
- package/dist/chunk-BVEDKMNG.js.map +0 -1
- package/dist/chunk-BYGPUUCT.js.map +0 -1
- package/dist/chunk-CWXTNPNZ.js.map +0 -1
- package/dist/chunk-DFKBTBO3.js.map +0 -1
- package/dist/chunk-F3JWOPTG.js.map +0 -1
- package/dist/chunk-FSGICFXB.js.map +0 -1
- package/dist/chunk-GASBX52O.js +0 -12
- package/dist/chunk-GASBX52O.js.map +0 -1
- package/dist/chunk-IY6256GK.js.map +0 -1
- package/dist/chunk-KJRVMMM3.js.map +0 -1
- package/dist/chunk-LM2NHRCJ.js.map +0 -1
- package/dist/chunk-QF6IQQFP.js.map +0 -1
- package/dist/chunk-R5IMFAFR.js.map +0 -1
- package/dist/chunk-TO7DJYLZ.js.map +0 -1
- package/dist/chunk-TR3HFH6J.js.map +0 -1
- package/dist/chunk-WJMPCVOR.js.map +0 -1
- package/dist/chunk-WSJ7RO54.js.map +0 -1
- package/dist/chunk-WVNDEYXL.js.map +0 -1
- package/dist/chunk-XHHMM5HX.js.map +0 -1
- package/dist/chunk-XN37VTDB.js.map +0 -1
- package/dist/chunk-XPJQXSC4.js.map +0 -1
- package/dist/chunk-YFAXOD7Z.js +0 -42
- package/dist/chunk-YFAXOD7Z.js.map +0 -1
- package/dist/chunk-YLYXW5PH.js.map +0 -1
- package/dist/chunk-Z2K4JPQ3.js.map +0 -1
- package/dist/chunk-ZCL7C5LH.js.map +0 -1
- package/dist/client-G2WGWPCA.js +0 -11
- package/dist/db-XKZYDCKM.js.map +0 -1
- package/dist/digest-7UBGUBRK.js.map +0 -1
- package/dist/digest-state-3KHZF7GD.js.map +0 -1
- package/dist/ensure-site-OAAE5YE2.js.map +0 -1
- package/dist/fleet-events-cF2Om88w.d.ts +0 -24
- package/dist/forms-notify-target-UC6PF3AE.js.map +0 -1
- package/dist/github-signals-TX5FPYP6.js.map +0 -1
- package/dist/header-image-PSPT7CUM.js.map +0 -1
- package/dist/launch-UD36AHUN.js.map +0 -1
- package/dist/lighthouse-airtable-YMH4UA7R.js +0 -12
- package/dist/match-harness-QRG6XZAR.js +0 -70
- package/dist/match-harness-QRG6XZAR.js.map +0 -1
- package/dist/migrate-Y7R4CESL.js +0 -7
- package/dist/parity-CAJ477PS.js +0 -14
- package/dist/preflight-RYIP54NY.js.map +0 -1
- package/dist/prismic-ci-6GKKZGB5.js.map +0 -1
- package/dist/prismic-models-P6FWWVRS.js.map +0 -1
- package/dist/prospect-audit-PRHU53HS.js.map +0 -1
- package/dist/protection-audit-SJO53MEQ.js +0 -168
- package/dist/protection-audit-SJO53MEQ.js.map +0 -1
- package/dist/render-YGNGTX3W.js.map +0 -1
- package/dist/replay-PCSQOGCZ.js.map +0 -1
- package/dist/report-LQS4ECTM.js.map +0 -1
- package/dist/report-mirror-HDTOJE64.js.map +0 -1
- package/dist/schema-A4WCPIF4.js.map +0 -1
- package/dist/site-mirror-WON4FTJ2.js.map +0 -1
- package/dist/submissions-3I3GAQAX.js.map +0 -1
- package/dist/submissions-OVZPVBYG.js.map +0 -1
- /package/dist/{airtable-JGCPS7JH.js.map → attachments-JFGFQRXQ.js.map} +0 -0
- /package/dist/{blux-4KNXUP2K.js.map → blux-NXQLMS4W.js.map} +0 -0
- /package/dist/{bump-deps-C5G2EFBB.js.map → bump-deps-775VLDYB.js.map} +0 -0
- /package/dist/{chunk-FUWFLAWQ.js.map → chunk-3RLDX3Q2.js.map} +0 -0
- /package/dist/{chunk-S7RYFUIK.js.map → chunk-6PGXKN6F.js.map} +0 -0
- /package/dist/{chunk-HTABUCF4.js.map → chunk-6THEZQLA.js.map} +0 -0
- /package/dist/{chunk-2LYOAB2E.js.map → chunk-HSMN35LU.js.map} +0 -0
- /package/dist/{chunk-BCKEP5S6.js.map → chunk-JHJNNJLG.js.map} +0 -0
- /package/dist/{chunk-ZQV7UQNE.js.map → chunk-LUF52EIY.js.map} +0 -0
- /package/dist/{chunk-VSGMCCGQ.js.map → chunk-MMSMZC7S.js.map} +0 -0
- /package/dist/{chunk-4763KQ6O.js.map → chunk-PXRN2MER.js.map} +0 -0
- /package/dist/{chunk-WMBLR5VA.js.map → chunk-PZZ6HM5T.js.map} +0 -0
- /package/dist/{chunk-CJ2RMTT7.js.map → chunk-TV76A6GN.js.map} +0 -0
- /package/dist/{chunk-KZRR4NFG.js.map → chunk-TVZ6QCOE.js.map} +0 -0
- /package/dist/{chunk-BI2VI6EM.js.map → chunk-YXWBM26P.js.map} +0 -0
- /package/dist/{attachments-NLZIJ5NU.js.map → client-DGTO65WB.js.map} +0 -0
- /package/dist/{convert-to-pnpm-YTFREUJK.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
- /package/dist/{client-G2WGWPCA.js.map → deadletter-G4M3HSVM.js.map} +0 -0
- /package/dist/{digest-collectors-DJFN5ZDK.js.map → digest-collectors-CYTN2EUX.js.map} +0 -0
- /package/dist/{fleet-state-UEVZ3X3J.js.map → fleet-state-RJROYXKO.js.map} +0 -0
- /package/dist/{freeze-A6MUCZU4.js.map → freeze-GUQ43EAU.js.map} +0 -0
- /package/dist/{health-mirror-XJAKA3EY.js.map → health-mirror-KU3G7V2Y.js.map} +0 -0
- /package/dist/{import-airtable-TAKSN4NV.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
- /package/dist/{lighthouse-VHI77PFB.js.map → lighthouse-52WSPZ5D.js.map} +0 -0
- /package/dist/{lighthouse-airtable-YMH4UA7R.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
- /package/dist/{migrate-Y7R4CESL.js.map → migrate-XZ7U5DFC.js.map} +0 -0
- /package/dist/{notify-IISTHNBR.js.map → notify-K3WVH3DM.js.map} +0 -0
- /package/dist/{onboard-TM2KESGF.js.map → onboard-JQQIMQW7.js.map} +0 -0
- /package/dist/{orchestrate-HYVQPRCE.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
- /package/dist/{parity-CAJ477PS.js.map → parity-2JGVUCZD.js.map} +0 -0
- /package/dist/{pipeline-YROAY6DT.js.map → pipeline-UUPXOS2J.js.map} +0 -0
- /package/dist/{prospect-audits-R3LWF6U7.js.map → prospect-audits-IZVQY3OQ.js.map} +0 -0
- /package/dist/{render-from-row-IFSBHWDH.js.map → render-from-row-75JTED2Z.js.map} +0 -0
- /package/dist/{svelte-codemods-WSV2FGNG.js.map → svelte-codemods-VOLOKKFI.js.map} +0 -0
- /package/dist/{sync-K3PKHVLH.js.map → sync-F4USL4K5.js.map} +0 -0
- /package/dist/{sync-configs-RRIHNLIJ.js.map → sync-configs-OXSWKVZ2.js.map} +0 -0
- /package/dist/{upgrade-VTD6V4HS.js.map → upgrade-EVAL3JTO.js.map} +0 -0
- /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
- /package/dist/{websites-TB4TCAWM.js.map → websites-Y6FN2XFD.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-53EVXKMU.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/fleet/site-id.ts
|
|
2
|
+
var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
|
+
var MAX_TIME = 2 ** 48 - 1;
|
|
4
|
+
function encodeUlid(timeMs, random) {
|
|
5
|
+
if (!Number.isInteger(timeMs) || timeMs < 0 || timeMs > MAX_TIME) {
|
|
6
|
+
throw new Error(`encodeUlid: time ${timeMs} is not a 48-bit millisecond timestamp`);
|
|
7
|
+
}
|
|
8
|
+
if (random.length !== 10) {
|
|
9
|
+
throw new Error(`encodeUlid: needs exactly 10 random bytes, got ${random.length}`);
|
|
10
|
+
}
|
|
11
|
+
let time = "";
|
|
12
|
+
let t = timeMs;
|
|
13
|
+
for (let i = 0; i < 10; i++) {
|
|
14
|
+
time = ALPHABET[t % 32] + time;
|
|
15
|
+
t = Math.floor(t / 32);
|
|
16
|
+
}
|
|
17
|
+
let rand = "";
|
|
18
|
+
let buffer = 0;
|
|
19
|
+
let bits = 0;
|
|
20
|
+
for (const byte of random) {
|
|
21
|
+
buffer = buffer << 8 | byte;
|
|
22
|
+
bits += 8;
|
|
23
|
+
while (bits >= 5) {
|
|
24
|
+
bits -= 5;
|
|
25
|
+
rand += ALPHABET[buffer >> bits & 31];
|
|
26
|
+
}
|
|
27
|
+
buffer &= (1 << bits) - 1;
|
|
28
|
+
}
|
|
29
|
+
return time + rand;
|
|
30
|
+
}
|
|
31
|
+
function mintSiteId(now = Date.now()) {
|
|
32
|
+
const random = new Uint8Array(10);
|
|
33
|
+
globalThis.crypto.getRandomValues(random);
|
|
34
|
+
return `site_${encodeUlid(now, random)}`;
|
|
35
|
+
}
|
|
36
|
+
function isAirtableRecordId(id) {
|
|
37
|
+
return id.length > 3 && id.startsWith("rec");
|
|
38
|
+
}
|
|
39
|
+
function skipsAirtableShadow(writer, id) {
|
|
40
|
+
if (isAirtableRecordId(id)) return false;
|
|
41
|
+
console.log(`AIRTABLE_SHADOW skipped=non-rec-id writer=${writer} id=${id}`);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
encodeUlid,
|
|
47
|
+
mintSiteId,
|
|
48
|
+
isAirtableRecordId,
|
|
49
|
+
skipsAirtableShadow
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=chunk-RQJFGK3A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fleet/site-id.ts"],"sourcesContent":["/**\n * Site ids after Airtable (#646 step 3, operator decision 2026-09-17).\n *\n * Two shapes coexist PERMANENTLY:\n *\n * - `rec…` every site that existed before the Turso-native creator. The\n * Airtable record id became the Turso primary key at import\n * (design D1), and it is never rewritten: `submissions.site_id`,\n * `reports.site_id`, `fleet_events`, `spam_screenouts`,\n * `digest_state` keys and event ids all hold it by convention.\n * - `site_<ULID>` every site created since, minted here.\n *\n * Neither is the external handle. The stored unique `sites.slug` is — URLs,\n * `/api/forms/:slug` and deployed sites all use it — so the id is free to be\n * whatever sorts and stays unique.\n *\n * ## Why a local ULID and not a dependency\n *\n * Nothing in the dependency tree implements ULID (checked 2026-09-17: the only\n * id-ish package is a transitive `nanoid`, which is not time-sortable and is not\n * ours to import). The encoding is 30 lines of arithmetic over a spec that has\n * not changed since 2016, this package is published to fleet sites, and adding a\n * runtime dependency for it would cost every consumer an install. The random\n * half comes from `globalThis.crypto` (Web Crypto, present in Node 20+ and in\n * Netlify functions), so the module needs no `node:` import either.\n *\n * Not monotonic within a millisecond: two ids minted in the same ms order by\n * their random half. Sites are created by hand, one command at a time; nothing\n * needs a strict order finer than the millisecond.\n */\n\n/** Crockford base32, the ULID alphabet (no I, L, O, U). */\nconst ALPHABET = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nconst MAX_TIME = 2 ** 48 - 1;\n\n/**\n * Encode a ULID from a millisecond timestamp and 10 random bytes: 10 chars of\n * big-endian time, then 16 chars of the 80 random bits. Pure, so tests can pin it.\n */\nexport function encodeUlid(timeMs: number, random: Uint8Array): string {\n if (!Number.isInteger(timeMs) || timeMs < 0 || timeMs > MAX_TIME) {\n throw new Error(`encodeUlid: time ${timeMs} is not a 48-bit millisecond timestamp`);\n }\n if (random.length !== 10) {\n throw new Error(`encodeUlid: needs exactly 10 random bytes, got ${random.length}`);\n }\n let time = \"\";\n let t = timeMs;\n for (let i = 0; i < 10; i++) {\n time = ALPHABET[t % 32]! + time;\n t = Math.floor(t / 32);\n }\n // 80 bits → 16 five-bit groups. Accumulate MSB-first; the buffer never holds\n // more than 12 bits, so plain numbers stay exact.\n let rand = \"\";\n let buffer = 0;\n let bits = 0;\n for (const byte of random) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n bits -= 5;\n rand += ALPHABET[(buffer >> bits) & 31]!;\n }\n buffer &= (1 << bits) - 1;\n }\n return time + rand;\n}\n\n/** Mint a new site id: `site_<ULID>`. */\nexport function mintSiteId(now: number = Date.now()): string {\n const random = new Uint8Array(10);\n globalThis.crypto.getRandomValues(random);\n return `site_${encodeUlid(now, random)}`;\n}\n\n/** A `rec…` id — the only shape Airtable can address. Deliberately a PREFIX test,\n * the decision's own wording (\"skip non-`rec` ids\"): real Airtable ids are\n * `rec` + 14 alphanumerics, but the suite's fixtures use readable ones like\n * `rec_site_acme`, and in production the only other shape that exists is\n * `site_<ULID>`, which this rejects either way. */\nexport function isAirtableRecordId(id: string): boolean {\n return id.length > 3 && id.startsWith(\"rec\");\n}\n\n/** A site id minted by {@link mintSiteId}. */\nexport function isMintedSiteId(id: string): boolean {\n return /^site_[0-9A-HJKMNP-TV-Z]{26}$/.test(id);\n}\n\n/**\n * The one decision every Airtable SHADOW writer makes before touching Airtable:\n * is this a record Airtable could possibly hold? A `site_` id never is — the site\n * was created in Turso and Airtable has never heard of it — so the write is\n * skipped ON PURPOSE rather than sent to 404.\n *\n * Returns `true` when the caller must skip, after logging exactly one stable,\n * greppable line:\n *\n * AIRTABLE_SHADOW skipped=non-rec-id writer=<name> id=<id>\n *\n * Logged rather than silent because a skip is a decision someone may need to\n * audit: \"why does Airtable not have this site's scores\" should be one grep away.\n * Lives outside `src/reports/airtable/` so the Turso-native creator can use it\n * without importing the layer Phase 6 deletes.\n */\nexport function skipsAirtableShadow(writer: string, id: string): boolean {\n if (isAirtableRecordId(id)) return false;\n console.log(`AIRTABLE_SHADOW skipped=non-rec-id writer=${writer} id=${id}`);\n return true;\n}\n"],"mappings":";AAgCA,IAAM,WAAW;AACjB,IAAM,WAAW,KAAK,KAAK;AAMpB,SAAS,WAAW,QAAgB,QAA4B;AACrE,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,KAAK,SAAS,UAAU;AAChE,UAAM,IAAI,MAAM,oBAAoB,MAAM,wCAAwC;AAAA,EACpF;AACA,MAAI,OAAO,WAAW,IAAI;AACxB,UAAM,IAAI,MAAM,kDAAkD,OAAO,MAAM,EAAE;AAAA,EACnF;AACA,MAAI,OAAO;AACX,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,WAAO,SAAS,IAAI,EAAE,IAAK;AAC3B,QAAI,KAAK,MAAM,IAAI,EAAE;AAAA,EACvB;AAGA,MAAI,OAAO;AACX,MAAI,SAAS;AACb,MAAI,OAAO;AACX,aAAW,QAAQ,QAAQ;AACzB,aAAU,UAAU,IAAK;AACzB,YAAQ;AACR,WAAO,QAAQ,GAAG;AAChB,cAAQ;AACR,cAAQ,SAAU,UAAU,OAAQ,EAAE;AAAA,IACxC;AACA,eAAW,KAAK,QAAQ;AAAA,EAC1B;AACA,SAAO,OAAO;AAChB;AAGO,SAAS,WAAW,MAAc,KAAK,IAAI,GAAW;AAC3D,QAAM,SAAS,IAAI,WAAW,EAAE;AAChC,aAAW,OAAO,gBAAgB,MAAM;AACxC,SAAO,QAAQ,WAAW,KAAK,MAAM,CAAC;AACxC;AAOO,SAAS,mBAAmB,IAAqB;AACtD,SAAO,GAAG,SAAS,KAAK,GAAG,WAAW,KAAK;AAC7C;AAuBO,SAAS,oBAAoB,QAAgB,IAAqB;AACvE,MAAI,mBAAmB,EAAE,EAAG,QAAO;AACnC,UAAQ,IAAI,6CAA6C,MAAM,OAAO,EAAE,EAAE;AAC1E,SAAO;AACT;","names":[]}
|
|
@@ -1,87 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SEVERITY_RANK,
|
|
3
|
+
canonicalizeStatus,
|
|
4
|
+
parseNotifyRouting,
|
|
5
|
+
parseSecurityAdvisories,
|
|
6
|
+
siteSlug,
|
|
7
|
+
toFrequency,
|
|
8
|
+
toPrismicModelsVerdict,
|
|
9
|
+
toVerdict,
|
|
10
|
+
trimToNull
|
|
11
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
12
|
+
import {
|
|
13
|
+
skipsAirtableShadow
|
|
14
|
+
} from "./chunk-RQJFGK3A.js";
|
|
15
|
+
|
|
1
16
|
// src/reports/airtable/site-status.ts
|
|
2
|
-
var CANONICAL_STATUSES = [
|
|
3
|
-
"building",
|
|
4
|
-
"launching",
|
|
5
|
-
"maintained",
|
|
6
|
-
"hosted-only",
|
|
7
|
-
"external",
|
|
8
|
-
"archived"
|
|
9
|
-
];
|
|
10
|
-
var CANONICAL_SET = new Set(CANONICAL_STATUSES);
|
|
11
|
-
function canonicalizeStatus(raw) {
|
|
12
|
-
if (typeof raw !== "string") return null;
|
|
13
|
-
return raw;
|
|
14
|
-
}
|
|
15
17
|
function toAirtableStatus(s) {
|
|
16
18
|
return s;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// src/reports/airtable/websites.ts
|
|
20
22
|
var WEBSITES_TABLE = "Websites";
|
|
21
|
-
function siteSlug(name) {
|
|
22
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
23
|
-
}
|
|
24
|
-
function trimToNull(raw) {
|
|
25
|
-
if (typeof raw !== "string") return null;
|
|
26
|
-
const trimmed = raw.trim();
|
|
27
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
28
|
-
}
|
|
29
|
-
function parseNotifyRouting(raw) {
|
|
30
|
-
if (typeof raw !== "string") return null;
|
|
31
|
-
const trimmed = raw.trim();
|
|
32
|
-
if (!trimmed) return null;
|
|
33
|
-
let parsed;
|
|
34
|
-
try {
|
|
35
|
-
parsed = JSON.parse(trimmed);
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
40
|
-
const o = parsed;
|
|
41
|
-
if (typeof o.field !== "string" || !o.field.trim()) return null;
|
|
42
|
-
if (!o.routes || typeof o.routes !== "object" || Array.isArray(o.routes)) return null;
|
|
43
|
-
const routing = {
|
|
44
|
-
field: o.field,
|
|
45
|
-
routes: o.routes
|
|
46
|
-
};
|
|
47
|
-
if (o.default !== void 0) routing.default = o.default;
|
|
48
|
-
if (Array.isArray(o.cc)) routing.cc = o.cc.filter((x) => typeof x === "string");
|
|
49
|
-
return routing;
|
|
50
|
-
}
|
|
51
|
-
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["maintained", "launching"]);
|
|
52
|
-
function isDashboardVisible(site) {
|
|
53
|
-
return site.status !== null && ACTIVE_STATUSES.has(site.status);
|
|
54
|
-
}
|
|
55
|
-
var PRE_LAUNCH_STATUSES = /* @__PURE__ */ new Set(["building", "launching"]);
|
|
56
|
-
function isPreLaunch(status) {
|
|
57
|
-
return status !== null && PRE_LAUNCH_STATUSES.has(status);
|
|
58
|
-
}
|
|
59
|
-
var KNOWN_STATUSES = new Set(CANONICAL_STATUSES);
|
|
60
|
-
var ARCHIVED_STATUSES = /* @__PURE__ */ new Set(["archived"]);
|
|
61
|
-
function isArchivedStatus(status) {
|
|
62
|
-
return status !== null && ARCHIVED_STATUSES.has(status);
|
|
63
|
-
}
|
|
64
|
-
function isUnrecognizedStatus(status) {
|
|
65
|
-
return status !== null && !KNOWN_STATUSES.has(status);
|
|
66
|
-
}
|
|
67
|
-
var FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
|
|
68
|
-
function toFrequency(raw, context) {
|
|
69
|
-
if (typeof raw !== "string") return "None";
|
|
70
|
-
const trimmed = raw.trim();
|
|
71
|
-
if (FREQUENCIES.includes(trimmed)) return trimmed;
|
|
72
|
-
if (trimmed !== "") {
|
|
73
|
-
console.warn(
|
|
74
|
-
`\u26A0 ${context}: unrecognized frequency '${raw}' \u2014 treating as None (not scheduling); fix the Airtable value`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return "None";
|
|
78
|
-
}
|
|
79
|
-
function toVerdict(raw) {
|
|
80
|
-
return raw === "pass" || raw === "fail" ? raw : null;
|
|
81
|
-
}
|
|
82
|
-
function toPrismicModelsVerdict(raw) {
|
|
83
|
-
return raw === "pass" || raw === "fail" || raw === "unknown" ? raw : null;
|
|
84
|
-
}
|
|
85
23
|
function mapRow(rec) {
|
|
86
24
|
const f = rec.fields;
|
|
87
25
|
const name = String(f["Name"] ?? "");
|
|
@@ -224,51 +162,7 @@ function depsFields(counts) {
|
|
|
224
162
|
}
|
|
225
163
|
return fields;
|
|
226
164
|
}
|
|
227
|
-
var SEVERITY_RANK = {
|
|
228
|
-
critical: 0,
|
|
229
|
-
high: 1,
|
|
230
|
-
moderate: 2,
|
|
231
|
-
low: 3
|
|
232
|
-
};
|
|
233
165
|
var MAX_PERSISTED_ADVISORIES = 25;
|
|
234
|
-
function normalizeSecurityAdvisory(raw) {
|
|
235
|
-
if (!raw || typeof raw !== "object") return null;
|
|
236
|
-
const e = raw;
|
|
237
|
-
const module = typeof e["module"] === "string" ? e["module"] : null;
|
|
238
|
-
const severity = e["severity"];
|
|
239
|
-
if (module === null) return null;
|
|
240
|
-
if (severity !== "low" && severity !== "moderate" && severity !== "high" && severity !== "critical")
|
|
241
|
-
return null;
|
|
242
|
-
const cves = Array.isArray(e["cves"]) ? e["cves"].filter((c) => typeof c === "string") : [];
|
|
243
|
-
const scope = e["scope"] === "runtime" || e["scope"] === "development" ? e["scope"] : void 0;
|
|
244
|
-
const relationship = e["relationship"] === "direct" || e["relationship"] === "transitive" ? e["relationship"] : void 0;
|
|
245
|
-
return {
|
|
246
|
-
module,
|
|
247
|
-
severity,
|
|
248
|
-
title: typeof e["title"] === "string" ? e["title"] : "",
|
|
249
|
-
cves,
|
|
250
|
-
url: typeof e["url"] === "string" ? e["url"] : null,
|
|
251
|
-
...scope ? { scope } : {},
|
|
252
|
-
...relationship ? { relationship } : {}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function allActionableVulnsTransitive(advisories) {
|
|
256
|
-
if (!advisories) return false;
|
|
257
|
-
const actionable = advisories.filter((a) => a.severity === "critical" || a.severity === "high");
|
|
258
|
-
if (actionable.length === 0) return false;
|
|
259
|
-
return actionable.every((a) => a.relationship === "transitive");
|
|
260
|
-
}
|
|
261
|
-
function parseSecurityAdvisories(raw) {
|
|
262
|
-
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
263
|
-
let parsed;
|
|
264
|
-
try {
|
|
265
|
-
parsed = JSON.parse(raw);
|
|
266
|
-
} catch {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
if (!Array.isArray(parsed)) return null;
|
|
270
|
-
return parsed.map(normalizeSecurityAdvisory).filter((a) => a !== null);
|
|
271
|
-
}
|
|
272
166
|
function securityAdvisoryFields(advisories) {
|
|
273
167
|
const capped = [...advisories].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]).slice(0, MAX_PERSISTED_ADVISORIES);
|
|
274
168
|
return { "Security advisories": JSON.stringify(capped) };
|
|
@@ -342,25 +236,37 @@ function formE2eFields(r) {
|
|
|
342
236
|
return fields;
|
|
343
237
|
}
|
|
344
238
|
async function updateScores(base, recordId, scores) {
|
|
239
|
+
if (skipsAirtableShadow("updateScores", recordId)) return;
|
|
345
240
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields: scoreFields(scores) }]);
|
|
346
241
|
}
|
|
347
|
-
|
|
242
|
+
function analyticsHealthFields(at) {
|
|
348
243
|
const fields = { "Analytics soft-fail at": at };
|
|
349
|
-
|
|
244
|
+
return fields;
|
|
245
|
+
}
|
|
246
|
+
async function updateAnalyticsHealth(base, recordId, at) {
|
|
247
|
+
const fields = analyticsHealthFields(at);
|
|
248
|
+
if (!skipsAirtableShadow("updateAnalyticsHealth", recordId)) {
|
|
249
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
250
|
+
}
|
|
350
251
|
return fields;
|
|
351
252
|
}
|
|
352
253
|
async function updateA11yCounts(base, recordId, counts) {
|
|
254
|
+
if (skipsAirtableShadow("updateA11yCounts", recordId)) return;
|
|
353
255
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields: a11yFields(counts) }]);
|
|
354
256
|
}
|
|
355
257
|
async function updateDepsCounts(base, recordId, counts) {
|
|
258
|
+
if (skipsAirtableShadow("updateDepsCounts", recordId)) return;
|
|
356
259
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields: depsFields(counts) }]);
|
|
357
260
|
}
|
|
358
261
|
async function updateSecurityCounts(base, recordId, counts) {
|
|
262
|
+
if (skipsAirtableShadow("updateSecurityCounts", recordId)) return;
|
|
359
263
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields: securityFields(counts) }]);
|
|
360
264
|
}
|
|
361
265
|
async function updateAutoFixAttempts(base, recordId, attempts) {
|
|
362
266
|
const fields = { "Security Auto-Fix Attempts": attempts };
|
|
363
|
-
|
|
267
|
+
if (!skipsAirtableShadow("updateAutoFixAttempts", recordId)) {
|
|
268
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
269
|
+
}
|
|
364
270
|
return fields;
|
|
365
271
|
}
|
|
366
272
|
async function updateNextDueDates(base, recordId, dates) {
|
|
@@ -368,12 +274,19 @@ async function updateNextDueDates(base, recordId, dates) {
|
|
|
368
274
|
"Next maintenance at": dates.maintenanceAt,
|
|
369
275
|
"Next testing at": dates.testingAt
|
|
370
276
|
};
|
|
371
|
-
|
|
277
|
+
if (!skipsAirtableShadow("updateNextDueDates", recordId)) {
|
|
278
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
279
|
+
}
|
|
372
280
|
return fields;
|
|
373
281
|
}
|
|
374
282
|
async function updateSiteField(base, recordId, column, value) {
|
|
283
|
+
if (skipsAirtableShadow("updateSiteField", recordId)) return;
|
|
375
284
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields: { [column]: value } }]);
|
|
376
285
|
}
|
|
286
|
+
async function updateSiteFields(base, recordId, fields) {
|
|
287
|
+
if (skipsAirtableShadow("updateSiteFields", recordId)) return;
|
|
288
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
289
|
+
}
|
|
377
290
|
async function updateAuditFields(base, recordId, audits) {
|
|
378
291
|
const fields = {};
|
|
379
292
|
if (audits.scores) Object.assign(fields, scoreFields(audits.scores));
|
|
@@ -388,7 +301,9 @@ async function updateAuditFields(base, recordId, audits) {
|
|
|
388
301
|
if (audits.functionHealth) Object.assign(fields, functionHealthFields(audits.functionHealth));
|
|
389
302
|
if (audits.smoke) Object.assign(fields, smokeFields(audits.smoke));
|
|
390
303
|
if (audits.formE2e) Object.assign(fields, formE2eFields(audits.formE2e));
|
|
391
|
-
|
|
304
|
+
if (!skipsAirtableShadow("updateAuditFields", recordId)) {
|
|
305
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
306
|
+
}
|
|
392
307
|
return fields;
|
|
393
308
|
}
|
|
394
309
|
async function updateGitHubSignals(base, recordId, signals) {
|
|
@@ -400,7 +315,9 @@ async function updateGitHubSignals(base, recordId, signals) {
|
|
|
400
315
|
if (signals.lastCommitAt !== null) {
|
|
401
316
|
fields["Last Commit At"] = signals.lastCommitAt;
|
|
402
317
|
}
|
|
403
|
-
|
|
318
|
+
if (!skipsAirtableShadow("updateGitHubSignals", recordId)) {
|
|
319
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
320
|
+
}
|
|
404
321
|
return fields;
|
|
405
322
|
}
|
|
406
323
|
var MAX_PRISMIC_DETAIL_CHARS = 5e4;
|
|
@@ -419,42 +336,27 @@ async function updatePrismicModels(base, recordId, models) {
|
|
|
419
336
|
"Prismic Models Checked At": models.checkedAt,
|
|
420
337
|
"Prismic Models Drift": models.detail === null ? null : truncatePrismicDetail(models.detail)
|
|
421
338
|
};
|
|
422
|
-
|
|
339
|
+
if (!skipsAirtableShadow("updatePrismicModels", recordId)) {
|
|
340
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
341
|
+
}
|
|
423
342
|
return fields;
|
|
424
343
|
}
|
|
425
344
|
async function updateLaunched(base, recordId, at) {
|
|
426
345
|
const fields = { Status: toAirtableStatus("maintained"), "Launched at": at };
|
|
427
|
-
|
|
346
|
+
if (!skipsAirtableShadow("updateLaunched", recordId)) {
|
|
347
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
348
|
+
}
|
|
428
349
|
return fields;
|
|
429
350
|
}
|
|
430
351
|
|
|
431
352
|
export {
|
|
432
|
-
CANONICAL_STATUSES,
|
|
433
|
-
canonicalizeStatus,
|
|
434
353
|
toAirtableStatus,
|
|
435
354
|
WEBSITES_TABLE,
|
|
436
|
-
siteSlug,
|
|
437
|
-
trimToNull,
|
|
438
|
-
parseNotifyRouting,
|
|
439
|
-
ACTIVE_STATUSES,
|
|
440
|
-
isDashboardVisible,
|
|
441
|
-
PRE_LAUNCH_STATUSES,
|
|
442
|
-
isPreLaunch,
|
|
443
|
-
KNOWN_STATUSES,
|
|
444
|
-
ARCHIVED_STATUSES,
|
|
445
|
-
isArchivedStatus,
|
|
446
|
-
isUnrecognizedStatus,
|
|
447
|
-
toFrequency,
|
|
448
|
-
toVerdict,
|
|
449
|
-
toPrismicModelsVerdict,
|
|
450
355
|
mapRow,
|
|
451
356
|
listWebsites,
|
|
452
357
|
getWebsiteBySlug,
|
|
453
|
-
SEVERITY_RANK,
|
|
454
|
-
normalizeSecurityAdvisory,
|
|
455
|
-
allActionableVulnsTransitive,
|
|
456
|
-
parseSecurityAdvisories,
|
|
457
358
|
updateScores,
|
|
359
|
+
analyticsHealthFields,
|
|
458
360
|
updateAnalyticsHealth,
|
|
459
361
|
updateA11yCounts,
|
|
460
362
|
updateDepsCounts,
|
|
@@ -462,9 +364,10 @@ export {
|
|
|
462
364
|
updateAutoFixAttempts,
|
|
463
365
|
updateNextDueDates,
|
|
464
366
|
updateSiteField,
|
|
367
|
+
updateSiteFields,
|
|
465
368
|
updateAuditFields,
|
|
466
369
|
updateGitHubSignals,
|
|
467
370
|
updatePrismicModels,
|
|
468
371
|
updateLaunched
|
|
469
372
|
};
|
|
470
|
-
//# sourceMappingURL=chunk-
|
|
373
|
+
//# sourceMappingURL=chunk-TC4GANMX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reports/airtable/site-status.ts","../src/reports/airtable/websites.ts"],"sourcesContent":["/**\n * The Airtable WRITE seam for the site-status vocabulary.\n *\n * The vocabulary itself — `Status`, `CANONICAL_STATUSES`, `canonicalizeStatus`,\n * `isCanonicalStatus` — moved to `src/fleet/site-status.ts` in #539 Phase 6 step 1\n * (#646): `canonicalizeStatus` runs on every Turso lead read, so it cannot live in\n * the directory Phase 6 deletes. It is re-exported here so existing Airtable-layer\n * imports keep resolving unchanged; only `toAirtableStatus`, which exists solely\n * to write an Airtable cell, stays behind and goes with the layer.\n */\nimport type { Status } from \"../../fleet/site-status.js\";\n\nexport {\n CANONICAL_STATUSES,\n canonicalizeStatus,\n isCanonicalStatus,\n type Status,\n} from \"../../fleet/site-status.js\";\n\n/**\n * The string to WRITE into the Airtable \"Status\" cell for a canonical status.\n * Every writer of a CODE-OWNED status routes through here.\n *\n * Since stage 3 this is the identity: Airtable's single-select carries exactly\n * the six canonical options, so the canonical name IS the option name. It is\n * kept as a named seam rather than inlined because it is the one place a future\n * divergence between the code's vocabulary and Airtable's would be expressed,\n * and because every writer already routes through it — inlining would scatter\n * that decision across `ensureSite`, `updateLaunched`, `forms-notify-target`\n * and the dashboard status save.\n *\n * The stage-1/2 hazard it used to carry is GONE: while a many-to-one map\n * existed, `toAirtableStatus(canonicalizeStatus(x))` was not the identity —\n * feeding it \"legacy\" yielded \"deprecated\", silently writing an option the\n * operator never asked for. Both functions are now the identity, so that trap\n * no longer exists. `forms-notify-target --restore` still writes operator text\n * verbatim (`restoreCell`) rather than routing through here, which remains the\n * right shape: operator free text is not a canonical status.\n */\nexport function toAirtableStatus(s: Status): string {\n return s;\n}\n","import type { FieldSet } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport type { LighthouseScores, LighthouseScoreWriteback } from \"../types.js\";\nimport { canonicalizeStatus, toAirtableStatus } from \"./site-status.js\";\nimport { skipsAirtableShadow } from \"../../fleet/site-id.js\";\nimport {\n siteSlug,\n trimToNull,\n parseNotifyRouting,\n toFrequency,\n toVerdict,\n toPrismicModelsVerdict,\n SEVERITY_RANK,\n parseSecurityAdvisories,\n type WebsiteRow,\n type PrismicModelsVerdict,\n type SecurityAdvisory,\n} from \"../../fleet/site-row.js\";\n\n// The site-row model and its pure coercers live in src/fleet/site-row.ts since\n// #539 Phase 6 step 1 (#646) — they run on Turso reads too. Re-exported so every\n// existing import from this module resolves unchanged.\nexport {\n siteSlug,\n trimToNull,\n parseNotifyRouting,\n ACTIVE_STATUSES,\n isDashboardVisible,\n PRE_LAUNCH_STATUSES,\n isPreLaunch,\n KNOWN_STATUSES,\n ARCHIVED_STATUSES,\n isArchivedStatus,\n isUnrecognizedStatus,\n toFrequency,\n toVerdict,\n toPrismicModelsVerdict,\n SEVERITY_RANK,\n normalizeSecurityAdvisory,\n allActionableVulnsTransitive,\n parseSecurityAdvisories,\n type Frequency,\n type Status,\n type NotifyRouting,\n type WebsiteRow,\n type PrismicModelsVerdict,\n type Severity,\n type SecurityAdvisory,\n} from \"../../fleet/site-row.js\";\n\nexport const WEBSITES_TABLE = \"Websites\";\n\n// NOTE: every `f[\"...\"]` key below is a load-bearing magic string that must match\n// the live Airtable \"Websites\" column name EXACTLY — including the legacy\n// misspelling `\"maintenence freq\"`, the mixed-case `\"GA4 property ID\"`, and the\n// lowercase `\"url\"` / `\"point of contact\"`. A column rename in Airtable silently\n// returns undefined here (→ null), which degrades quietly (GA skipped, recipients\n// empty) with no error. If you rename a column, change it here too.\nexport function mapRow(rec: { id: string; fields: Record<string, unknown> }): WebsiteRow {\n const f = rec.fields;\n const name = String(f[\"Name\"] ?? \"\");\n const attachments =\n (f[\"Header image\"] as Array<{ url: string; filename: string; type: string }> | undefined) ?? [];\n // LAST, not first: Airtable's uploadAttachment APPENDS, so the newest file is the\n // tail. Reading [0] served the OLDEST forever whenever a field held more than one —\n // which is how a pre-clean-plate header reached a live announcement (#574/#577).\n // The prune keeps these fields at a single entry, so this is normally the same\n // element; preferring the tail means a field that stacks again still serves the\n // CURRENT header instead of silently reverting to the first one ever uploaded.\n const header = attachments.at(-1) ?? null;\n return {\n id: rec.id,\n name,\n url: String(f[\"url\"] ?? \"\"),\n status: canonicalizeStatus(f[\"Status\"]),\n statusRaw: (f[\"Status\"] as string | undefined) ?? null,\n pointOfContact: (f[\"point of contact\"] as string | undefined) ?? null,\n maintenanceFreq: toFrequency(f[\"maintenence freq\"], `${name} maintenance`),\n testingFreq: toFrequency(f[\"testing freq\"], `${name} testing`),\n maintenanceFreqRaw: (f[\"maintenence freq\"] as string | undefined) ?? null,\n testingFreqRaw: (f[\"testing freq\"] as string | undefined) ?? null,\n maintenanceDay: (f[\"maintenance day\"] as string | undefined) ?? null,\n testingDay: (f[\"testing day\"] as string | undefined) ?? null,\n ga4PropertyId: (f[\"GA4 property ID\"] as string | undefined) ?? null,\n searchQuery: (f[\"Search query\"] as string | undefined) ?? null,\n searchConsoleProperty: (f[\"Search Console property\"] as string | undefined) ?? null,\n analyticsSoftFailAt: (f[\"Analytics soft-fail at\"] as string | undefined) ?? null,\n gitRepo: (f[\"Git repo\"] as string | undefined) ?? null,\n reportRecipientsTo: (f[\"Report recipients (To)\"] as string | undefined) ?? null,\n reportRecipientsCc: (f[\"Report recipients (CC)\"] as string | undefined) ?? null,\n // Tolerate BOTH the current Multiple-Select array shape AND a delimited long-text\n // string (comma/newline separated), so the field can migrate to a plain text column\n // with no code change here. Trim + drop empties either way. The array branch also\n // validates ELEMENT types: a collaborator/attachment-shaped field passes\n // Array.isArray with OBJECT elements, and an unchecked cast would make assignTier's\n // `.trim()` throw — one misconfigured row must not 500 the whole cockpit build.\n acceptedWatchConditions: Array.isArray(f[\"Accepted Watch Conditions\"])\n ? (f[\"Accepted Watch Conditions\"] as unknown[])\n .filter((x): x is string => typeof x === \"string\")\n .map((s) => s.trim())\n .filter(Boolean)\n : typeof f[\"Accepted Watch Conditions\"] === \"string\"\n ? (f[\"Accepted Watch Conditions\"] as string)\n .split(/[\\n,]/)\n .map((s) => s.trim())\n .filter(Boolean)\n : [],\n headerImage: header,\n pScore: (f[\"pScore\"] as number | undefined) ?? null,\n rScore: (f[\"rScore\"] as number | undefined) ?? null,\n bpScore: (f[\"bpScore\"] as number | undefined) ?? null,\n seoScore: (f[\"seoScore\"] as number | undefined) ?? null,\n lastLighthouseAuditAt: (f[\"Last lighthouse audit at\"] as string | undefined) ?? null,\n a11yViolations: (f[\"A11y Violations\"] as number | undefined) ?? null,\n depsDrifted: (f[\"Deps Drifted\"] as number | undefined) ?? null,\n depsMajorBehind: (f[\"Deps Major Behind\"] as number | undefined) ?? null,\n depsOutdated: (f[\"Deps Outdated\"] as number | undefined) ?? null,\n depsMajorOutdated: (f[\"Deps Major Outdated\"] as number | undefined) ?? null,\n securityVulnsCritical: (f[\"Security Vulns Critical\"] as number | undefined) ?? null,\n securityVulnsHigh: (f[\"Security Vulns High\"] as number | undefined) ?? null,\n securityVulnsModerate: (f[\"Security Vulns Moderate\"] as number | undefined) ?? null,\n securityVulnsLow: (f[\"Security Vulns Low\"] as number | undefined) ?? null,\n securityAutoFixAttempts: (f[\"Security Auto-Fix Attempts\"] as number | undefined) ?? null,\n lastSecurityAuditAt: (f[\"Last security audit at\"] as string | undefined) ?? null,\n securityAdvisories: parseSecurityAdvisories(f[\"Security advisories\"]),\n certDaysRemaining: (f[\"Cert days remaining\"] as number | undefined) ?? null,\n domainCheckedAt: (f[\"Domain checked at\"] as string | undefined) ?? null,\n netlifyId: trimToNull(f[\"Netlify ID\"]),\n deployStatus: (f[\"Deploy status\"] as string | undefined) ?? null,\n lastDeployAt: (f[\"Last deploy at\"] as string | undefined) ?? null,\n deployLogUrl: (f[\"Deploy log URL\"] as string | undefined) ?? null,\n deployCheckedAt: (f[\"Deploy checked at\"] as string | undefined) ?? null,\n functionHealth: toVerdict(f[\"Function health\"]),\n cmsReachable: toVerdict(f[\"CMS Reachable\"]),\n turnstileWidget: toVerdict(f[\"Turnstile widget\"]),\n functionHealthCheckedAt: (f[\"Function health checked at\"] as string | undefined) ?? null,\n crossbrowserOk:\n typeof f[\"Crossbrowser OK\"] === \"boolean\" ? (f[\"Crossbrowser OK\"] as boolean) : null,\n mobileOk: typeof f[\"Mobile OK\"] === \"boolean\" ? (f[\"Mobile OK\"] as boolean) : null,\n linksOk: typeof f[\"Links OK\"] === \"boolean\" ? (f[\"Links OK\"] as boolean) : null,\n brokenLinks: typeof f[\"Broken links\"] === \"number\" ? (f[\"Broken links\"] as number) : null,\n browserCheckedAt: (f[\"Browser checked at\"] as string | undefined) ?? null,\n reachableOk: toVerdict(f[\"Uptime Reachable\"]),\n titleMetaOk: toVerdict(f[\"Titles & Meta OK\"]),\n copyIntro: trimToNull(f[\"Copy — Intro\"]),\n copyContact: trimToNull(f[\"Copy — Contact\"]),\n copyFooter: trimToNull(f[\"Copy — Footer\"]),\n launchedAt: (f[\"Launched at\"] as string | undefined) ?? null,\n newsletterWebhook: trimToNull(f[\"Newsletter Webhook\"]),\n notifyRouting: parseNotifyRouting(f[\"Notify Routing\"]),\n notifyRoutingRaw: trimToNull(f[\"Notify Routing\"]),\n mailchimpApiKey: trimToNull(f[\"Mailchimp API Key\"]),\n mailchimpAudienceId: trimToNull(f[\"Mailchimp Audience ID\"]),\n // Boolean guard like crossbrowserOk, but defaults FALSE (not null) when absent: an\n // unset/unknown column must read as \"not required\" so the feature ships dark.\n requireTurnstile:\n typeof f[\"Require Turnstile\"] === \"boolean\" ? (f[\"Require Turnstile\"] as boolean) : false,\n renovateFailingCis: (f[\"Renovate Failing CIs\"] as number | undefined) ?? null,\n defaultBranchCi: (f[\"Default Branch CI\"] as string | undefined) ?? null,\n lastCommitAt: (f[\"Last Commit At\"] as string | undefined) ?? null,\n githubSignalsAt: (f[\"GitHub Signals At\"] as string | undefined) ?? null,\n smokeOk: toVerdict(f[\"Smoke OK\"]),\n lastSmokeAt: (f[\"Last Smoke At\"] as string | undefined) ?? null,\n formE2eOk: toVerdict(f[\"Form E2E OK\"]),\n formE2eCheckedAt: (f[\"Form E2E checked at\"] as string | undefined) ?? null,\n prismicModels: toPrismicModelsVerdict(f[\"Prismic Models\"]),\n prismicModelsCheckedAt: (f[\"Prismic Models Checked At\"] as string | undefined) ?? null,\n prismicModelsDrift: (f[\"Prismic Models Drift\"] as string | undefined) ?? null,\n prismicAckUntil: (f[\"Prismic Ack Until\"] as string | undefined) ?? null,\n nextMaintenanceAt: (f[\"Next maintenance at\"] as string | undefined) ?? null,\n nextTestingAt: (f[\"Next testing at\"] as string | undefined) ?? null,\n };\n}\n\nexport async function listWebsites(base: AirtableBase): Promise<WebsiteRow[]> {\n const out: WebsiteRow[] = [];\n await base(WEBSITES_TABLE)\n .select({ pageSize: 100 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\nexport async function getWebsiteBySlug(\n base: AirtableBase,\n slug: string,\n): Promise<WebsiteRow | null> {\n // Slugs are siteSlug() output: [a-z0-9] segments joined by single hyphens.\n // Reject anything else — it can't match a real row, and it keeps URL-supplied\n // input out of the filter formula below (formula-injection guard).\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) return null;\n\n // Narrow the fetch to the slug-matching row server-side instead of paging the\n // whole table per request (MEDIUM-H). The formula replicates siteSlug() on\n // {Name} — lowercase → non-alnum runs to \"-\" → strip leading/trailing \"-\" —\n // verified against the live base. maxRecords caps it (slug collisions keep the\n // prior first-match-wins behavior).\n const formula = `REGEX_REPLACE(REGEX_REPLACE(LOWER({Name}),\"[^a-z0-9]+\",\"-\"),\"^-|-$\",\"\")=${JSON.stringify(\n slug,\n )}`;\n const rows: WebsiteRow[] = [];\n await base(WEBSITES_TABLE)\n .select({ filterByFormula: formula, maxRecords: 1 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) rows.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n // Confirm the match in JS too: keeps the function correct if the formula and\n // siteSlug() ever drift, and under test fakes that don't evaluate the formula.\n return rows.find((w) => siteSlug(w.name) === slug) ?? null;\n}\n\n// ── audit-field builders ─────────────────────────────────────────────────────\n// One source of truth for the column-name → value mappings of each audit type.\n// The per-audit `updateXxxCounts` writers delegate to these (for their other\n// callers), and `updateAuditFields` merges whichever are present into ONE write —\n// so the field-name magic strings live in exactly one place.\n\nexport type A11yCounts = { violations: number };\nexport type DepsCounts = {\n drifted: number;\n majorBehind: number;\n outdated: number | null;\n majorOutdated: number | null;\n};\nexport type SecurityCounts = { critical: number; high: number; moderate: number; low: number };\nexport type DomainResult = { certDaysRemaining: number | null; checkedAt: string };\nexport type NetlifyDeployResult = {\n /** Netlify deploy state lower-cased (`ready`/`error`/`building`/…), null = none. */\n state: string | null;\n /** When the latest production deploy was published/created (ISO), null = unknown. */\n deployedAt: string | null;\n /** Link to the deploy / its build log, null = unknown. */\n logUrl: string | null;\n /** When the audit last ran. */\n checkedAt: string;\n};\nexport type BrowserAuditFields = {\n desktopOk: boolean;\n mobileOk: boolean;\n linksOk: boolean;\n reachableOk: boolean;\n titleMetaOk: boolean;\n brokenLinks: number;\n checkedAt: string;\n};\nexport type FunctionHealthResult = {\n /** `pass` when `/health` answered `ok:true`, else `fail`. Never null — the audit only produces a\n * result when it ran (a self-skip carries no details, so this extractor isn't reached). */\n functionHealth: \"pass\" | \"fail\";\n /** From the same body's `prismic` sub-status (R2.2): `\"ok\"` → `\"pass\"`, `\"error\"` → `\"fail\"`.\n * Anything else (`\"skipped\"` — a placeholder repo with no live Prismic — or a raw `null`, e.g.\n * the synthetic \"deployed but erroring\" body) → `null`: the CMS probe never actually ran, so it\n * must NOT red CMS reachability for a site that simply hasn't wired Prismic yet. */\n cmsReachable: \"pass\" | \"fail\" | null;\n /** From the same body's `forms.turnstile` boolean: `true` → `\"pass\"`, `false` → `\"fail\"`.\n * A missing/null `forms` block (older site package, or the synthetic \"deployed but\n * erroring\" body) → `null` — the widget state is simply unknown, not a failure. */\n turnstileWidget: \"pass\" | \"fail\" | null;\n /** When the audit ran (freshness stamp for all three verdicts). */\n checkedAt: string;\n};\n\nexport type SmokeResult = { ok: \"pass\" | \"fail\"; checkedAt: string };\n\n/** `ok` null clears the single-select cell (n/a — no contact form); a fresh\n * `checkedAt` still stamps the row so Plan 4 reads null+fresh as n/a. */\nexport type FormE2eResult = {\n /** Absent = no FORM verdict this run; the column and its stamp are left alone. */\n ok?: \"pass\" | \"fail\" | null;\n checkedAt: string;\n /** The `Turnstile widget` verdict, owned by this audit since #689 — see\n * `formE2eFields`. Absent = no opinion this run (preserve); null = looked and\n * could not tell (clear). */\n turnstileWidget?: \"pass\" | \"fail\" | null;\n};\n\nfunction scoreFields(scores: LighthouseScoreWriteback): FieldSet {\n // A null score CLEARS the cell (→ dashboard \"—\"), distinguishing a metric that\n // errored this run (e.g. NO_LCP → null performance) from a real low score. This\n // intentionally overwrites a prior value: a run that couldn't measure the metric\n // shouldn't keep showing a stale number. Writing null to clear mirrors\n // updateAnalyticsHealth; the `as FieldSet` cast is needed because airtable's\n // FieldSet type omits null.\n const fields: Record<string, number | string | null> = {\n pScore: scores.performance,\n rScore: scores.accessibility,\n bpScore: scores.bestPractices,\n seoScore: scores.seo,\n \"Last lighthouse audit at\": new Date().toISOString(),\n };\n return fields as FieldSet;\n}\n\nfunction a11yFields(counts: A11yCounts): FieldSet {\n return { \"A11y Violations\": counts.violations };\n}\n\nfunction depsFields(counts: DepsCounts): FieldSet {\n const fields: FieldSet = {\n \"Deps Drifted\": counts.drifted,\n \"Deps Major Behind\": counts.majorBehind,\n };\n // Only write the outdated count when it was determined — a null (no/stale\n // lockfile this run) must not clobber a previously-good value.\n if (counts.outdated !== null) {\n fields[\"Deps Outdated\"] = counts.outdated;\n }\n // Same null-guard for the registry-major breakdown (same source signal).\n if (counts.majorOutdated !== null) {\n fields[\"Deps Major Outdated\"] = counts.majorOutdated;\n }\n return fields;\n}\n\n/** Cap persisted advisories so the JSON field can't blow up on a badly-neglected site. */\nconst MAX_PERSISTED_ADVISORIES = 25;\n\nfunction securityAdvisoryFields(advisories: SecurityAdvisory[]): FieldSet {\n const capped = [...advisories]\n .sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])\n .slice(0, MAX_PERSISTED_ADVISORIES);\n // Always write — an empty array (\"[]\") clears a stale list when a once-vulnerable site goes clean.\n return { \"Security advisories\": JSON.stringify(capped) };\n}\n\nfunction securityFields(counts: SecurityCounts): FieldSet {\n return {\n \"Security Vulns Critical\": counts.critical,\n \"Security Vulns High\": counts.high,\n \"Security Vulns Moderate\": counts.moderate,\n \"Security Vulns Low\": counts.low,\n // Stamp freshness alongside the counts so the Security Updates auto-tick can require a recent\n // audit (a clean count from months ago must not silently keep ticking the box).\n \"Last security audit at\": new Date().toISOString(),\n };\n}\n\nfunction domainFields(result: DomainResult): FieldSet {\n const fields: FieldSet = { \"Domain checked at\": result.checkedAt };\n // Write the cert days UNCONDITIONALLY: a null (unresolved / no usable cert) must CLEAR any\n // previously-good value. Leaving a stale number in place — next to a freshly-stamped \"Domain\n // checked at\" — false-passes the Domain/DNS/SSL auto-tick (domainEvidence reads the stale\n // non-null value as a current pass) for a site that's actually down. null clears the cell in\n // Airtable; FieldSet's type doesn't model null, hence the cast through a widened record.\n (fields as Record<string, number | null>)[\"Cert days remaining\"] = result.certDaysRemaining;\n return fields;\n}\n\nfunction netlifyDeployFields(result: NetlifyDeployResult): FieldSet {\n // Write all three deploy fields UNCONDITIONALLY (null clears the cell): a null state /\n // deployedAt / logUrl this run means \"couldn't read it\" and must not leave a STALE value\n // sitting next to a fresh \"Deploy checked at\" — the dashboard would otherwise render a green\n // \"ready\" badge for a site whose latest deploy actually errored. FieldSet's type doesn't model\n // null, hence the cast through a widened record (same approach as domainFields).\n const fields: Record<string, string | null> = {\n \"Deploy status\": result.state,\n \"Last deploy at\": result.deployedAt,\n \"Deploy log URL\": result.logUrl,\n \"Deploy checked at\": result.checkedAt,\n };\n return fields as FieldSet;\n}\n\nfunction browserFields(r: BrowserAuditFields): FieldSet {\n return {\n \"Crossbrowser OK\": r.desktopOk,\n \"Mobile OK\": r.mobileOk,\n \"Links OK\": r.linksOk,\n \"Broken links\": r.brokenLinks,\n \"Browser checked at\": r.checkedAt,\n // NEW tri-state single-select verdicts (empty = never ran). The browser audit only produces a\n // BrowserAuditFields when it actually ran (hasBrowserResult guards on checkedAt), so each verdict\n // is always a concrete boolean here — serialize true→\"pass\", false→\"fail\". The existing boolean\n // columns above are deliberately NOT retrofitted (out of scope).\n \"Uptime Reachable\": r.reachableOk ? \"pass\" : \"fail\",\n \"Titles & Meta OK\": r.titleMetaOk ? \"pass\" : \"fail\",\n };\n}\n\nfunction functionHealthFields(r: FunctionHealthResult): FieldSet {\n // \"CMS Reachable\" is written UNCONDITIONALLY (null clears the cell): the audit only supplies a\n // result when it ran, but `cmsReachable` itself can legitimately be null this run (R2.2 — the CMS\n // probe never actually happened, e.g. a placeholder repo with no live Prismic). Clearing rather\n // than leaving a stale pass/fail sitting next to a freshly-stamped checked-at is what keeps a\n // placeholder site from showing a stale CMS verdict. FieldSet's type doesn't model null, hence the\n // cast through a widened record (same approach as domainFields/netlifyDeployFields).\n // \"Function health\" is never null — the audit only writes when it ran, so it's always a concrete\n // pass/fail. Written SEPARATELY from \"Deploy status\" so the Netlify build state keeps its own\n // meaning.\n const fields: Record<string, string | null> = {\n \"Function health\": r.functionHealth,\n \"CMS Reachable\": r.cmsReachable,\n // \"Turnstile widget\" is NOT written here any more. /health only knows whether\n // PUBLIC_TURNSTILE_SITE_KEY is a non-empty string — it never contacts\n // Cloudflare — so this audit cannot tell a working widget from one whose\n // hostname is not on the allowlist (#689). The column is owned by `form-e2e`,\n // which drives a real browser at the real widget and can. Writing it from\n // both would also be unreachable by the red alarm: this sweep runs at 08:00,\n // the digest reads at 09:23, form-e2e writes at 10:15 — a browser verdict\n // would be cleared by this null every morning before the alarm ever saw it.\n \"Function health checked at\": r.checkedAt,\n };\n return fields as FieldSet;\n}\n\nfunction smokeFields(r: SmokeResult): FieldSet {\n // The verdict is stored as the literal single-select option (\"pass\"/\"fail\"), so\n // no boolean→string coercion is needed. A skip never reaches here (it produces no\n // SmokeResult), so this column is only ever written with a concrete verdict.\n return { \"Smoke OK\": r.ok, \"Last Smoke At\": r.checkedAt };\n}\n\nfunction formE2eFields(r: FormE2eResult): FieldSet {\n // `ok` is already the single-select value (\"pass\"/\"fail\") or null. Writing null\n // CLEARS the cell (→ n/a, distinguished from \"never ran\" by the fresh checked-at\n // stamped alongside). FieldSet's type omits null, hence the widened-record cast\n // (same approach as domainFields / netlifyDeployFields).\n const fields: Record<string, string | null> = {};\n // The FORM verdict and its stamp move together, and only when this run produced\n // one. A run with no form verdict (the testMode-undeclared skip) writes neither,\n // which preserves both — refreshing the stamp while preserving a stale verdict\n // would present months-old evidence as fresh to auto-tick's `formsEvidence`.\n if (r.ok !== undefined) {\n fields[\"Form E2E OK\"] = r.ok;\n fields[\"Form E2E checked at\"] = r.checkedAt;\n }\n // The Turnstile verdict rides this audit because it is the only one that opens\n // a browser on the live form, where the site's REAL widget renders (the probe\n // never swaps the sitekey). Written only when this run had an opinion: the key\n // is ABSENT on a run that could not look, which preserves the prior verdict,\n // and an explicit null is the distinct \"looked, could not tell\" that clears it.\n if (r.turnstileWidget !== undefined) fields[\"Turnstile widget\"] = r.turnstileWidget;\n return fields as FieldSet;\n}\n\n// ————————————————————————— Websites writers (the Airtable shadow) —————————————————————————\n//\n// Every writer below addresses a Websites row by SITE id, and every one opens with\n// `skipsAirtableShadow` (#646 step 3). Sites created since the Turso-native\n// `ensure-site` carry `site_<ULID>` ids that Airtable has never held, so a write\n// for one is skipped on purpose — with an `AIRTABLE_SHADOW skipped=non-rec-id` line — instead of\n// 404ing. The FieldSet-returning writers still RETURN their payload when they\n// skip: callers feed it to the authoritative Turso write, which must not depend on\n// the shadow. tests/reports/airtable/shadow-skip-site-ids.test.ts fails for any\n// exported `update*` writer that is not covered.\n\n/**\n * Write the four Lighthouse scores + a refreshed-at timestamp onto a Websites row.\n * Called by `audit lighthouse --write-back` after a successful audit run, so\n * the operator never has to paste numbers manually before drafting a report.\n */\nexport async function updateScores(\n base: AirtableBase,\n recordId: string,\n scores: LighthouseScores,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateScores\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: scoreFields(scores) }]);\n}\n\n/** The `Analytics soft-fail at` FieldSet, as a pure function of the stamp (#782).\n * Drafting mirrors THIS into Turso first — the authoritative store — and only\n * then shadows it to Airtable, so the two writes carry one payload and the\n * shadow's field gap cannot cost the real write. `at` is an ISO timestamp when\n * the site's last draft had a GA/Search soft-failure, `null` after a clean\n * enrichment (the signal self-heals). */\nexport function analyticsHealthFields(at: string | null): FieldSet {\n const fields: Record<string, string | null> = { \"Analytics soft-fail at\": at };\n return fields as FieldSet;\n}\n\n/**\n * Record (or clear) the per-site GA/Search enrichment health on the `Analytics\n * soft-fail at` column. The caller (drafting) swallows errors: this column is\n * operator-added and — checked against the base on 2026-09-15 — has never\n * existed there, so Airtable throws UNKNOWN_FIELD_NAME on every call. That is\n * why drafting writes Turso BEFORE this (#782): Turso is authoritative and the\n * Airtable layer is the shadow Phase 6 (#646) deletes.\n */\nexport async function updateAnalyticsHealth(\n base: AirtableBase,\n recordId: string,\n at: string | null,\n): Promise<FieldSet> {\n const fields = analyticsHealthFields(at);\n if (!skipsAirtableShadow(\"updateAnalyticsHealth\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n }\n // Same contract as updateNextDueDates/updateAuditFields: the #539 Turso mirror\n // consumes the returned FieldSet, so the two writes cannot diverge.\n return fields;\n}\n\n/** Persist a11y violation count. */\nexport async function updateA11yCounts(\n base: AirtableBase,\n recordId: string,\n counts: A11yCounts,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateA11yCounts\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: a11yFields(counts) }]);\n}\n\n/** Persist deps drift counts (declared-range drift + real outdated installs). */\nexport async function updateDepsCounts(\n base: AirtableBase,\n recordId: string,\n counts: DepsCounts,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateDepsCounts\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: depsFields(counts) }]);\n}\n\n/** Persist security vulnerability counts by severity. */\nexport async function updateSecurityCounts(\n base: AirtableBase,\n recordId: string,\n counts: SecurityCounts,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateSecurityCounts\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: securityFields(counts) }]);\n}\n\n/** Persist a site's auto-fix attempt counter. Its own one-field writer so the\n * nightly Renovate dispatch can update it without touching the audit's counts. */\nexport async function updateAutoFixAttempts(\n base: AirtableBase,\n recordId: string,\n attempts: number,\n): Promise<FieldSet> {\n const fields: FieldSet = { \"Security Auto-Fix Attempts\": attempts };\n if (!skipsAirtableShadow(\"updateAutoFixAttempts\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n }\n // Returned for the #539 Turso mirror — see updateNextDueDates.\n return fields;\n}\n\n/**\n * Persist the code-computed next-due dates (date-only `YYYY-MM-DD`, or `null` to\n * clear) for the maintenance + testing schedules. Owned by the nightly `--due` sweep\n * so the \"next\" dates shown in Airtable come from the SAME logic as the scheduler\n * (`nextDueDate`) — no Airtable-side formula or automation. Best-effort at the call\n * site: the `Next … at` columns are operator-added, so until they exist Airtable\n * throws UNKNOWN_FIELD_NAME, which must not break the nightly draft run.\n */\nexport async function updateNextDueDates(\n base: AirtableBase,\n recordId: string,\n dates: { maintenanceAt: string | null; testingAt: string | null },\n): Promise<FieldSet> {\n const fields: Record<string, string | null> = {\n \"Next maintenance at\": dates.maintenanceAt,\n \"Next testing at\": dates.testingAt,\n };\n if (!skipsAirtableShadow(\"updateNextDueDates\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: fields as FieldSet }]);\n }\n // Same contract as updateAuditFields/updateGitHubSignals: the Phase 3 Turso\n // mirror consumes the returned FieldSet, so the two writes cannot diverge.\n return fields as FieldSet;\n}\n\n/** The cell shapes the site editor can write. Airtable rejects a string written\n * to a checkbox or a multi-select, so `Require Turnstile` (boolean) and\n * `Accepted Watch Conditions` (string[]) travel as themselves rather than being\n * stringified at the boundary and coerced back later. */\nexport type AirtableCellValue = string | boolean | string[];\n\n/** Generic single-field writer for the dashboard site-details editor. The caller\n * (setSiteDetail) restricts `column` to the EDITABLE_SITE_FIELDS allowlist, so this\n * never writes an arbitrary column from request input. */\nexport async function updateSiteField(\n base: AirtableBase,\n recordId: string,\n column: string,\n value: AirtableCellValue,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateSiteField\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: { [column]: value } }]);\n}\n\n/** The multi-column shadow for `ensure-site`'s fill-blanks / `--name` path on a\n * pre-existing `rec` site (#646 step 3): ONE update, so a resumed bootstrap that\n * fills `url` and retitles `Name` cannot leave the shadow half-written. Turso is\n * the store that decides what to write; this only repeats it. */\nexport async function updateSiteFields(\n base: AirtableBase,\n recordId: string,\n fields: Record<string, string>,\n): Promise<void> {\n if (skipsAirtableShadow(\"updateSiteFields\", recordId)) return;\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: fields as FieldSet }]);\n}\n\n/**\n * Persist all of a single audit run's results to one Websites row in ONE atomic\n * `update()` — instead of up to four sequential updates on the same id (which left\n * a row half-written on a mid-sequence failure and quadrupled the request volume).\n * Pass only the audit slices that produced real values; each present slice is merged\n * via the SAME field mappings the per-audit writers use. Omit a slice (or pass\n * undefined) to leave those columns untouched. Returns the merged FieldSet so the\n * caller can enumerate what was written.\n */\nexport async function updateAuditFields(\n base: AirtableBase,\n recordId: string,\n audits: {\n scores?: LighthouseScoreWriteback;\n a11y?: A11yCounts;\n deps?: DepsCounts;\n security?: SecurityCounts;\n securityAdvisories?: SecurityAdvisory[];\n domain?: DomainResult;\n browser?: BrowserAuditFields;\n netlifyDeploy?: NetlifyDeployResult;\n functionHealth?: FunctionHealthResult;\n smoke?: SmokeResult;\n formE2e?: FormE2eResult;\n },\n): Promise<FieldSet> {\n const fields: FieldSet = {};\n if (audits.scores) Object.assign(fields, scoreFields(audits.scores));\n if (audits.a11y) Object.assign(fields, a11yFields(audits.a11y));\n if (audits.deps) Object.assign(fields, depsFields(audits.deps));\n if (audits.security) Object.assign(fields, securityFields(audits.security));\n // Separate slice (not folded into `security`) so the advisory list and the counts can be\n // written independently, but in practice the security audit supplies both together.\n if (audits.securityAdvisories)\n Object.assign(fields, securityAdvisoryFields(audits.securityAdvisories));\n if (audits.domain) Object.assign(fields, domainFields(audits.domain));\n if (audits.browser) Object.assign(fields, browserFields(audits.browser));\n if (audits.netlifyDeploy) Object.assign(fields, netlifyDeployFields(audits.netlifyDeploy));\n if (audits.functionHealth) Object.assign(fields, functionHealthFields(audits.functionHealth));\n if (audits.smoke) Object.assign(fields, smokeFields(audits.smoke));\n if (audits.formE2e) Object.assign(fields, formE2eFields(audits.formE2e));\n if (!skipsAirtableShadow(\"updateAuditFields\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n }\n return fields;\n}\n\n/** Persist the GitHub-signals sweep onto a Websites row (slice 2a). A null\n * `lastCommitAt` is OMITTED so a not-determined-this-run value never clobbers a\n * previously-good timestamp (mirrors updateDepsCounts' outdated handling).\n * Returns the FieldSet it wrote — the Phase 3 Turso mirror consumes the same\n * payload, so the two writes cannot diverge (updateAuditFields' contract). */\nexport async function updateGitHubSignals(\n base: AirtableBase,\n recordId: string,\n signals: {\n renovateFailingCis: number;\n ciState: string;\n lastCommitAt: string | null;\n sweptAt: string;\n },\n): Promise<FieldSet> {\n const fields: FieldSet = {\n \"Renovate Failing CIs\": signals.renovateFailingCis,\n \"Default Branch CI\": signals.ciState,\n \"GitHub Signals At\": signals.sweptAt,\n };\n if (signals.lastCommitAt !== null) {\n fields[\"Last Commit At\"] = signals.lastCommitAt;\n }\n if (!skipsAirtableShadow(\"updateGitHubSignals\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n }\n return fields;\n}\n\n/** One site's Prismic model verdict, as the sweep hands it to the record.\n *\n * `verdict: null` is a REQUEST TO BLANK the cell, not \"leave it alone\" — see\n * {@link updatePrismicModels}. There is no way to express \"leave it alone\" here\n * on purpose. */\nexport type PrismicModelsWriteback = {\n verdict: PrismicModelsVerdict | null;\n checkedAt: string;\n /** The finding, the failure reason, or the skip reason — whichever this\n * verdict has. `null` clears the column. */\n detail: string | null;\n};\n\n/** Airtable's long-text cells hold ~100k characters and the sweep's report runs\n * long on a badly drifted site (an empty Prismic repository sorts every local\n * model into `toCreate`, with a line per field). Half the cell is the budget;\n * the rest is headroom for whatever renders it. */\nconst MAX_PRISMIC_DETAIL_CHARS = 50_000;\n\n/**\n * Shorten a detail that will not fit, and SAY SO — with the original length, so\n * nobody mistakes the remainder for the whole. Same reasoning as the PR comment's\n * truncation notice: a report that was shortened without saying it was shortened\n * is a report that looks complete and is not.\n *\n * The cut is by code unit, so it can land between the halves of an astral\n * character; a lone surrogate is not valid text and can make Airtable reject the\n * write, losing the entire finding to a cosmetic detail.\n */\nfunction truncatePrismicDetail(detail: string): string {\n if (detail.length <= MAX_PRISMIC_DETAIL_CHARS) return detail;\n const notice =\n `\\n…[truncated: this report is ${detail.length} characters and only the first` +\n ` ${MAX_PRISMIC_DETAIL_CHARS} are kept here. Run \\`reddoor-maint prismic-models\\` in the` +\n ` site — dry is the default — for the whole thing.]`;\n let head = detail.slice(0, MAX_PRISMIC_DETAIL_CHARS - notice.length);\n const last = head.charCodeAt(head.length - 1);\n if (last >= 0xd800 && last <= 0xdbff) head = head.slice(0, -1);\n return head + notice;\n}\n\n/**\n * Persist one site's Prismic model verdict.\n *\n * ALL THREE COLUMNS, ALWAYS, in one update — including when the verdict is\n * `unknown` or blank. That is the whole design: the alternative (\"only write a\n * verdict when we have one\") leaves the PREVIOUS verdict standing for a site\n * whose check has since started failing, and a stale `pass` is never aged out by\n * anything — the digest's freshness gate only examines failures. So the record\n * always says what the last run actually established, and `checkedAt` always\n * says when. See {@link WebsiteRow.prismicModels} for the four states.\n *\n * Best-effort AT THE CALL SITE: `Prismic Models*` are operator-added columns, so\n * until they exist Airtable throws UNKNOWN_FIELD_NAME. The nightly sweep must\n * survive that and collect it — same contract as `updateNextDueDates`.\n *\n * Takes a non-null `AirtableBase`, like every other writer here: whether to write\n * at all is the caller's decision, and this repo keeps \"do no writes\" separate\n * from \"do no IO\" one layer up.\n */\nexport async function updatePrismicModels(\n base: AirtableBase,\n recordId: string,\n models: PrismicModelsWriteback,\n): Promise<FieldSet> {\n const fields: Record<string, string | null> = {\n \"Prismic Models\": models.verdict,\n \"Prismic Models Checked At\": models.checkedAt,\n \"Prismic Models Drift\": models.detail === null ? null : truncatePrismicDetail(models.detail),\n };\n if (!skipsAirtableShadow(\"updatePrismicModels\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields: fields as FieldSet }]);\n }\n // Returned for the #539 Turso mirror — see updateNextDueDates.\n return fields as FieldSet;\n}\n\n/** Mark a site launched: flip Status → maintained + stamp Launched at (M6b).\n * The first code that writes Status. Called after a Launch report sends.\n * Routed through `toAirtableStatus`, so it still writes Airtable's \"maintenance\"\n * option until the stage-2 switch flips. */\nexport async function updateLaunched(\n base: AirtableBase,\n recordId: string,\n at: string,\n): Promise<FieldSet> {\n const fields: FieldSet = { Status: toAirtableStatus(\"maintained\"), \"Launched at\": at };\n if (!skipsAirtableShadow(\"updateLaunched\", recordId)) {\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n }\n // Returned for the #539 Turso mirror. BOTH columns travel together on purpose:\n // mirroring them as two UPDATEs would open a window where Turso says a site is\n // maintained but never launched.\n return fields;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuCO,SAAS,iBAAiB,GAAmB;AAClD,SAAO;AACT;;;ACSO,IAAM,iBAAiB;AAQvB,SAAS,OAAO,KAAkE;AACvF,QAAM,IAAI,IAAI;AACd,QAAM,OAAO,OAAO,EAAE,MAAM,KAAK,EAAE;AACnC,QAAM,cACH,EAAE,cAAc,KAA4E,CAAC;AAOhG,QAAM,SAAS,YAAY,GAAG,EAAE,KAAK;AACrC,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR;AAAA,IACA,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE;AAAA,IAC1B,QAAQ,mBAAmB,EAAE,QAAQ,CAAC;AAAA,IACtC,WAAY,EAAE,QAAQ,KAA4B;AAAA,IAClD,gBAAiB,EAAE,kBAAkB,KAA4B;AAAA,IACjE,iBAAiB,YAAY,EAAE,kBAAkB,GAAG,GAAG,IAAI,cAAc;AAAA,IACzE,aAAa,YAAY,EAAE,cAAc,GAAG,GAAG,IAAI,UAAU;AAAA,IAC7D,oBAAqB,EAAE,kBAAkB,KAA4B;AAAA,IACrE,gBAAiB,EAAE,cAAc,KAA4B;AAAA,IAC7D,gBAAiB,EAAE,iBAAiB,KAA4B;AAAA,IAChE,YAAa,EAAE,aAAa,KAA4B;AAAA,IACxD,eAAgB,EAAE,iBAAiB,KAA4B;AAAA,IAC/D,aAAc,EAAE,cAAc,KAA4B;AAAA,IAC1D,uBAAwB,EAAE,yBAAyB,KAA4B;AAAA,IAC/E,qBAAsB,EAAE,wBAAwB,KAA4B;AAAA,IAC5E,SAAU,EAAE,UAAU,KAA4B;AAAA,IAClD,oBAAqB,EAAE,wBAAwB,KAA4B;AAAA,IAC3E,oBAAqB,EAAE,wBAAwB,KAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAO3E,yBAAyB,MAAM,QAAQ,EAAE,2BAA2B,CAAC,IAChE,EAAE,2BAA2B,EAC3B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,EAChD,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,IACjB,OAAO,EAAE,2BAA2B,MAAM,WACvC,EAAE,2BAA2B,EAC3B,MAAM,OAAO,EACb,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,IACjB,CAAC;AAAA,IACP,aAAa;AAAA,IACb,QAAS,EAAE,QAAQ,KAA4B;AAAA,IAC/C,QAAS,EAAE,QAAQ,KAA4B;AAAA,IAC/C,SAAU,EAAE,SAAS,KAA4B;AAAA,IACjD,UAAW,EAAE,UAAU,KAA4B;AAAA,IACnD,uBAAwB,EAAE,0BAA0B,KAA4B;AAAA,IAChF,gBAAiB,EAAE,iBAAiB,KAA4B;AAAA,IAChE,aAAc,EAAE,cAAc,KAA4B;AAAA,IAC1D,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,cAAe,EAAE,eAAe,KAA4B;AAAA,IAC5D,mBAAoB,EAAE,qBAAqB,KAA4B;AAAA,IACvE,uBAAwB,EAAE,yBAAyB,KAA4B;AAAA,IAC/E,mBAAoB,EAAE,qBAAqB,KAA4B;AAAA,IACvE,uBAAwB,EAAE,yBAAyB,KAA4B;AAAA,IAC/E,kBAAmB,EAAE,oBAAoB,KAA4B;AAAA,IACrE,yBAA0B,EAAE,4BAA4B,KAA4B;AAAA,IACpF,qBAAsB,EAAE,wBAAwB,KAA4B;AAAA,IAC5E,oBAAoB,wBAAwB,EAAE,qBAAqB,CAAC;AAAA,IACpE,mBAAoB,EAAE,qBAAqB,KAA4B;AAAA,IACvE,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,WAAW,WAAW,EAAE,YAAY,CAAC;AAAA,IACrC,cAAe,EAAE,eAAe,KAA4B;AAAA,IAC5D,cAAe,EAAE,gBAAgB,KAA4B;AAAA,IAC7D,cAAe,EAAE,gBAAgB,KAA4B;AAAA,IAC7D,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,gBAAgB,UAAU,EAAE,iBAAiB,CAAC;AAAA,IAC9C,cAAc,UAAU,EAAE,eAAe,CAAC;AAAA,IAC1C,iBAAiB,UAAU,EAAE,kBAAkB,CAAC;AAAA,IAChD,yBAA0B,EAAE,4BAA4B,KAA4B;AAAA,IACpF,gBACE,OAAO,EAAE,iBAAiB,MAAM,YAAa,EAAE,iBAAiB,IAAgB;AAAA,IAClF,UAAU,OAAO,EAAE,WAAW,MAAM,YAAa,EAAE,WAAW,IAAgB;AAAA,IAC9E,SAAS,OAAO,EAAE,UAAU,MAAM,YAAa,EAAE,UAAU,IAAgB;AAAA,IAC3E,aAAa,OAAO,EAAE,cAAc,MAAM,WAAY,EAAE,cAAc,IAAe;AAAA,IACrF,kBAAmB,EAAE,oBAAoB,KAA4B;AAAA,IACrE,aAAa,UAAU,EAAE,kBAAkB,CAAC;AAAA,IAC5C,aAAa,UAAU,EAAE,kBAAkB,CAAC;AAAA,IAC5C,WAAW,WAAW,EAAE,mBAAc,CAAC;AAAA,IACvC,aAAa,WAAW,EAAE,qBAAgB,CAAC;AAAA,IAC3C,YAAY,WAAW,EAAE,oBAAe,CAAC;AAAA,IACzC,YAAa,EAAE,aAAa,KAA4B;AAAA,IACxD,mBAAmB,WAAW,EAAE,oBAAoB,CAAC;AAAA,IACrD,eAAe,mBAAmB,EAAE,gBAAgB,CAAC;AAAA,IACrD,kBAAkB,WAAW,EAAE,gBAAgB,CAAC;AAAA,IAChD,iBAAiB,WAAW,EAAE,mBAAmB,CAAC;AAAA,IAClD,qBAAqB,WAAW,EAAE,uBAAuB,CAAC;AAAA;AAAA;AAAA,IAG1D,kBACE,OAAO,EAAE,mBAAmB,MAAM,YAAa,EAAE,mBAAmB,IAAgB;AAAA,IACtF,oBAAqB,EAAE,sBAAsB,KAA4B;AAAA,IACzE,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,cAAe,EAAE,gBAAgB,KAA4B;AAAA,IAC7D,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,SAAS,UAAU,EAAE,UAAU,CAAC;AAAA,IAChC,aAAc,EAAE,eAAe,KAA4B;AAAA,IAC3D,WAAW,UAAU,EAAE,aAAa,CAAC;AAAA,IACrC,kBAAmB,EAAE,qBAAqB,KAA4B;AAAA,IACtE,eAAe,uBAAuB,EAAE,gBAAgB,CAAC;AAAA,IACzD,wBAAyB,EAAE,2BAA2B,KAA4B;AAAA,IAClF,oBAAqB,EAAE,sBAAsB,KAA4B;AAAA,IACzE,iBAAkB,EAAE,mBAAmB,KAA4B;AAAA,IACnE,mBAAoB,EAAE,qBAAqB,KAA4B;AAAA,IACvE,eAAgB,EAAE,iBAAiB,KAA4B;AAAA,EACjE;AACF;AAEA,eAAsB,aAAa,MAA2C;AAC5E,QAAM,MAAoB,CAAC;AAC3B,QAAM,KAAK,cAAc,EACtB,OAAO,EAAE,UAAU,IAAI,CAAC,EACxB,SAAS,CAAC,SAAS,kBAAkB;AACpC,eAAW,OAAO,QAAS,KAAI,KAAK,OAAO,EAAE,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO,CAAC,CAAC;AAC9E,kBAAc;AAAA,EAChB,CAAC;AACH,SAAO;AACT;AAEA,eAAsB,iBACpB,MACA,MAC4B;AAI5B,MAAI,CAAC,6BAA6B,KAAK,IAAI,EAAG,QAAO;AAOrD,QAAM,UAAU,2EAA2E,KAAK;AAAA,IAC9F;AAAA,EACF,CAAC;AACD,QAAM,OAAqB,CAAC;AAC5B,QAAM,KAAK,cAAc,EACtB,OAAO,EAAE,iBAAiB,SAAS,YAAY,EAAE,CAAC,EAClD,SAAS,CAAC,SAAS,kBAAkB;AACpC,eAAW,OAAO,QAAS,MAAK,KAAK,OAAO,EAAE,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO,CAAC,CAAC;AAC/E,kBAAc;AAAA,EAChB,CAAC;AAGH,SAAO,KAAK,KAAK,CAAC,MAAM,SAAS,EAAE,IAAI,MAAM,IAAI,KAAK;AACxD;AAmEA,SAAS,YAAY,QAA4C;AAO/D,QAAM,SAAiD;AAAA,IACrD,QAAQ,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,6BAA4B,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrD;AACA,SAAO;AACT;AAEA,SAAS,WAAW,QAA8B;AAChD,SAAO,EAAE,mBAAmB,OAAO,WAAW;AAChD;AAEA,SAAS,WAAW,QAA8B;AAChD,QAAM,SAAmB;AAAA,IACvB,gBAAgB,OAAO;AAAA,IACvB,qBAAqB,OAAO;AAAA,EAC9B;AAGA,MAAI,OAAO,aAAa,MAAM;AAC5B,WAAO,eAAe,IAAI,OAAO;AAAA,EACnC;AAEA,MAAI,OAAO,kBAAkB,MAAM;AACjC,WAAO,qBAAqB,IAAI,OAAO;AAAA,EACzC;AACA,SAAO;AACT;AAGA,IAAM,2BAA2B;AAEjC,SAAS,uBAAuB,YAA0C;AACxE,QAAM,SAAS,CAAC,GAAG,UAAU,EAC1B,KAAK,CAAC,GAAG,MAAM,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,CAAC,EACpE,MAAM,GAAG,wBAAwB;AAEpC,SAAO,EAAE,uBAAuB,KAAK,UAAU,MAAM,EAAE;AACzD;AAEA,SAAS,eAAe,QAAkC;AACxD,SAAO;AAAA,IACL,2BAA2B,OAAO;AAAA,IAClC,uBAAuB,OAAO;AAAA,IAC9B,2BAA2B,OAAO;AAAA,IAClC,sBAAsB,OAAO;AAAA;AAAA;AAAA,IAG7B,2BAA0B,oBAAI,KAAK,GAAE,YAAY;AAAA,EACnD;AACF;AAEA,SAAS,aAAa,QAAgC;AACpD,QAAM,SAAmB,EAAE,qBAAqB,OAAO,UAAU;AAMjE,EAAC,OAAyC,qBAAqB,IAAI,OAAO;AAC1E,SAAO;AACT;AAEA,SAAS,oBAAoB,QAAuC;AAMlE,QAAM,SAAwC;AAAA,IAC5C,iBAAiB,OAAO;AAAA,IACxB,kBAAkB,OAAO;AAAA,IACzB,kBAAkB,OAAO;AAAA,IACzB,qBAAqB,OAAO;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,SAAS,cAAc,GAAiC;AACtD,SAAO;AAAA,IACL,mBAAmB,EAAE;AAAA,IACrB,aAAa,EAAE;AAAA,IACf,YAAY,EAAE;AAAA,IACd,gBAAgB,EAAE;AAAA,IAClB,sBAAsB,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,IAKxB,oBAAoB,EAAE,cAAc,SAAS;AAAA,IAC7C,oBAAoB,EAAE,cAAc,SAAS;AAAA,EAC/C;AACF;AAEA,SAAS,qBAAqB,GAAmC;AAU/D,QAAM,SAAwC;AAAA,IAC5C,mBAAmB,EAAE;AAAA,IACrB,iBAAiB,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASnB,8BAA8B,EAAE;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,YAAY,GAA0B;AAI7C,SAAO,EAAE,YAAY,EAAE,IAAI,iBAAiB,EAAE,UAAU;AAC1D;AAEA,SAAS,cAAc,GAA4B;AAKjD,QAAM,SAAwC,CAAC;AAK/C,MAAI,EAAE,OAAO,QAAW;AACtB,WAAO,aAAa,IAAI,EAAE;AAC1B,WAAO,qBAAqB,IAAI,EAAE;AAAA,EACpC;AAMA,MAAI,EAAE,oBAAoB,OAAW,QAAO,kBAAkB,IAAI,EAAE;AACpE,SAAO;AACT;AAkBA,eAAsB,aACpB,MACA,UACA,QACe;AACf,MAAI,oBAAoB,gBAAgB,QAAQ,EAAG;AACnD,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,QAAQ,YAAY,MAAM,EAAE,CAAC,CAAC;AACnF;AAQO,SAAS,sBAAsB,IAA6B;AACjE,QAAM,SAAwC,EAAE,0BAA0B,GAAG;AAC7E,SAAO;AACT;AAUA,eAAsB,sBACpB,MACA,UACA,IACmB;AACnB,QAAM,SAAS,sBAAsB,EAAE;AACvC,MAAI,CAAC,oBAAoB,yBAAyB,QAAQ,GAAG;AAC3D,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAAO,CAAC,CAAC;AAAA,EAC9D;AAGA,SAAO;AACT;AAGA,eAAsB,iBACpB,MACA,UACA,QACe;AACf,MAAI,oBAAoB,oBAAoB,QAAQ,EAAG;AACvD,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,QAAQ,WAAW,MAAM,EAAE,CAAC,CAAC;AAClF;AAGA,eAAsB,iBACpB,MACA,UACA,QACe;AACf,MAAI,oBAAoB,oBAAoB,QAAQ,EAAG;AACvD,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,QAAQ,WAAW,MAAM,EAAE,CAAC,CAAC;AAClF;AAGA,eAAsB,qBACpB,MACA,UACA,QACe;AACf,MAAI,oBAAoB,wBAAwB,QAAQ,EAAG;AAC3D,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,QAAQ,eAAe,MAAM,EAAE,CAAC,CAAC;AACtF;AAIA,eAAsB,sBACpB,MACA,UACA,UACmB;AACnB,QAAM,SAAmB,EAAE,8BAA8B,SAAS;AAClE,MAAI,CAAC,oBAAoB,yBAAyB,QAAQ,GAAG;AAC3D,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAAO,CAAC,CAAC;AAAA,EAC9D;AAEA,SAAO;AACT;AAUA,eAAsB,mBACpB,MACA,UACA,OACmB;AACnB,QAAM,SAAwC;AAAA,IAC5C,uBAAuB,MAAM;AAAA,IAC7B,mBAAmB,MAAM;AAAA,EAC3B;AACA,MAAI,CAAC,oBAAoB,sBAAsB,QAAQ,GAAG;AACxD,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAA2B,CAAC,CAAC;AAAA,EAClF;AAGA,SAAO;AACT;AAWA,eAAsB,gBACpB,MACA,UACA,QACA,OACe;AACf,MAAI,oBAAoB,mBAAmB,QAAQ,EAAG;AACtD,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,QAAQ,EAAE,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AACnF;AAMA,eAAsB,iBACpB,MACA,UACA,QACe;AACf,MAAI,oBAAoB,oBAAoB,QAAQ,EAAG;AACvD,QAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAA2B,CAAC,CAAC;AAClF;AAWA,eAAsB,kBACpB,MACA,UACA,QAamB;AACnB,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO,OAAQ,QAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,CAAC;AACnE,MAAI,OAAO,KAAM,QAAO,OAAO,QAAQ,WAAW,OAAO,IAAI,CAAC;AAC9D,MAAI,OAAO,KAAM,QAAO,OAAO,QAAQ,WAAW,OAAO,IAAI,CAAC;AAC9D,MAAI,OAAO,SAAU,QAAO,OAAO,QAAQ,eAAe,OAAO,QAAQ,CAAC;AAG1E,MAAI,OAAO;AACT,WAAO,OAAO,QAAQ,uBAAuB,OAAO,kBAAkB,CAAC;AACzE,MAAI,OAAO,OAAQ,QAAO,OAAO,QAAQ,aAAa,OAAO,MAAM,CAAC;AACpE,MAAI,OAAO,QAAS,QAAO,OAAO,QAAQ,cAAc,OAAO,OAAO,CAAC;AACvE,MAAI,OAAO,cAAe,QAAO,OAAO,QAAQ,oBAAoB,OAAO,aAAa,CAAC;AACzF,MAAI,OAAO,eAAgB,QAAO,OAAO,QAAQ,qBAAqB,OAAO,cAAc,CAAC;AAC5F,MAAI,OAAO,MAAO,QAAO,OAAO,QAAQ,YAAY,OAAO,KAAK,CAAC;AACjE,MAAI,OAAO,QAAS,QAAO,OAAO,QAAQ,cAAc,OAAO,OAAO,CAAC;AACvE,MAAI,CAAC,oBAAoB,qBAAqB,QAAQ,GAAG;AACvD,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAAO,CAAC,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;AAOA,eAAsB,oBACpB,MACA,UACA,SAMmB;AACnB,QAAM,SAAmB;AAAA,IACvB,wBAAwB,QAAQ;AAAA,IAChC,qBAAqB,QAAQ;AAAA,IAC7B,qBAAqB,QAAQ;AAAA,EAC/B;AACA,MAAI,QAAQ,iBAAiB,MAAM;AACjC,WAAO,gBAAgB,IAAI,QAAQ;AAAA,EACrC;AACA,MAAI,CAAC,oBAAoB,uBAAuB,QAAQ,GAAG;AACzD,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAAO,CAAC,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;AAmBA,IAAM,2BAA2B;AAYjC,SAAS,sBAAsB,QAAwB;AACrD,MAAI,OAAO,UAAU,yBAA0B,QAAO;AACtD,QAAM,SACJ;AAAA,mCAAiC,OAAO,MAAM,kCAC1C,wBAAwB;AAE9B,MAAI,OAAO,OAAO,MAAM,GAAG,2BAA2B,OAAO,MAAM;AACnE,QAAM,OAAO,KAAK,WAAW,KAAK,SAAS,CAAC;AAC5C,MAAI,QAAQ,SAAU,QAAQ,MAAQ,QAAO,KAAK,MAAM,GAAG,EAAE;AAC7D,SAAO,OAAO;AAChB;AAqBA,eAAsB,oBACpB,MACA,UACA,QACmB;AACnB,QAAM,SAAwC;AAAA,IAC5C,kBAAkB,OAAO;AAAA,IACzB,6BAA6B,OAAO;AAAA,IACpC,wBAAwB,OAAO,WAAW,OAAO,OAAO,sBAAsB,OAAO,MAAM;AAAA,EAC7F;AACA,MAAI,CAAC,oBAAoB,uBAAuB,QAAQ,GAAG;AACzD,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAA2B,CAAC,CAAC;AAAA,EAClF;AAEA,SAAO;AACT;AAMA,eAAsB,eACpB,MACA,UACA,IACmB;AACnB,QAAM,SAAmB,EAAE,QAAQ,iBAAiB,YAAY,GAAG,eAAe,GAAG;AACrF,MAAI,CAAC,oBAAoB,kBAAkB,QAAQ,GAAG;AACpD,UAAM,KAAK,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,UAAU,OAAO,CAAC,CAAC;AAAA,EAC9D;AAIA,SAAO;AACT;","names":[]}
|