@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/cli/commands/ensure-site.ts
|
|
2
|
+
async function defaultAirtable(db) {
|
|
3
|
+
const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
|
|
4
|
+
let cfg;
|
|
5
|
+
try {
|
|
6
|
+
cfg = readAirtableConfig();
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const base = openBase(cfg);
|
|
11
|
+
const { findWebsiteRecordBySlug } = await import("./ensure-site-OO4UPWNQ.js");
|
|
12
|
+
const { updateSiteFields } = await import("./websites-Y6FN2XFD.js");
|
|
13
|
+
const { mirrorSiteInsert } = await import("./fleet-state-RJROYXKO.js");
|
|
14
|
+
return {
|
|
15
|
+
findBySlug: (slug) => findWebsiteRecordBySlug(base, slug),
|
|
16
|
+
// The #645 heal: the importer's own mapping, so an adopted row is exactly what
|
|
17
|
+
// the import would have written.
|
|
18
|
+
adopt: (rec) => mirrorSiteInsert(db, rec, (/* @__PURE__ */ new Date()).toISOString()),
|
|
19
|
+
update: (recordId, patch) => {
|
|
20
|
+
const fields = {};
|
|
21
|
+
if (patch.name !== void 0) fields["Name"] = patch.name;
|
|
22
|
+
if (patch.url !== void 0) fields["url"] = patch.url;
|
|
23
|
+
if (patch.pointOfContact !== void 0) fields["point of contact"] = patch.pointOfContact;
|
|
24
|
+
if (patch.gitRepo !== void 0) fields["Git repo"] = patch.gitRepo;
|
|
25
|
+
return updateSiteFields(base, recordId, fields);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function runEnsureSiteCommand(slug, opts, deps = {}) {
|
|
30
|
+
if (!slug) return { output: "Provide a <slug> (e.g. `ensure-site roalson`).", code: 2 };
|
|
31
|
+
try {
|
|
32
|
+
const open = deps.openDb ?? (async () => {
|
|
33
|
+
const { openDb, readDbConfig } = await import("./client-3XGSZMLU.js");
|
|
34
|
+
return openDb(readDbConfig());
|
|
35
|
+
});
|
|
36
|
+
const db = await open();
|
|
37
|
+
const { makeSiteStore } = await import("./site-create-ZU3ACBTO.js");
|
|
38
|
+
const { ensureSite } = await import("./ensure-site-JEKV2ODE.js");
|
|
39
|
+
const airtable = deps.airtable ? await deps.airtable() : await defaultAirtable(db);
|
|
40
|
+
const result = await ensureSite(
|
|
41
|
+
{
|
|
42
|
+
slug,
|
|
43
|
+
...opts.name ? { displayName: opts.name } : {},
|
|
44
|
+
...opts.url ? { url: opts.url } : {},
|
|
45
|
+
...opts.contact ? { pointOfContact: opts.contact } : {},
|
|
46
|
+
...opts.gitRepo ? { gitRepo: opts.gitRepo } : {}
|
|
47
|
+
},
|
|
48
|
+
{ store: makeSiteStore(db), airtable }
|
|
49
|
+
);
|
|
50
|
+
const renamed = result.updatedFields.includes("name") ? ` \u2014 name set to "${opts.name}"` : "";
|
|
51
|
+
const blanks = result.updatedFields.filter((f) => f !== "name");
|
|
52
|
+
const filled = blanks.length > 0 ? ` \u2014 filled blank field(s): ${blanks.join(", ")}` : "";
|
|
53
|
+
const skipped = result.skippedMismatches.length > 0 ? ` \u2014 differs from existing, left untouched (edit on the dashboard site page): ${result.skippedMismatches.join(", ")}` : "";
|
|
54
|
+
const healed = result.healedDbRow ? ` \u2014 HEALED: the Turso row was MISSING and has been adopted from Airtable; leads for this slug were being dropped (run \`db replay-deadletters\`)` : "";
|
|
55
|
+
const nameNote = result.status === "created" && !opts.name ? ` \u2014 name set to "${slug}"; re-run with --name before forms/announce go live` : "";
|
|
56
|
+
const batchNote = result.status === "created" ? `
|
|
57
|
+
note: no Airtable record was created (the site id is not an Airtable id). Batch jobs that still enumerate the fleet from Airtable \u2014 nightly audits incl. form-e2e, report drafting, digest \u2014 will not see this site until #646 step 4 moves them to Turso. Form ingest and the dashboard see it now.` : "";
|
|
58
|
+
return {
|
|
59
|
+
output: `[${slug}] ${result.status} (${result.siteId})${healed}${renamed}${filled}${skipped}${nameNote}${batchNote}`,
|
|
60
|
+
code: 0
|
|
61
|
+
};
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const e = err;
|
|
64
|
+
return { output: e.message ?? String(err), code: e.exitCode ?? 1 };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
runEnsureSiteCommand
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=ensure-site-F2WFCWWA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/ensure-site.ts"],"sourcesContent":["import type { Db } from \"../../db/client.js\";\nimport type { LegacyAirtableSites } from \"../../fleet/ensure-site.js\";\n\nexport type EnsureSiteCommandOptions = {\n name?: string;\n url?: string;\n contact?: string;\n gitRepo?: string;\n cwd?: string;\n};\n\n/** The two stores, injectable so tests never open a real libSQL handle (even with\n * `TURSO_*` exported) or reach Airtable. */\nexport type EnsureSiteCommandDeps = {\n openDb?: () => Promise<Db>;\n /** `null` = no Airtable wired (no creds): no #645 heal lookup, no `rec` shadow. */\n airtable?: () => Promise<LegacyAirtableSites | null>;\n};\n\n/** Wire the legacy Airtable half only when credentials exist. Airtable is a shadow\n * now, so its absence is a supported state — a missing PAT must not stop a site\n * from being created. */\nasync function defaultAirtable(db: Db): Promise<LegacyAirtableSites | null> {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n let cfg: ReturnType<typeof readAirtableConfig>;\n try {\n cfg = readAirtableConfig();\n } catch {\n return null;\n }\n const base = openBase(cfg);\n const { findWebsiteRecordBySlug } = await import(\"../../reports/airtable/ensure-site.js\");\n const { updateSiteFields } = await import(\"../../reports/airtable/websites.js\");\n const { mirrorSiteInsert } = await import(\"../../db/fleet-state.js\");\n return {\n findBySlug: (slug) => findWebsiteRecordBySlug(base, slug),\n // The #645 heal: the importer's own mapping, so an adopted row is exactly what\n // the import would have written.\n adopt: (rec) => mirrorSiteInsert(db, rec, new Date().toISOString()),\n update: (recordId, patch) => {\n const fields: Record<string, string> = {};\n if (patch.name !== undefined) fields[\"Name\"] = patch.name;\n if (patch.url !== undefined) fields[\"url\"] = patch.url;\n if (patch.pointOfContact !== undefined) fields[\"point of contact\"] = patch.pointOfContact;\n if (patch.gitRepo !== undefined) fields[\"Git repo\"] = patch.gitRepo;\n return updateSiteFields(base, recordId, fields);\n },\n };\n}\n\n/**\n * `ensure-site <slug>` — create/verify a site's fleet row (#646 step 3: in Turso,\n * with a `site_<ULID>` id for a new site). Day-one step of the /new-site bootstrap\n * skill. Fill-blanks-only; safe to re-run; `--name` renames without moving the slug.\n */\nexport async function runEnsureSiteCommand(\n slug: string | undefined,\n opts: EnsureSiteCommandOptions,\n deps: EnsureSiteCommandDeps = {},\n): Promise<{ output: string; code: number }> {\n if (!slug) return { output: \"Provide a <slug> (e.g. `ensure-site roalson`).\", code: 2 };\n try {\n const open =\n deps.openDb ??\n (async () => {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n return openDb(readDbConfig());\n });\n const db = await open();\n const { makeSiteStore } = await import(\"../../db/site-create.js\");\n const { ensureSite } = await import(\"../../fleet/ensure-site.js\");\n const airtable = deps.airtable ? await deps.airtable() : await defaultAirtable(db);\n const result = await ensureSite(\n {\n slug,\n ...(opts.name ? { displayName: opts.name } : {}),\n ...(opts.url ? { url: opts.url } : {}),\n ...(opts.contact ? { pointOfContact: opts.contact } : {}),\n ...(opts.gitRepo ? { gitRepo: opts.gitRepo } : {}),\n },\n { store: makeSiteStore(db), airtable },\n );\n // #664: a name change on the exists path is a rename the operator asked for.\n const renamed = result.updatedFields.includes(\"name\") ? ` — name set to \"${opts.name}\"` : \"\";\n const blanks = result.updatedFields.filter((f) => f !== \"name\");\n const filled = blanks.length > 0 ? ` — filled blank field(s): ${blanks.join(\", \")}` : \"\";\n const skipped =\n result.skippedMismatches.length > 0\n ? ` — differs from existing, left untouched (edit on the dashboard site page): ${result.skippedMismatches.join(\", \")}`\n : \"\";\n // #645. Loud, and its own clause: a healed row means this site's leads were\n // being answered `unknown-site` and dropped until this run.\n const healed = result.healedDbRow\n ? ` — HEALED: the Turso row was MISSING and has been adopted from Airtable; leads for this slug were being dropped (run \\`db replay-deadletters\\`)`\n : \"\";\n const nameNote =\n result.status === \"created\" && !opts.name\n ? ` — name set to \"${slug}\"; re-run with --name before forms/announce go live`\n : \"\";\n // The honest cost of a Turso-only site until #646 step 4 lands (see\n // src/fleet/ensure-site.ts): say it where the operator is looking.\n const batchNote =\n result.status === \"created\"\n ? `\\n note: no Airtable record was created (the site id is not an Airtable id). Batch jobs that still enumerate the fleet from Airtable — nightly audits incl. form-e2e, report drafting, digest — will not see this site until #646 step 4 moves them to Turso. Form ingest and the dashboard see it now.`\n : \"\";\n return {\n output: `[${slug}] ${result.status} (${result.siteId})${healed}${renamed}${filled}${skipped}${nameNote}${batchNote}`,\n code: 0,\n };\n } catch (err) {\n const e = err as { message?: string; exitCode?: number };\n return { output: e.message ?? String(err), code: e.exitCode ?? 1 };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
siteSlug
|
|
3
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
4
|
+
import {
|
|
5
|
+
isAirtableRecordId,
|
|
6
|
+
mintSiteId,
|
|
7
|
+
skipsAirtableShadow
|
|
8
|
+
} from "./chunk-RQJFGK3A.js";
|
|
9
|
+
|
|
10
|
+
// src/fleet/ensure-site.ts
|
|
11
|
+
var slugTaken = (err) => typeof err === "object" && err !== null && err.code === "SLUG_TAKEN";
|
|
12
|
+
async function ensureSite(input, deps) {
|
|
13
|
+
const { store } = deps;
|
|
14
|
+
const airtable = deps.airtable ?? null;
|
|
15
|
+
const slug = siteSlug(input.slug);
|
|
16
|
+
if (!slug) throw new Error(`ensure-site: '${input.slug}' does not slugify to a usable slug`);
|
|
17
|
+
if (input.displayName !== void 0 && siteSlug(input.displayName) !== slug) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`ensure-site: display name '${input.displayName}' slugifies to '${siteSlug(input.displayName)}', not '${slug}' \u2014 a rename never changes the slug`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
let existing = await store.findBySlug(slug);
|
|
23
|
+
let healedDbRow = false;
|
|
24
|
+
if (!existing && airtable) {
|
|
25
|
+
const legacy = await airtable.findBySlug(slug);
|
|
26
|
+
if (legacy) {
|
|
27
|
+
await airtable.adopt(legacy);
|
|
28
|
+
existing = await store.findBySlug(slug);
|
|
29
|
+
if (!existing) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`ensure-site: adopted Airtable record ${legacy.id} for '${slug}' but Turso still has no row for the slug`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
healedDbRow = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!existing) {
|
|
38
|
+
const id = (deps.mintId ?? mintSiteId)();
|
|
39
|
+
try {
|
|
40
|
+
await store.create({
|
|
41
|
+
id,
|
|
42
|
+
slug,
|
|
43
|
+
name: input.displayName ?? slug,
|
|
44
|
+
status: "building",
|
|
45
|
+
url: input.url ?? null,
|
|
46
|
+
pointOfContact: input.pointOfContact ?? null,
|
|
47
|
+
gitRepo: input.gitRepo ?? `reddoorla/${slug}`
|
|
48
|
+
});
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (!slugTaken(err)) throw err;
|
|
51
|
+
const owner = await store.findBySlug(slug).catch(() => null);
|
|
52
|
+
throw new Error(
|
|
53
|
+
`ensure-site: slug '${slug}' is already taken${owner ? ` by site ${owner.id}` : ""} \u2014 it was created after this run looked; re-run to resume that site`,
|
|
54
|
+
{ cause: err }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
skipsAirtableShadow("ensureSite.create", id);
|
|
58
|
+
return {
|
|
59
|
+
status: "created",
|
|
60
|
+
siteId: id,
|
|
61
|
+
updatedFields: [],
|
|
62
|
+
skippedMismatches: [],
|
|
63
|
+
healedDbRow: false,
|
|
64
|
+
airtableShadow: "skipped"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const patch = {};
|
|
68
|
+
const skippedMismatches = [];
|
|
69
|
+
const consider = (key, provided, current) => {
|
|
70
|
+
if (!provided) return;
|
|
71
|
+
if (current === null || current === "") patch[key] = provided;
|
|
72
|
+
else if (current !== provided) skippedMismatches.push(key);
|
|
73
|
+
};
|
|
74
|
+
consider("url", input.url, existing.url);
|
|
75
|
+
consider("pointOfContact", input.pointOfContact, existing.pointOfContact);
|
|
76
|
+
consider("gitRepo", input.gitRepo, existing.gitRepo);
|
|
77
|
+
if (input.displayName !== void 0 && existing.name !== input.displayName) {
|
|
78
|
+
patch.name = input.displayName;
|
|
79
|
+
}
|
|
80
|
+
const updatedFields = Object.keys(patch);
|
|
81
|
+
let airtableShadow = "none";
|
|
82
|
+
if (updatedFields.length > 0) {
|
|
83
|
+
if (skipsAirtableShadow("ensureSite.update", existing.id)) {
|
|
84
|
+
airtableShadow = "skipped";
|
|
85
|
+
} else if (airtable && isAirtableRecordId(existing.id)) {
|
|
86
|
+
await airtable.update(existing.id, patch);
|
|
87
|
+
airtableShadow = "written";
|
|
88
|
+
}
|
|
89
|
+
if (!await store.updateIdentity(existing.id, patch)) {
|
|
90
|
+
throw new Error(`ensure-site: site ${existing.id} vanished from Turso mid-run`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
status: "exists",
|
|
95
|
+
siteId: existing.id,
|
|
96
|
+
updatedFields,
|
|
97
|
+
skippedMismatches,
|
|
98
|
+
healedDbRow,
|
|
99
|
+
airtableShadow
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
ensureSite
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=ensure-site-JEKV2ODE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fleet/ensure-site.ts"],"sourcesContent":["/**\n * `ensure-site`, Turso-native (#539 Phase 6 step 3, #646; operator decision\n * 2026-09-17).\n *\n * Find-or-create a site by SLUG so it exists in the fleet from day one. Turso is\n * the source of truth: a new site gets a minted `site_<ULID>` id and its three\n * rows (`sites`, `site_health`, `site_schedule`) in one transaction. The command\n * is re-run to RESUME a partial bootstrap, so everything keys on the slug — a\n * re-run finds the site and never mints a second id.\n *\n * Fill-blanks-only on the exists path: a resumed bootstrap must never clobber an\n * operator's edit. The one exception is `displayName` (#664): `--name` targets the\n * name and nothing else, so a differing value IS the operator's rename. A rename\n * changes `sites.name` only; `sites.slug` is the stable external handle and never\n * moves, which is why a name that would slugify differently is refused outright.\n *\n * ## The Airtable shadow for a NEW site: none\n *\n * A new site is not written to Airtable at all. An Airtable record would carry\n * its own `rec` id that is NOT the site id, and every batch job still enumerating\n * the fleet from Airtable (#646 step 4, not yet done) would then write Turso by\n * that `rec` id — an UPDATE matching no row, which under the freeze THROWS\n * (`mirrored=missed`). A half-visible site that reds the nightly sweeps is worse\n * than one those sweeps cannot see yet. The rollback week is over and a rollback\n * to Airtable has never been rehearsed, so there is no recovery the record would\n * be keeping trustworthy. Every Airtable shadow writer skips a `site_` id with an\n * `AIRTABLE_SHADOW skipped=non-rec-id` line (`src/fleet/site-id.ts`).\n *\n * The cost, named: until step 4 moves batch enumeration to Turso, a site created\n * here is invisible to the Airtable-enumerated jobs (audits incl. form-e2e,\n * report drafting, digest, github-signals, renovate-dispatch…). Form ingest,\n * the dashboard and every Turso reader see it immediately.\n *\n * ## Pre-existing `rec` sites\n *\n * Both id shapes coexist permanently. A `rec` site found in Turso keeps its id,\n * and its fill-blanks / rename is still shadowed to Airtable (Airtable stays the\n * shadow until step 6), Airtable first so a re-run converges both stores.\n *\n * The #645 heal survives in one form: when Turso has no row for the slug but\n * Airtable does, the Airtable record is ADOPTED under its own `rec` id instead of\n * minting a second identity for the same site. That lookup runs only when an\n * Airtable client is wired, and a lookup that FAILS refuses the create — minting\n * while unable to rule out a duplicate identity would be the one unrecoverable\n * outcome here.\n */\nimport { siteSlug } from \"./site-row.js\";\nimport { isAirtableRecordId, mintSiteId, skipsAirtableShadow } from \"./site-id.js\";\nimport type { NewSiteRow, SiteIdentityPatch } from \"../db/fleet-state.js\";\n\nexport type { SiteIdentityPatch } from \"../db/fleet-state.js\";\n\nexport type EnsureSiteInput = {\n /** Canonical slug; normalised through `siteSlug`. The stored `sites.slug`. */\n slug: string;\n /** Human display name. Written on create (default: the slug) and, unlike every\n * other input, UPDATED on an existing site when it differs (#664) — it is used\n * verbatim in client-facing copy (forms auto-reply intro, report subjects).\n * Must slugify to the same slug, or it is refused. */\n displayName?: string;\n url?: string;\n pointOfContact?: string;\n /** owner/repo. Default on create: `reddoorla/<slug>`. */\n gitRepo?: string;\n};\n\n/** The identity columns of an existing site, as `ensure-site` compares them. */\nexport type ExistingSite = {\n id: string;\n name: string;\n url: string | null;\n pointOfContact: string | null;\n gitRepo: string | null;\n};\n\n/** The Turso operations the creator needs. `src/db/site-create.ts` implements it. */\nexport type SiteStore = {\n findBySlug: (slug: string) => Promise<ExistingSite | null>;\n /** Insert all three rows atomically. Rejects with `code: \"SLUG_TAKEN\"` when the\n * `sites.slug` UNIQUE index refuses the row. */\n create: (site: NewSiteRow) => Promise<void>;\n /** Resolves whether a row matched. Never touches the slug (by type). */\n updateIdentity: (siteId: string, patch: SiteIdentityPatch) => Promise<boolean>;\n};\n\n/** The Airtable layer, as the creator still needs it until #646 step 6. Every\n * member is legacy: the lookup + adopt keep the #645 heal, `update` shadows a\n * `rec` site's fill. The CLI wires it only when Airtable credentials exist. */\nexport type LegacyAirtableSites = {\n findBySlug: (slug: string) => Promise<{ id: string; fields: Record<string, unknown> } | null>;\n /** Insert Turso rows for an Airtable record that has none (the #645 heal). */\n adopt: (rec: { id: string; fields: Record<string, unknown> }) => Promise<void>;\n update: (recordId: string, patch: SiteIdentityPatch) => Promise<void>;\n};\n\nexport type EnsureSiteDeps = {\n store: SiteStore;\n airtable?: LegacyAirtableSites | null;\n /** Injectable for tests; defaults to {@link mintSiteId}. */\n mintId?: () => string;\n};\n\nexport type EnsureSiteResult = {\n status: \"created\" | \"exists\";\n siteId: string;\n /** Identity fields written on the exists path (`name` = a `--name` rename). */\n updatedFields: Array<keyof SiteIdentityPatch>;\n /** Inputs that DIFFERED from an existing non-blank value and were left untouched\n * (fill-blanks-only) — surfaced so a resumed bootstrap with a corrected value\n * does not silently discard it. */\n skippedMismatches: Array<keyof SiteIdentityPatch>;\n /** #645: the site existed in Airtable with NO Turso row, and this run adopted it.\n * Its leads were being answered `unknown-site` until now. */\n healedDbRow: boolean;\n /** What happened to the Airtable shadow: `skipped` for a `site_` id (created or\n * updated), `written` when a `rec` site's changes were shadowed, `none` when\n * there was nothing to shadow or no Airtable client was wired. */\n airtableShadow: \"skipped\" | \"written\" | \"none\";\n};\n\nconst slugTaken = (err: unknown): boolean =>\n typeof err === \"object\" && err !== null && (err as { code?: unknown }).code === \"SLUG_TAKEN\";\n\nexport async function ensureSite(\n input: EnsureSiteInput,\n deps: EnsureSiteDeps,\n): Promise<EnsureSiteResult> {\n const { store } = deps;\n const airtable = deps.airtable ?? null;\n const slug = siteSlug(input.slug);\n if (!slug) throw new Error(`ensure-site: '${input.slug}' does not slugify to a usable slug`);\n if (input.displayName !== undefined && siteSlug(input.displayName) !== slug) {\n throw new Error(\n `ensure-site: display name '${input.displayName}' slugifies to '${siteSlug(input.displayName)}', not '${slug}' — a rename never changes the slug`,\n );\n }\n\n let existing = await store.findBySlug(slug);\n let healedDbRow = false;\n\n if (!existing && airtable) {\n // Throws through on failure — see \"Pre-existing rec sites\" above.\n const legacy = await airtable.findBySlug(slug);\n if (legacy) {\n await airtable.adopt(legacy);\n existing = await store.findBySlug(slug);\n if (!existing) {\n throw new Error(\n `ensure-site: adopted Airtable record ${legacy.id} for '${slug}' but Turso still has no row for the slug`,\n );\n }\n healedDbRow = true;\n }\n }\n\n if (!existing) {\n const id = (deps.mintId ?? mintSiteId)();\n try {\n await store.create({\n id,\n slug,\n name: input.displayName ?? slug,\n status: \"building\",\n url: input.url ?? null,\n pointOfContact: input.pointOfContact ?? null,\n gitRepo: input.gitRepo ?? `reddoorla/${slug}`,\n });\n } catch (err) {\n if (!slugTaken(err)) throw err;\n const owner = await store.findBySlug(slug).catch(() => null);\n throw new Error(\n `ensure-site: slug '${slug}' is already taken${owner ? ` by site ${owner.id}` : \"\"} — it was created after this run looked; re-run to resume that site`,\n { cause: err },\n );\n }\n skipsAirtableShadow(\"ensureSite.create\", id);\n return {\n status: \"created\",\n siteId: id,\n updatedFields: [],\n skippedMismatches: [],\n healedDbRow: false,\n airtableShadow: \"skipped\",\n };\n }\n\n const patch: SiteIdentityPatch = {};\n const skippedMismatches: Array<keyof SiteIdentityPatch> = [];\n const consider = (\n key: \"url\" | \"pointOfContact\" | \"gitRepo\",\n provided: string | undefined,\n current: string | null,\n ) => {\n if (!provided) return;\n if (current === null || current === \"\") patch[key] = provided;\n else if (current !== provided) skippedMismatches.push(key);\n };\n consider(\"url\", input.url, existing.url);\n consider(\"pointOfContact\", input.pointOfContact, existing.pointOfContact);\n consider(\"gitRepo\", input.gitRepo, existing.gitRepo);\n if (input.displayName !== undefined && existing.name !== input.displayName) {\n patch.name = input.displayName;\n }\n\n const updatedFields = Object.keys(patch) as Array<keyof SiteIdentityPatch>;\n let airtableShadow: EnsureSiteResult[\"airtableShadow\"] = \"none\";\n if (updatedFields.length > 0) {\n if (skipsAirtableShadow(\"ensureSite.update\", existing.id)) {\n airtableShadow = \"skipped\";\n } else if (airtable && isAirtableRecordId(existing.id)) {\n await airtable.update(existing.id, patch);\n airtableShadow = \"written\";\n }\n if (!(await store.updateIdentity(existing.id, patch))) {\n throw new Error(`ensure-site: site ${existing.id} vanished from Turso mid-run`);\n }\n }\n return {\n status: \"exists\",\n siteId: existing.id,\n updatedFields,\n skippedMismatches,\n healedDbRow,\n airtableShadow,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
openBase,
|
|
3
|
-
readAirtableConfig
|
|
4
|
-
} from "./chunk-HN6I2XWI.js";
|
|
5
1
|
import {
|
|
6
2
|
WEBSITES_TABLE,
|
|
7
3
|
mapRow,
|
|
8
|
-
siteSlug,
|
|
9
4
|
toAirtableStatus
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import
|
|
5
|
+
} from "./chunk-TC4GANMX.js";
|
|
6
|
+
import {
|
|
7
|
+
siteSlug
|
|
8
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
9
|
+
import "./chunk-RQJFGK3A.js";
|
|
12
10
|
|
|
13
11
|
// src/reports/airtable/ensure-site.ts
|
|
14
12
|
var COLS = {
|
|
@@ -16,6 +14,17 @@ var COLS = {
|
|
|
16
14
|
pointOfContact: "point of contact",
|
|
17
15
|
gitRepo: "Git repo"
|
|
18
16
|
};
|
|
17
|
+
async function findWebsiteRecordBySlug(base, slug) {
|
|
18
|
+
let found = null;
|
|
19
|
+
await base(WEBSITES_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
20
|
+
for (const rec of records) {
|
|
21
|
+
if (!found && siteSlug(mapRow(rec).name) === slug)
|
|
22
|
+
found = { id: rec.id, fields: rec.fields };
|
|
23
|
+
}
|
|
24
|
+
fetchNextPage();
|
|
25
|
+
});
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
19
28
|
async function ensureSite(base, input, mirror) {
|
|
20
29
|
const slug = siteSlug(input.slug);
|
|
21
30
|
if (!slug) throw new Error(`ensure-site: '${input.slug}' does not slugify to a usable slug`);
|
|
@@ -85,40 +94,8 @@ async function ensureSite(base, input, mirror) {
|
|
|
85
94
|
}
|
|
86
95
|
return { status: "exists", siteId: existing.id, updatedFields, skippedMismatches, healedDbRow };
|
|
87
96
|
}
|
|
88
|
-
|
|
89
|
-
// src/cli/commands/ensure-site.ts
|
|
90
|
-
async function runEnsureSiteCommand(slug, opts) {
|
|
91
|
-
if (!slug) return { output: "Provide a <slug> (e.g. `ensure-site roalson`).", code: 2 };
|
|
92
|
-
try {
|
|
93
|
-
const base = openBase(readAirtableConfig());
|
|
94
|
-
const { makeSiteMirror } = await import("./site-mirror-H2FYMN55.js");
|
|
95
|
-
const result = await ensureSite(
|
|
96
|
-
base,
|
|
97
|
-
{
|
|
98
|
-
slug,
|
|
99
|
-
...opts.name ? { displayName: opts.name } : {},
|
|
100
|
-
...opts.url ? { url: opts.url } : {},
|
|
101
|
-
...opts.contact ? { pointOfContact: opts.contact } : {},
|
|
102
|
-
...opts.gitRepo ? { gitRepo: opts.gitRepo } : {}
|
|
103
|
-
},
|
|
104
|
-
await makeSiteMirror()
|
|
105
|
-
);
|
|
106
|
-
const renamed = result.updatedFields.includes("Name") ? ` \u2014 Name set to "${opts.name}"` : "";
|
|
107
|
-
const blanks = result.updatedFields.filter((f) => f !== "Name");
|
|
108
|
-
const filled = blanks.length > 0 ? ` \u2014 filled blank field(s): ${blanks.join(", ")}` : "";
|
|
109
|
-
const skipped = result.skippedMismatches.length > 0 ? ` \u2014 differs from existing, left untouched (edit in Airtable): ${result.skippedMismatches.join(", ")}` : "";
|
|
110
|
-
const healed = result.healedDbRow ? ` \u2014 HEALED: the Turso row was MISSING and has been inserted; leads for this slug were being dropped (run \`db replay-deadletters\`)` : "";
|
|
111
|
-
const nameNote = result.status === "created" && !opts.name ? ` \u2014 Name set to "${slug}"; retitle in Airtable before forms/announce go live (or re-run with --name)` : "";
|
|
112
|
-
return {
|
|
113
|
-
output: `[${slug}] ${result.status} (${result.siteId})${healed}${renamed}${filled}${skipped}${nameNote}`,
|
|
114
|
-
code: 0
|
|
115
|
-
};
|
|
116
|
-
} catch (err) {
|
|
117
|
-
const e = err;
|
|
118
|
-
return { output: e.message ?? String(err), code: e.exitCode ?? 1 };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
97
|
export {
|
|
122
|
-
|
|
98
|
+
ensureSite,
|
|
99
|
+
findWebsiteRecordBySlug
|
|
123
100
|
};
|
|
124
|
-
//# sourceMappingURL=ensure-site-
|
|
101
|
+
//# sourceMappingURL=ensure-site-OO4UPWNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reports/airtable/ensure-site.ts"],"sourcesContent":["import type { FieldSet, Records } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport { WEBSITES_TABLE, mapRow, siteSlug, type WebsiteRow } from \"./websites.js\";\nimport { toAirtableStatus } from \"./site-status.js\";\n\nexport type EnsureSiteInput = {\n /** Canonical slug — matches by siteSlug(Name); the Name on create when no displayName given. */\n slug: string;\n /** Human display name written to Name on create, and — unlike every other\n * input — UPDATED on an existing row when it differs (#664). Name is\n * consumed VERBATIM in client-facing copy (forms auto-reply intro, report\n * subjects), so a bare machine slug there reads as \"Thanks for reaching out\n * to acme-co.\" — and the row that has it is exactly the one created before\n * the name was settled, which only this command re-run can fix. Must\n * slugify to the same slug or the row wouldn't be found on re-run. */\n displayName?: string;\n url?: string;\n pointOfContact?: string;\n /** owner/repo. Default on create: `reddoorla/<slug>`. */\n gitRepo?: string;\n};\n\n/** #539 Phase 5. Structurally typed rather than importing the db layer's\n * `SiteMirror`, so this module stays the leaf it has always been — a real\n * `SiteMirror` satisfies it. Two ops because `ensureSite` has two write paths,\n * and the create one is the reason this exists: every other site mirror is an\n * UPDATE, which does nothing for a row that does not exist yet. */\nexport type EnsureSiteMirror = {\n created: (rec: { id: string; fields: Record<string, unknown> }) => Promise<void>;\n site: (siteId: string, fields: Record<string, unknown>) => Promise<void>;\n /** #645. Does Turso hold a row for this site id? Optional so every pre-#645\n * caller (and every hand-built test mirror) is byte-for-byte unchanged —\n * absent means the heal below never runs. */\n hasRow?: (siteId: string) => Promise<boolean>;\n};\n\nexport type EnsureSiteResult = {\n status: \"created\" | \"exists\";\n siteId: string;\n /** Airtable column names written on the exists path (create path writes all). */\n updatedFields: string[];\n /** Inputs that DIFFERED from an existing non-blank cell and were left\n * untouched (fill-blanks-only) — surfaced so a resumed bootstrap with a\n * corrected value doesn't silently discard it. */\n skippedMismatches: string[];\n /** #645. True when the site existed in Airtable but had NO Turso row and this\n * run inserted one. Surfaced rather than logged-only because it is the\n * difference between \"nothing to do\" and \"this site's leads were being\n * dropped until just now\". */\n healedDbRow: boolean;\n};\n\n/** Column-name map (load-bearing magic strings — see websites.ts's mapRow header). */\nconst COLS = {\n url: \"url\",\n pointOfContact: \"point of contact\",\n gitRepo: \"Git repo\",\n} as const;\n\n/**\n * One select over Websites, returning the RAW stored record whose Name slugifies\n * to `slug` (#646 step 3). The Turso-native creator (`src/fleet/ensure-site.ts`)\n * uses it for the #645 heal only — to ADOPT an Airtable site that has no Turso row\n * under its own `rec` id instead of minting a second identity for it. Raw, not a\n * mapped `WebsiteRow`, because the adopt re-inserts what Airtable STORED.\n */\nexport async function findWebsiteRecordBySlug(\n base: AirtableBase,\n slug: string,\n): Promise<{ id: string; fields: Record<string, unknown> } | null> {\n let found: { id: string; fields: Record<string, unknown> } | null = null;\n await base(WEBSITES_TABLE)\n .select({ pageSize: 100 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) {\n if (!found && siteSlug(mapRow(rec).name) === slug)\n found = { id: rec.id, fields: rec.fields };\n }\n fetchNextPage();\n });\n return found;\n}\n\n/**\n * @deprecated since #646 step 3 — the `ensure-site` command no longer calls this.\n * New sites are created in Turso by `src/fleet/ensure-site.ts` with `site_<ULID>`\n * ids and no Airtable record. Kept, not deleted, because removing anything from\n * the Airtable layer is #646 steps 6–8, which need their own go.\n *\n * Find-or-create the Websites row for a slug so a new site exists in the fleet\n * inventory (audits, form-ingest slug resolution, reports) from day one.\n *\n * Fill-blanks-only on the exists path: this command runs from a bootstrap skill\n * that may be re-run to resume — it must never clobber operator-edited cells.\n * The one exception is `displayName` (#664): `--name` targets Name and nothing\n * else, so a differing value IS the operator's edit, and the create message\n * sends them back here to make it. Frequencies are deliberately NOT set\n * (launch flips the lifecycle); Status is only written on create (\"building\" —\n * Airtable's \"in development\" until the stage-2 vocabulary switch flips).\n */\nexport async function ensureSite(\n base: AirtableBase,\n input: EnsureSiteInput,\n mirror?: EnsureSiteMirror,\n): Promise<EnsureSiteResult> {\n const slug = siteSlug(input.slug);\n if (!slug) throw new Error(`ensure-site: '${input.slug}' does not slugify to a usable slug`);\n if (input.displayName && siteSlug(input.displayName) !== slug)\n throw new Error(\n `ensure-site: display name '${input.displayName}' slugifies to '${siteSlug(input.displayName)}', not '${slug}' — the row would not be found on re-run`,\n );\n\n // Paged inline rather than through `listWebsites` so the RAW stored record\n // survives alongside the mapped row: the #645 heal below re-inserts what\n // Airtable STORED (the same contract as the create path's mirror), and a\n // mapped `WebsiteRow` cannot be turned back into a FieldSet. Same single\n // select `listWebsites` issues — no extra Airtable read.\n const raw: Array<{ id: string; fields: Record<string, unknown> }> = [];\n await base(WEBSITES_TABLE)\n .select({ pageSize: 100 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) raw.push({ id: rec.id, fields: rec.fields });\n fetchNextPage();\n });\n let existing: WebsiteRow | undefined;\n let existingRaw: { id: string; fields: Record<string, unknown> } | undefined;\n for (const rec of raw) {\n const row = mapRow(rec);\n if (siteSlug(row.name) === slug) {\n existing = row;\n existingRaw = rec;\n break;\n }\n }\n\n if (!existing || !existingRaw) {\n const fields: FieldSet = {\n Name: input.displayName ?? slug,\n Status: toAirtableStatus(\"building\"),\n [COLS.gitRepo]: input.gitRepo ?? `reddoorla/${slug}`,\n };\n if (input.url) fields[COLS.url] = input.url;\n if (input.pointOfContact) fields[COLS.pointOfContact] = input.pointOfContact;\n const created = (await base(WEBSITES_TABLE).create([{ fields }])) as Records<FieldSet>;\n const rec = created[0]!;\n // The mirror gets what Airtable STORED, never the `fields` we sent: parity\n // diffs Turso against the stored record, so mapping our own payload would\n // diverge the moment Airtable normalises a value.\n await mirror?.created({ id: rec.id, fields: rec.fields as Record<string, unknown> });\n return {\n status: \"created\",\n siteId: rec.id,\n updatedFields: [],\n skippedMismatches: [],\n // The create path just inserted the row; there is nothing to heal, and a\n // `hasRow` probe here would be a round-trip whose answer is already known.\n healedDbRow: false,\n };\n }\n\n // #645. THE gap: before this, `exists` meant \"Airtable has it\" and said nothing\n // about Turso. Post-flip (#643) the form-ingest lookup reads Turso ONLY, so a\n // site with an Airtable row and no Turso row has every lead answered\n // `unknown-site` — silently, permanently, and with no alarm once Phase 6 (#646)\n // deletes `mirror_missed`. `ensure-site` is the command an operator already\n // re-runs to finish a half-created site, so the heal belongs here.\n //\n // FIRST, before the fill-blanks update below: `mirror.site` is an UPDATE, and\n // under the freeze a no-match THROWS (`SITE_MIRROR … no such row in Turso`).\n // Healing second would abort ensure-site on exactly the site it exists to\n // repair. `mirrorSiteInsert` is an upsert, so a lost race just re-writes.\n let healedDbRow = false;\n if (mirror?.hasRow && !(await mirror.hasRow(existingRaw.id))) {\n await mirror.created(existingRaw);\n healedDbRow = true;\n }\n\n const updates: FieldSet = {};\n const skippedMismatches: string[] = [];\n const blank = (v: unknown) => v === null || v === undefined || v === \"\";\n const consider = (column: string, provided: string | undefined, current: unknown) => {\n if (!provided) return;\n if (blank(current)) updates[column] = provided;\n else if (current !== provided) skippedMismatches.push(column);\n };\n consider(COLS.url, input.url, existing.url || null);\n consider(COLS.pointOfContact, input.pointOfContact, existing.pointOfContact);\n consider(COLS.gitRepo, input.gitRepo, existing.gitRepo);\n // #664: Name is NOT fill-blanks. It is never blank (the slug match found it),\n // and `--name` is the only way to retitle a row created before the display\n // name was settled — the slugify guard above already proved the new value\n // resolves to the same row. Goes through the same update + mirror below, so\n // Turso gets it too.\n if (input.displayName && existing.name !== input.displayName) {\n updates[\"Name\"] = input.displayName;\n }\n\n const updatedFields = Object.keys(updates);\n if (updatedFields.length > 0) {\n await base(WEBSITES_TABLE).update([{ id: existing.id, fields: updates }]);\n // The fill-blanks path writes real cells too — a resumed bootstrap that only\n // filled a blank `url` would otherwise leave Turso stale until the sync.\n await mirror?.site(existing.id, updates);\n }\n return { status: \"exists\", siteId: existing.id, updatedFields, skippedMismatches, healedDbRow };\n}\n"],"mappings":"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","names":[]}
|