@reddoorla/maintenance 0.96.0 → 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 +5 -6
- package/dist/{announce-45HVP4J4.js → announce-5ZD2SBGD.js} +39 -31
- package/dist/announce-5ZD2SBGD.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-775VLDYB.js} +6 -6
- package/dist/{chunk-IMYL67JV.js → chunk-3RLDX3Q2.js} +5 -5
- 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-GEUC5VGM.js → chunk-BINFZQ2T.js} +49 -4
- package/dist/chunk-BINFZQ2T.js.map +1 -0
- package/dist/{chunk-72FJZ6LO.js → chunk-DRNZVORX.js} +26 -22
- package/dist/chunk-DRNZVORX.js.map +1 -0
- package/dist/{chunk-SHGIDTYF.js → chunk-DYTYPKFN.js} +2 -2
- 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-H445EH33.js → chunk-FP6HOW6J.js} +10 -7
- package/dist/chunk-FP6HOW6J.js.map +1 -0
- 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-T76MA2FH.js → chunk-IOTOOTII.js} +12 -1
- package/dist/{chunk-T76MA2FH.js.map → chunk-IOTOOTII.js.map} +1 -1
- package/dist/{chunk-ECDJDHKT.js → chunk-JHJNNJLG.js} +2 -2
- package/dist/{chunk-XY7YV2YQ.js → chunk-L3S3QZXL.js} +2 -2
- package/dist/{chunk-XED2DNAI.js → chunk-LAG6IP7Y.js} +7 -7
- package/dist/{chunk-P4A6D6HO.js → chunk-LDHRIQUW.js} +32 -18
- package/dist/chunk-LDHRIQUW.js.map +1 -0
- 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-MMSMZC7S.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-PARKXDXI.js} +3 -3
- package/dist/{chunk-62YWOFVN.js.map → chunk-PARKXDXI.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-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-VEIIUCYL.js → chunk-TV76A6GN.js} +2 -2
- package/dist/{chunk-D3CTOCUR.js → chunk-TVZ6QCOE.js} +3 -3
- package/dist/{chunk-3EA43S3R.js → chunk-U2ZI6G7H.js} +12 -12
- package/dist/chunk-UPE6D4AT.js +69 -0
- package/dist/chunk-UPE6D4AT.js.map +1 -0
- package/dist/{chunk-OTEVKRAH.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-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-EQQ4H6L3.js → chunk-YXWBM26P.js} +2 -2
- 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 +25 -22
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-DGTO65WB.js +11 -0
- package/dist/{convert-to-pnpm-4DON7IFS.js → convert-to-pnpm-KLWA53N5.js} +6 -6
- package/dist/{db-5K4CDEGJ.js → db-II5WAK4D.js} +13 -13
- package/dist/{digest-5AJGWS33.js → digest-CWKK2DMR.js} +28 -27
- package/dist/digest-CWKK2DMR.js.map +1 -0
- package/dist/{digest-collectors-MC5NWEUA.js → digest-collectors-CYTN2EUX.js} +15 -12
- 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-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-X6UEOI2M.js} +17 -13
- package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
- package/dist/{github-signals-DMU74SAX.js → github-signals-QIRTHHET.js} +20 -15
- package/dist/github-signals-QIRTHHET.js.map +1 -0
- package/dist/{header-image-BRTOLHJF.js → header-image-H7ZJAV26.js} +13 -17
- package/dist/header-image-H7ZJAV26.js.map +1 -0
- package/dist/{health-endpoint-UCLMSSCN.js → health-endpoint-BJ6BKRHF.js} +7 -7
- package/dist/{health-mirror-ONRT4DQ5.js → health-mirror-KU3G7V2Y.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 +60 -49
- package/dist/index.js.map +1 -1
- package/dist/{init-3LMZTCVD.js → init-GOWDA5QS.js} +15 -15
- package/dist/{launch-XTDMC6LD.js → launch-7N3X2MXB.js} +36 -34
- package/dist/launch-7N3X2MXB.js.map +1 -0
- package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
- package/dist/{chunk-IHHAYXKY.js → match-harness-GBCDSVPS.js} +70 -4
- package/dist/match-harness-GBCDSVPS.js.map +1 -0
- package/dist/migrate-XZ7U5DFC.js +7 -0
- package/dist/notify-K3WVH3DM.js +1 -1
- package/dist/{onboard-CZYBWVWH.js → onboard-JQQIMQW7.js} +6 -6
- package/dist/{orchestrate-TGDFGY52.js → orchestrate-L2A5BZFY.js} +13 -10
- package/dist/parity-2JGVUCZD.js +14 -0
- package/dist/{preflight-POZZI3GN.js → preflight-O7Q6FWHU.js} +35 -11
- package/dist/preflight-O7Q6FWHU.js.map +1 -0
- package/dist/{prismic-ci-AHY5ULJR.js → prismic-ci-NAPWOXXD.js} +11 -11
- package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
- package/dist/{prismic-models-URYGODNY.js → prismic-models-LL6ORTQ2.js} +19 -16
- package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
- package/dist/{prospect-audit-AJEE3Z4L.js → prospect-audit-FMPGQIRQ.js} +2 -2
- 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-from-row-65DC6ZP3.js → render-from-row-75JTED2Z.js} +6 -4
- package/dist/{renovate-dispatch-ONJMBUHJ.js → renovate-dispatch-2IHAZDVA.js} +13 -8
- package/dist/{renovate-dispatch-ONJMBUHJ.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
- package/dist/{report-RC5LGPU3.js → report-IAHXN5CW.js} +102 -58
- package/dist/report-IAHXN5CW.js.map +1 -0
- package/dist/{report-mirror-KKZZ6RMZ.js → report-mirror-4QM57LMS.js} +28 -10
- 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/{self-updating-RKKIYNV4.js → self-updating-ZUWPGYUP.js} +7 -7
- package/dist/{selftest-AFLR7YV7.js → selftest-G4W3LIBQ.js} +21 -17
- package/dist/{selftest-AFLR7YV7.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-mirror-H2FYMN55.js → site-mirror-WR7ACXOV.js} +10 -10
- package/dist/site-mirror-WR7ACXOV.js.map +1 -0
- package/dist/{smoke-suite-BWHR3XX5.js → smoke-suite-WAUPI6EJ.js} +7 -7
- package/dist/{submissions-II5J35QM.js → submissions-2SHCFOKD.js} +11 -6
- package/dist/submissions-2SHCFOKD.js.map +1 -0
- package/dist/{svelte-codemods-QWGIRNN6.js → svelte-codemods-VOLOKKFI.js} +6 -6
- package/dist/{sync-NKPDCMYW.js → sync-F4USL4K5.js} +6 -6
- package/dist/{sync-configs-HGLQV55H.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-WZ44IARG.js → upgrade-EVAL3JTO.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-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-MZZDMBDT.js.map +0 -1
- package/dist/chunk-OTEVKRAH.js.map +0 -1
- package/dist/chunk-P4A6D6HO.js.map +0 -1
- package/dist/chunk-UIITN37J.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/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/report-RC5LGPU3.js.map +0 -1
- package/dist/report-mirror-KKZZ6RMZ.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-775VLDYB.js.map} +0 -0
- /package/dist/{chunk-IMYL67JV.js.map → chunk-3RLDX3Q2.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-RRE74VWI.js.map → chunk-HSMN35LU.js.map} +0 -0
- /package/dist/{chunk-ECDJDHKT.js.map → chunk-JHJNNJLG.js.map} +0 -0
- /package/dist/{chunk-XY7YV2YQ.js.map → chunk-L3S3QZXL.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-MMSMZC7S.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/{chunk-3EA43S3R.js.map → chunk-U2ZI6G7H.js.map} +0 -0
- /package/dist/{chunk-EQQ4H6L3.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-4DON7IFS.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
- /package/dist/{db-5K4CDEGJ.js.map → db-II5WAK4D.js.map} +0 -0
- /package/dist/{client-L7NAUG6J.js.map → digest-collectors-CYTN2EUX.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-BJ6BKRHF.js.map} +0 -0
- /package/dist/{health-mirror-ONRT4DQ5.js.map → health-mirror-KU3G7V2Y.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-GOWDA5QS.js.map} +0 -0
- /package/dist/{import-airtable-IIK2QUUF.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
- /package/dist/{lighthouse-airtable-3NOFB42B.js.map → migrate-XZ7U5DFC.js.map} +0 -0
- /package/dist/{onboard-CZYBWVWH.js.map → onboard-JQQIMQW7.js.map} +0 -0
- /package/dist/{migrate-NE3DUMTM.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
- /package/dist/{orchestrate-TGDFGY52.js.map → parity-2JGVUCZD.js.map} +0 -0
- /package/dist/{prospect-audit-AJEE3Z4L.js.map → prospect-audit-FMPGQIRQ.js.map} +0 -0
- /package/dist/{parity-AEKYSLN6.js.map → render-from-row-75JTED2Z.js.map} +0 -0
- /package/dist/{self-updating-RKKIYNV4.js.map → self-updating-ZUWPGYUP.js.map} +0 -0
- /package/dist/{smoke-suite-BWHR3XX5.js.map → smoke-suite-WAUPI6EJ.js.map} +0 -0
- /package/dist/{svelte-codemods-QWGIRNN6.js.map → svelte-codemods-VOLOKKFI.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-OXSWKVZ2.js.map} +0 -0
- /package/dist/{upgrade-WZ44IARG.js.map → upgrade-EVAL3JTO.js.map} +0 -0
- /package/dist/{render-from-row-65DC6ZP3.js.map → websites-Y6FN2XFD.js.map} +0 -0
- /package/dist/{websites-6DHIHJ2D.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
describeNotifyTarget
|
|
3
3
|
} from "./chunk-PXRN2MER.js";
|
|
4
4
|
import "./chunk-Z7FDJ35L.js";
|
|
5
|
-
import "./chunk-5ML7FMBD.js";
|
|
6
5
|
import "./chunk-NO74DXDP.js";
|
|
6
|
+
import "./chunk-5ML7FMBD.js";
|
|
7
7
|
import "./chunk-I7NHLVVI.js";
|
|
8
8
|
import {
|
|
9
9
|
openBase,
|
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
} from "./chunk-HN6I2XWI.js";
|
|
12
12
|
import "./chunk-BGTYPVLT.js";
|
|
13
13
|
import {
|
|
14
|
-
canonicalizeStatus,
|
|
15
|
-
listWebsites,
|
|
16
|
-
siteSlug,
|
|
17
14
|
toAirtableStatus,
|
|
18
15
|
updateSiteField
|
|
19
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TC4GANMX.js";
|
|
17
|
+
import {
|
|
18
|
+
canonicalizeStatus,
|
|
19
|
+
siteSlug
|
|
20
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
21
|
+
import "./chunk-RQJFGK3A.js";
|
|
20
22
|
import "./chunk-2OP4JKIQ.js";
|
|
21
23
|
import "./chunk-6VAL7XJT.js";
|
|
22
24
|
|
|
@@ -38,14 +40,14 @@ function findSite(rows, site) {
|
|
|
38
40
|
}
|
|
39
41
|
async function formsNotifyTarget(deps) {
|
|
40
42
|
const base = deps.base ?? openBase(readAirtableConfig());
|
|
41
|
-
const rows = await
|
|
43
|
+
const rows = await deps.roster();
|
|
42
44
|
const row = findSite(rows, deps.site);
|
|
43
45
|
if (!row) {
|
|
44
46
|
const needle = siteSlug(deps.site);
|
|
45
47
|
const near = rows.map((r) => r.name).filter((n) => siteSlug(n).includes(needle) || needle.includes(siteSlug(n)));
|
|
46
48
|
throw Object.assign(
|
|
47
49
|
new Error(
|
|
48
|
-
`No
|
|
50
|
+
`No site matches '${deps.site}'. The stored NAME is not always the repo slug (Sonder, not gallerysonder).` + (near.length > 0 ? ` Did you mean: ${near.join(", ")}?` : "")
|
|
49
51
|
),
|
|
50
52
|
{ exitCode: 2 }
|
|
51
53
|
);
|
|
@@ -73,10 +75,10 @@ async function formsNotifyTarget(deps) {
|
|
|
73
75
|
}
|
|
74
76
|
const cell = deps.set === "on" ? toAirtableStatus(VERIFY_STATUS) : restoreCell(restoreRaw);
|
|
75
77
|
await updateSiteField(base, row.id, STATUS_COLUMN, cell);
|
|
76
|
-
await deps.siteMirror
|
|
77
|
-
const after = findSite(await
|
|
78
|
+
await deps.siteMirror.site(row.id, { [STATUS_COLUMN]: cell });
|
|
79
|
+
const after = findSite(await deps.roster(), deps.site);
|
|
78
80
|
if (!after) {
|
|
79
|
-
throw Object.assign(new Error(`${row.name} vanished from
|
|
81
|
+
throw Object.assign(new Error(`${row.name} vanished from the fleet during the flip.`), {
|
|
80
82
|
exitCode: 1
|
|
81
83
|
});
|
|
82
84
|
}
|
|
@@ -137,15 +139,17 @@ function formatNotifyTarget(r) {
|
|
|
137
139
|
}
|
|
138
140
|
async function runFormsNotifyTargetCommand(site, opts) {
|
|
139
141
|
if (!site?.trim()) {
|
|
140
|
-
return { output: "forms-notify-target requires <site> (slug or
|
|
142
|
+
return { output: "forms-notify-target requires <site> (slug or site name)", code: 2 };
|
|
141
143
|
}
|
|
142
144
|
const set = opts.set?.trim().toLowerCase();
|
|
143
145
|
if (set !== void 0 && set !== "on" && set !== "off") {
|
|
144
146
|
return { output: `--set must be 'on' or 'off' (got '${opts.set}')`, code: 2 };
|
|
145
147
|
}
|
|
146
148
|
try {
|
|
147
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
149
|
+
const { makeSiteMirror } = await import("./site-mirror-WR7ACXOV.js");
|
|
150
|
+
const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
|
|
148
151
|
const result = await formsNotifyTarget({
|
|
152
|
+
roster: () => readFleetRoster(),
|
|
149
153
|
siteMirror: await makeSiteMirror(),
|
|
150
154
|
site: site.trim(),
|
|
151
155
|
...set ? { set } : {},
|
|
@@ -164,4 +168,4 @@ export {
|
|
|
164
168
|
formatNotifyTarget,
|
|
165
169
|
runFormsNotifyTargetCommand
|
|
166
170
|
};
|
|
167
|
-
//# sourceMappingURL=forms-notify-target-
|
|
171
|
+
//# sourceMappingURL=forms-notify-target-X6UEOI2M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/recipes/forms-notify-target.ts","../src/cli/commands/forms-notify-target.ts"],"sourcesContent":["import { openBase, readAirtableConfig } from \"../reports/airtable/client.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport type { SiteMirror } from \"../db/site-mirror.js\";\nimport {\n siteSlug,\n updateSiteField,\n type Status,\n type WebsiteRow,\n} from \"../reports/airtable/websites.js\";\nimport { canonicalizeStatus, toAirtableStatus } from \"../reports/airtable/site-status.js\";\nimport { describeNotifyTarget, type NotifyTarget } from \"../forms/notify.js\";\n\n/** The Airtable column the pre-launch guard actually lives in. */\nexport const STATUS_COLUMN = \"Status\";\n\n/** The two ends of the verify flip. Deliberately the ONLY transition this\n * performs: a site in any other status is already guarded (or is deliberately\n * something else, like \"hosted-only\"), and silently rewriting that would be the\n * same class of unseen change as the incident. */\nexport const LIVE_STATUS: Status = \"maintained\";\nexport const VERIFY_STATUS: Status = \"launching\";\n\nexport type FormsNotifyTargetDeps = {\n base?: AirtableBase;\n /** Every site in the fleet, read from TURSO (#646 step 4). Required, not\n * defaulted: the unit suite drives this with a fake fleet, and a default would\n * open a real libSQL handle from inside it. The CLI wires `readFleetRoster`.\n *\n * It is called TWICE on a flip — once to find the site, once to read the cell\n * back — and both reads matter: Turso is what `/api/forms/:slug` consults to\n * decide who a submission emails (`getSiteBySlug`, #643), so confirming the\n * Airtable cell would confirm the wrong store, and an Airtable roster cannot\n * see a `site_<ULID>` site at all. */\n roster: () => Promise<WebsiteRow[]>;\n /** Site slug or the stored site NAME (both accepted). */\n site: string;\n /** `on` routes notifications to the operator; `off` restores. Omit to read. */\n set?: \"on\" | \"off\";\n /** Status to restore with `--set off`. Required, never inferred. */\n restore?: string;\n /** The Status write itself (#646 step 4): Turso is the store the form ingest\n * reads, so this is the write the read-back below confirms — the Airtable\n * `updateSiteField` beside it is the shadow, and it skips a `site_` id.\n * Required, and injected at the CLI root for the same reason `roster` is. */\n siteMirror: SiteMirror;\n};\n\nexport type FormsNotifyTargetResult = {\n site: string;\n status: Status | null;\n target: NotifyTarget;\n /** Present only when a flip was attempted. `confirmed` is the whole point:\n * it comes from RE-READING the row, not from the write call returning. */\n flip?: { from: Status | null; to: Status; confirmed: boolean };\n};\n\n/**\n * The exact Airtable cell to write for an operator-supplied `--restore` value:\n * the operator's own string, verbatim.\n *\n * This is the ONE non-revertible surface in the #539 Phase 4 stage-1 rename —\n * every other change is code, and `git revert` undoes code. It cannot undo a\n * rewritten Airtable cell. So the rule here is stricter than everywhere else:\n * substitute only when the substitution is provably lossless, i.e. when\n * canonicalizing and mapping back ROUND-TRIPS to the operator's own string.\n *\n * That condition is, today, never false in a way that changes the answer — which\n * is the point. `toAirtableStatus(canonicalizeStatus(raw))` either equals `raw`\n * (so writing it is writing `raw`) or it does not (so we must write `raw`). The\n * function therefore reduces to \"write raw\", and it is written this way so the\n * reduction is visible rather than assumed. The case that made it matter:\n * `--restore legacy` canonicalizes to `archived`, which maps back to\n * \"deprecated\" — a different, real, operator-visible Airtable option that nobody\n * asked for. `hosting` → `hosted-only` → \"hosting\" round-trips and is safe.\n *\n * Airtable, not this module, is the authority on which option strings the\n * \"Status\" single-select accepts. Writing verbatim delegates to it: a typo is\n * rejected loudly at the API, exactly as it was before the rename.\n */\nexport function restoreCell(raw: string): string {\n const canonical = canonicalizeStatus(raw);\n if (canonical === null) return raw;\n const roundTripped = toAirtableStatus(canonical);\n return roundTripped === raw ? roundTripped : raw;\n}\n\nfunction findSite(rows: WebsiteRow[], site: string): WebsiteRow | undefined {\n const wanted = site.trim().toLowerCase();\n return rows.find(\n (r) => siteSlug(r.name) === siteSlug(site) || r.name.trim().toLowerCase() === wanted,\n );\n}\n\n/**\n * Answer \"who would a form submission on this site email?\" — and optionally\n * flip the pre-launch guard, confirming the flip by reading it back.\n *\n * The guard is a single Airtable `Status` cell. Nothing between \"I intended to\n * flip it\" and \"the client received a test lead\" reported the current state, so\n * on 2026-08-03 a flip that never landed sent a real client a test submission.\n * The fix is not a better intention, it is feedback: this reads the row back\n * after every write and refuses to call the flip confirmed on anything less.\n */\nexport async function formsNotifyTarget(\n deps: FormsNotifyTargetDeps,\n): Promise<FormsNotifyTargetResult> {\n const base = deps.base ?? openBase(readAirtableConfig());\n const rows = await deps.roster();\n const row = findSite(rows, deps.site);\n if (!row) {\n // The Websites NAME is not the repo slug (\"Sonder\", not \"gallerysonder\"),\n // and that mismatch has cost time before — so name the near misses rather\n // than making the operator go read Airtable to find the spelling.\n const needle = siteSlug(deps.site);\n const near = rows\n .map((r) => r.name)\n .filter((n) => siteSlug(n).includes(needle) || needle.includes(siteSlug(n)));\n throw Object.assign(\n new Error(\n `No site matches '${deps.site}'. The stored NAME is not always the repo ` +\n `slug (Sonder, not gallerysonder).` +\n (near.length > 0 ? ` Did you mean: ${near.join(\", \")}?` : \"\"),\n ),\n { exitCode: 2 },\n );\n }\n\n if (!deps.set) {\n return { site: row.name, status: row.status, target: describeNotifyTarget(row) };\n }\n\n // `--restore` is operator free text. It is canonicalized for what this command\n // REPORTS (`flip.to`, and the predicates downstream), but NEVER for what it\n // WRITES — see `restoreCell` below.\n const restoreRaw = deps.restore?.trim();\n const to = deps.set === \"on\" ? VERIFY_STATUS : (canonicalizeStatus(restoreRaw) ?? undefined);\n if (deps.set === \"off\" && !to) {\n throw Object.assign(\n new Error(\n `--set off needs --restore <status>: the status to return to is never inferred. ` +\n `Guessing \"${LIVE_STATUS}\" for a site that was \"hosted-only\" or \"archived\" would start ` +\n `sending real client notifications — the inverse of the failure this command exists ` +\n `to prevent.`,\n ),\n { exitCode: 2 },\n );\n }\n // Only ever flip a LIVE site into verify mode. A site already outside\n // \"maintained\" is guarded already, and rewriting its status would destroy a\n // real value nobody asked us to touch.\n if (deps.set === \"on\" && row.status !== LIVE_STATUS) {\n throw Object.assign(\n new Error(\n `${row.name} is \"${row.status ?? \"blank\"}\", not \"${LIVE_STATUS}\" — notifications ` +\n `already go to the operator only, so there is nothing to flip. Leaving the status ` +\n `untouched.`,\n ),\n { exitCode: 2 },\n );\n }\n\n // `--set on` writes a status this MODULE owns (VERIFY_STATUS), so mapping it to\n // the current Airtable vocabulary is correct. `--set off` writes the operator's\n // own string — see restoreCell.\n const cell = deps.set === \"on\" ? toAirtableStatus(VERIFY_STATUS) : restoreCell(restoreRaw!);\n // The Airtable shadow first, still allowed to fail loudly while it is kept\n // trustworthy; it skips a site id Airtable cannot hold (#646 step 3).\n await updateSiteField(base, row.id, STATUS_COLUMN, cell);\n // The write that DECIDES: `/api/forms/:slug` reads this cell from Turso, and\n // the console reads it there too.\n await deps.siteMirror.site(row.id, { [STATUS_COLUMN]: cell });\n\n // Read it back — from Turso, the store just confirmed to be the one that\n // matters. The write returning is NOT evidence the field changed.\n const after = findSite(await deps.roster(), deps.site);\n if (!after) {\n throw Object.assign(new Error(`${row.name} vanished from the fleet during the flip.`), {\n exitCode: 1,\n });\n }\n return {\n site: after.name,\n status: after.status,\n target: describeNotifyTarget(after),\n // `confirmed` compares the RAW cell against the exact string written, not\n // canonical-to-canonical. Canonical equality is too weak to be a read-back\n // guard here: `legacy` and `deprecated` are the same canonical status, so a\n // cell holding either would confirm a flip to the other. Comparing raw is the\n // only comparison that can actually catch \"the cell is not what I sent\". The\n // canonical check rides along so a reader that stopped canonicalizing is\n // caught too.\n flip: {\n from: row.status,\n to: to!,\n confirmed: after.statusRaw === cell && after.status === to,\n },\n };\n}\n","import {\n formsNotifyTarget,\n LIVE_STATUS,\n VERIFY_STATUS,\n type FormsNotifyTargetResult,\n} from \"../../recipes/forms-notify-target.js\";\n\nexport type FormsNotifyTargetCommandOptions = {\n set?: string;\n restore?: string;\n cwd?: string;\n};\n\nconst AUDIENCE_LABEL = {\n client: \"THE CLIENT\",\n operator: \"OPERATOR ONLY\",\n nobody: \"NOBODY\",\n} as const;\n\n/** The line that decides whether it is safe to test-submit. Deliberately the\n * loudest thing on screen: the incident happened because the answer was\n * invisible, not because it was subtle. */\nexport function formatNotifyTarget(r: FormsNotifyTargetResult): string {\n const lines: string[] = [];\n if (r.flip) {\n const arrow = `${r.flip.from ?? \"blank\"} → ${r.flip.to}`;\n lines.push(\n r.flip.confirmed\n ? `${r.site} Status: ${arrow} ✓ confirmed by read-back`\n : `${r.site} Status: ${arrow} ✗ NOT CONFIRMED — the row still reads \"${r.status ?? \"blank\"}\". Nothing was verified; do not test-submit.`,\n );\n } else {\n lines.push(`${r.site} Status: ${r.status ?? \"blank\"}`);\n }\n\n lines.push(`A submission right now would notify: ${AUDIENCE_LABEL[r.target.audience]}`);\n if (r.target.to.length > 0) lines.push(` to: ${r.target.to.join(\", \")}`);\n if (r.target.cc.length > 0) lines.push(` cc: ${r.target.cc.join(\", \")}`);\n lines.push(` ${r.target.reason}`);\n\n if (r.target.audience === \"client\") {\n lines.push(\n \"\",\n `⚠️ A test submission WILL email the client, and email cannot be recalled.`,\n ` Route it to yourself first: reddoor forms-notify-target ${r.site} --set on`,\n );\n }\n if (r.flip?.confirmed && r.flip.to === VERIFY_STATUS) {\n lines.push(\n \"\",\n `Safe to test. When you are done, restore it:`,\n ` reddoor forms-notify-target ${r.site} --set off --restore ${r.flip.from ?? LIVE_STATUS}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * `forms-notify-target <site>` — show who a form submission would email, and\n * optionally flip the pre-launch guard with a read-back confirmation.\n *\n * Read-only by default: answering the question must never be riskier than not\n * asking it.\n */\nexport async function runFormsNotifyTargetCommand(\n site: string | undefined,\n opts: FormsNotifyTargetCommandOptions,\n): Promise<{ output: string; code: number }> {\n if (!site?.trim()) {\n return { output: \"forms-notify-target requires <site> (slug or site name)\", code: 2 };\n }\n const set = opts.set?.trim().toLowerCase();\n if (set !== undefined && set !== \"on\" && set !== \"off\") {\n return { output: `--set must be 'on' or 'off' (got '${opts.set}')`, code: 2 };\n }\n try {\n // #539 Phase 5: the flip writes Status on the Websites row; mirror it so the\n // console (which reads Turso) shows the new routing immediately.\n const { makeSiteMirror } = await import(\"../../db/site-mirror.js\");\n // #646 step 4: the fleet roster — and the flip's read-back — come from Turso,\n // which is the store `/api/forms/:slug` reads to decide who a submission\n // emails, and the only one that can see a `site_<ULID>` site.\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n const result = await formsNotifyTarget({\n roster: () => readFleetRoster(),\n siteMirror: await makeSiteMirror(),\n site: site.trim(),\n ...(set ? { set: set as \"on\" | \"off\" } : {}),\n ...(opts.restore ? { restore: opts.restore } : {}),\n });\n // An unconfirmed flip must not exit 0: a script (or a person skimming) that\n // reads exit status would otherwise take \"I flipped it\" on faith, which is\n // exactly the assumption that sent a client a test lead.\n return {\n output: formatNotifyTarget(result),\n code: result.flip && !result.flip.confirmed ? 1 : 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":[]}
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
} from "./chunk-PZQ3WED2.js";
|
|
4
4
|
import {
|
|
5
5
|
formatFleetWriteSummary
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-FHFXJ3EM.js";
|
|
7
|
+
import "./chunk-X56LAKPR.js";
|
|
8
8
|
import "./chunk-WQLNUEZL.js";
|
|
9
9
|
import {
|
|
10
10
|
detectSignalEvents,
|
|
@@ -15,19 +15,21 @@ import {
|
|
|
15
15
|
readAirtableConfig
|
|
16
16
|
} from "./chunk-HN6I2XWI.js";
|
|
17
17
|
import {
|
|
18
|
-
listWebsites,
|
|
19
|
-
siteSlug,
|
|
20
18
|
updateGitHubSignals
|
|
21
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-TC4GANMX.js";
|
|
20
|
+
import {
|
|
21
|
+
siteSlug
|
|
22
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
23
|
+
import "./chunk-RQJFGK3A.js";
|
|
22
24
|
import {
|
|
23
25
|
makeGitHub
|
|
24
26
|
} from "./chunk-43SW3FGW.js";
|
|
25
27
|
import {
|
|
26
28
|
recordFleetEventsBestEffort
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-T76MA2FH.js";
|
|
29
|
+
} from "./chunk-YXWBM26P.js";
|
|
30
|
+
import "./chunk-MMSMZC7S.js";
|
|
30
31
|
import "./chunk-LBYOLBW7.js";
|
|
32
|
+
import "./chunk-IOTOOTII.js";
|
|
31
33
|
import "./chunk-6VAL7XJT.js";
|
|
32
34
|
|
|
33
35
|
// src/audits/github-signals.ts
|
|
@@ -61,18 +63,21 @@ function githubSignalsExitCode(written, failed) {
|
|
|
61
63
|
return failed > written ? 1 : 0;
|
|
62
64
|
}
|
|
63
65
|
async function runGitHubSignalsCommand(opts, deps = {}) {
|
|
64
|
-
if (!opts.fleet || !opts.
|
|
65
|
-
return { output: "github-signals currently supports only --fleet --write-
|
|
66
|
+
if (!opts.fleet || !opts.writeBack) {
|
|
67
|
+
return { output: "github-signals currently supports only --fleet --write-back", code: 2 };
|
|
66
68
|
}
|
|
67
|
-
const token = process.env.
|
|
69
|
+
const token = process.env.GH_TOKEN?.trim();
|
|
68
70
|
if (!token) {
|
|
69
71
|
return {
|
|
70
|
-
output: "github-signals skipped: no
|
|
72
|
+
output: "github-signals skipped: no GH_TOKEN (fleet read) configured.",
|
|
71
73
|
code: 0
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
const base = deps.openBase ? deps.openBase() : openBase(readAirtableConfig());
|
|
75
|
-
const websites = await
|
|
77
|
+
const websites = await (deps.roster ?? (async () => {
|
|
78
|
+
const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
|
|
79
|
+
return readFleetRoster();
|
|
80
|
+
}))();
|
|
76
81
|
const gh = deps.makeGh ? deps.makeGh(token) : makeGitHub({ token });
|
|
77
82
|
const sites = websites.map((w) => ({
|
|
78
83
|
path: "",
|
|
@@ -91,7 +96,7 @@ async function runGitHubSignalsCommand(opts, deps = {}) {
|
|
|
91
96
|
);
|
|
92
97
|
const sweptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
98
|
const makeMirror = deps.makeMirror ?? (async () => {
|
|
94
|
-
const { makeHealthMirrorBestEffort } = await import("./health-mirror-
|
|
99
|
+
const { makeHealthMirrorBestEffort } = await import("./health-mirror-KU3G7V2Y.js");
|
|
95
100
|
return makeHealthMirrorBestEffort();
|
|
96
101
|
});
|
|
97
102
|
const mirror = await makeMirror();
|
|
@@ -156,4 +161,4 @@ export {
|
|
|
156
161
|
githubSignalsExitCode,
|
|
157
162
|
runGitHubSignalsCommand
|
|
158
163
|
};
|
|
159
|
-
//# sourceMappingURL=github-signals-
|
|
164
|
+
//# sourceMappingURL=github-signals-QIRTHHET.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audits/github-signals.ts","../src/cli/commands/github-signals.ts"],"sourcesContent":["import type { Site } from \"../types.js\";\nimport type { CiState, PullRequestSummary } from \"../github/gh.js\";\nimport { isFailingRenovatePR } from \"../alerts/renovate.js\";\n\n/** One swept row, ready for the Airtable writer (slug-keyed by `site`). */\nexport type GitHubSignalsRow = {\n site: string; // the site name/slug the writer matches on\n repo: string; // owner/repo\n renovateFailingCis: number;\n ciState: CiState;\n lastCommitAt: string | null;\n};\n\n/** Injected GitHub reads (so the sweep is pure + testable). */\nexport type GitHubSignalsDeps = {\n openPullRequests: (repo: string) => Promise<PullRequestSummary[]>;\n defaultBranchStatus: (repo: string) => Promise<{ ciState: CiState; lastCommitAt: string | null }>;\n};\n\n/** Per repo-backed site: count its failing Renovate PRs + read its default-branch\n * status. Sites without `gitRepo` are skipped (not errors). A repo whose probe\n * throws is reported via `onSkip` and produces no row — one GitHub hiccup never\n * sinks the sweep. PURE over `deps`. */\nexport async function collectGitHubSignals(\n sites: Site[],\n deps: GitHubSignalsDeps,\n onSkip: (s: { repo: string }) => void = () => {},\n): Promise<GitHubSignalsRow[]> {\n const rows: GitHubSignalsRow[] = [];\n for (const s of sites) {\n const repo = s.gitRepo;\n if (!repo) continue;\n const name = s.name;\n if (!name) continue;\n try {\n const prs = await deps.openPullRequests(repo);\n const status = await deps.defaultBranchStatus(repo);\n rows.push({\n site: name,\n repo,\n renovateFailingCis: prs.filter(isFailingRenovatePR).length,\n ciState: status.ciState,\n lastCommitAt: status.lastCommitAt,\n });\n } catch {\n onSkip({ repo });\n }\n }\n return rows;\n}\n","import { openBase, readAirtableConfig, type AirtableBase } from \"../../reports/airtable/client.js\";\nimport { siteSlug, updateGitHubSignals } from \"../../reports/airtable/websites.js\";\nimport type { FleetRoster } from \"../../fleet/roster.js\";\nimport type { Site } from \"../../types.js\";\nimport { collectGitHubSignals } from \"../../audits/github-signals.js\";\nimport { makeGitHub, type GitHub } from \"../../github/gh.js\";\nimport {\n formatFleetWriteSummary,\n type FleetWriteResult,\n} from \"../../audits/write-audits-to-airtable.js\";\nimport { detectSignalEvents, fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport type { FleetEvent } from \"../../db/fleet-events.js\";\nimport type { HealthMirror } from \"../../audits/health-mirror.js\";\n\n/** The slice of the GitHub client this sweep actually probes. */\ntype GhProbes = Pick<\n GitHub,\n \"openPullRequests\" | \"defaultBranchStatus\" | \"mergedRenovatePullRequests\"\n>;\n\n/** Injectable wiring for {@link runGitHubSignalsCommand}. Every default is the\n * real fleet path; tests override to reach the per-row write+mirror loop —\n * the hand-rolled `makeHealthMirrorBestEffort()` call made that loop\n * untestable (no test could prove a mirror throw stays out of `failed`, that\n * the counters increment the right way round, or that the mirror sees the\n * same payload Airtable got). Same seam shape as\n * `writeFleetAuditsToAirtable`'s `mirror` argument. */\nexport type GitHubSignalsDeps = {\n /** Airtable base (default: real creds via readAirtableConfig). Still needed:\n * the signals write is an Airtable SHADOW write. */\n openBase: () => AirtableBase;\n /** #646 step 4: the fleet roster this sweep walks. Default: Turso\n * (`readFleetRoster`) — Airtable cannot see a `site_<ULID>` site at all. */\n roster: FleetRoster;\n /** GitHub probe client for the fleet token (default: makeGitHub). */\n makeGh: (token: string) => GhProbes;\n /** Turso mirror factory (default: makeHealthMirrorBestEffort — null without\n * libSQL creds, leaving the Airtable sweep byte-for-byte unchanged). */\n makeMirror: () => Promise<HealthMirror | null>;\n /** Fleet-activity recorder (default: recordFleetEventsBestEffort). */\n recordEvents: (events: FleetEvent[], now: Date) => Promise<void>;\n};\n\n/** Exit code for a fleet github-signals run. Exit 1 when failures are the\n * MAJORITY of the fleet (`failed > written`), not only on a total wipeout —\n * a run where 11/12 repos failed but 1 wrote should still signal an outage.\n * All-success or a minority of flakes (e.g. 1/12 failed) stays exit 0. The\n * no-token clean-skip returns 0 separately (before any probe runs). */\nexport function githubSignalsExitCode(written: number, failed: number): number {\n return failed > written ? 1 : 0;\n}\n\n/** `github-signals --fleet --write-back`: sweep every repo-backed site for its\n * Renovate-failing count + default-branch CI state + last-commit date, write each\n * row serially (Airtable ~5 req/sec), and emit FLEET_WRITE_SUMMARY for CI. A\n * missing fleet token is a clean skip (local runs), not a failure. */\nexport async function runGitHubSignalsCommand(\n opts: {\n fleet?: boolean | undefined;\n writeBack?: boolean | undefined;\n },\n deps: Partial<GitHubSignalsDeps> = {},\n): Promise<{ output: string; code: number }> {\n if (!opts.fleet || !opts.writeBack) {\n return { output: \"github-signals currently supports only --fleet --write-back\", code: 2 };\n }\n const token = process.env.GH_TOKEN?.trim();\n if (!token) {\n return {\n output: \"github-signals skipped: no GH_TOKEN (fleet read) configured.\",\n code: 0,\n };\n }\n const base = deps.openBase ? deps.openBase() : openBase(readAirtableConfig());\n const websites = await (\n deps.roster ??\n (async () => {\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n return readFleetRoster();\n })\n )();\n const gh: GhProbes = deps.makeGh ? deps.makeGh(token) : makeGitHub({ token });\n const sites: Site[] = websites.map((w) => ({\n path: \"\",\n name: w.name,\n meta: {},\n ...(w.gitRepo ? { gitRepo: w.gitRepo } : {}),\n }));\n\n const skipped: string[] = [];\n const rows = await collectGitHubSignals(\n sites,\n {\n openPullRequests: (r) => gh.openPullRequests(r),\n defaultBranchStatus: (r) => gh.defaultBranchStatus(r),\n },\n ({ repo }) => skipped.push(repo),\n );\n\n const sweptAt = new Date().toISOString();\n // Phase 3 dual-write (#539): mirror each row's written FieldSet into\n // site_health. Null when libSQL creds are absent — the Airtable sweep\n // proceeds exactly as before. (Dynamic import so the no-mirror path never\n // loads the db client.)\n const makeMirror =\n deps.makeMirror ??\n (async () => {\n const { makeHealthMirrorBestEffort } = await import(\"../../audits/health-mirror.js\");\n return makeHealthMirrorBestEffort();\n });\n const mirror = await makeMirror();\n const result: FleetWriteResult = {\n written: [],\n failed: [],\n ...(mirror ? { mirrored: 0, mirrorFailed: 0, mirrorMissed: 0 } : {}),\n };\n const byRepo = new Map(websites.filter((w) => w.gitRepo).map((w) => [w.gitRepo, w]));\n const events: FleetEvent[] = [];\n const sweptMs = Date.parse(sweptAt);\n const since24h = new Date(sweptMs - 24 * 60 * 60 * 1000).toISOString();\n // Serial: Airtable's ~5 req/sec limit (matches writeFleetAuditsToAirtable).\n for (const row of rows) {\n const target = byRepo.get(row.repo);\n if (!target) {\n result.failed.push({ slug: siteSlug(row.site), error: \"no Websites row matched\" });\n continue;\n }\n try {\n const ghFields = await updateGitHubSignals(base, target.id, {\n renovateFailingCis: row.renovateFailingCis,\n ciState: row.ciState,\n lastCommitAt: row.lastCommitAt,\n sweptAt,\n });\n if (mirror) {\n // Count, never throw: a Turso blip must not move an Airtable-written\n // row into `failed` (that would red the sweep via githubSignalsExitCode\n // on a fleet-wide mirror outage). A 0-row match (site not yet imported)\n // is a miss, not a mirror — see FleetWriteResult.mirrorMissed.\n try {\n if (await mirror(target.id, ghFields)) {\n result.mirrored = (result.mirrored ?? 0) + 1;\n } else {\n result.mirrorMissed = (result.mirrorMissed ?? 0) + 1;\n }\n } catch (e) {\n result.mirrorFailed = (result.mirrorFailed ?? 0) + 1;\n console.error(`[health-mirror] ${target.name}: ${(e as Error).message}`);\n }\n }\n result.written.push({\n siteName: target.name,\n writes: [{ audit: \"github-signals\", counts: row }],\n });\n // Fleet-activity events for this repo: merged Renovate PRs since the last sweep\n // (watermark = the row's prior GitHub Signals At, else a 24h fallback) + a\n // CI-recovered transition. A PR-fetch hiccup drops only this repo's PR events.\n const since = target.githubSignalsAt ?? since24h;\n let merged: Awaited<ReturnType<typeof gh.mergedRenovatePullRequests>> = [];\n try {\n merged = await gh.mergedRenovatePullRequests(row.repo, since);\n } catch {\n // PR list unavailable this run — skip pr_automerged for this repo, keep ci_recovered\n }\n events.push(...detectSignalEvents(target, row, merged, sweptAt));\n } catch (e) {\n result.failed.push({ slug: siteSlug(row.site), error: (e as Error).message });\n }\n }\n for (const repo of skipped) result.failed.push({ slug: repo, error: \"probe failed (skipped)\" });\n\n events.push(fleetSweptEvent(\"github-signals\", result.written.length, sweptAt));\n await (deps.recordEvents ?? recordFleetEventsBestEffort)(events, new Date());\n\n // Exit non-zero when failures are the MAJORITY of the fleet, not only on a\n // total wipeout. A run where 11/12 repos failed but 1 wrote used to return 0,\n // masking a large outage. The nightly cron step is `continue-on-error`, so a\n // non-zero here is an operator-visibility signal, not a red build.\n return {\n output: formatFleetWriteSummary(result),\n code: githubSignalsExitCode(result.written.length, result.failed.length),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
uploadAttachment
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
openBase,
|
|
6
|
-
readAirtableConfig
|
|
7
|
-
} from "./chunk-HN6I2XWI.js";
|
|
3
|
+
} from "./chunk-YJBSB4LP.js";
|
|
8
4
|
import {
|
|
9
5
|
generateHeaderImage
|
|
10
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LZMAG73U.js";
|
|
7
|
+
import "./chunk-TC4GANMX.js";
|
|
11
8
|
import {
|
|
12
9
|
ACTIVE_STATUSES,
|
|
13
|
-
listWebsites,
|
|
14
10
|
siteSlug
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-ICSRWW5Q.js";
|
|
12
|
+
import "./chunk-RQJFGK3A.js";
|
|
17
13
|
|
|
18
14
|
// src/cli/commands/header-image.ts
|
|
19
15
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -44,7 +40,7 @@ function parseSettleMs(value) {
|
|
|
44
40
|
async function generateForTargets(targets, opts, generate = generateHeaderImage) {
|
|
45
41
|
const outDir = resolve(opts.outDir ?? "reports");
|
|
46
42
|
const settleMs = parseSettleMs(opts.settleMs);
|
|
47
|
-
if (!opts.
|
|
43
|
+
if (!opts.writeBack) await mkdir(outDir, { recursive: true });
|
|
48
44
|
const lines = [];
|
|
49
45
|
let failed = 0;
|
|
50
46
|
for (const row of targets) {
|
|
@@ -57,7 +53,7 @@ async function generateForTargets(targets, opts, generate = generateHeaderImage)
|
|
|
57
53
|
...settleMs === void 0 ? {} : { settleMs },
|
|
58
54
|
...opts.consentSelector === void 0 ? {} : { consentSelector: opts.consentSelector }
|
|
59
55
|
});
|
|
60
|
-
if (opts.
|
|
56
|
+
if (opts.writeBack) {
|
|
61
57
|
await uploadAttachment(row.id, "Header image", gen.bytes, gen.filename, gen.contentType, {
|
|
62
58
|
replaceIn: "Websites"
|
|
63
59
|
});
|
|
@@ -81,7 +77,7 @@ async function generateForTargets(targets, opts, generate = generateHeaderImage)
|
|
|
81
77
|
} else {
|
|
82
78
|
const path = resolve(outDir, gen.filename);
|
|
83
79
|
await writeFile(path, gen.bytes);
|
|
84
|
-
lines.push(`\u2714 ${row.name} \u2014 wrote ${path} (review, then re-run with --write-
|
|
80
|
+
lines.push(`\u2714 ${row.name} \u2014 wrote ${path} (review, then re-run with --write-back)`);
|
|
85
81
|
}
|
|
86
82
|
} catch (err) {
|
|
87
83
|
failed++;
|
|
@@ -92,8 +88,8 @@ async function generateForTargets(targets, opts, generate = generateHeaderImage)
|
|
|
92
88
|
return { output: lines.join("\n"), code: failed > 0 ? 1 : 0 };
|
|
93
89
|
}
|
|
94
90
|
async function runHeaderImageCommand(site, opts) {
|
|
95
|
-
const
|
|
96
|
-
const rows = await
|
|
91
|
+
const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
|
|
92
|
+
const rows = await readFleetRoster();
|
|
97
93
|
const targets = resolveTargets(rows, { site, all: opts.all, force: opts.force });
|
|
98
94
|
if (targets.length === 0) {
|
|
99
95
|
return {
|
|
@@ -102,8 +98,8 @@ async function runHeaderImageCommand(site, opts) {
|
|
|
102
98
|
};
|
|
103
99
|
}
|
|
104
100
|
let withStore = opts;
|
|
105
|
-
if (opts.
|
|
106
|
-
const { openDb, readDbConfig } = await import("./client-
|
|
101
|
+
if (opts.writeBack && !opts.storeDb && process.env.TURSO_DATABASE_URL) {
|
|
102
|
+
const { openDb, readDbConfig } = await import("./client-DGTO65WB.js");
|
|
107
103
|
const { storeHeaderImage } = await import("./header-images-SQBCE5OD.js");
|
|
108
104
|
const db = await openDb(readDbConfig());
|
|
109
105
|
withStore = { ...opts, storeDb: (id, img) => storeHeaderImage(db, id, img) };
|
|
@@ -116,4 +112,4 @@ export {
|
|
|
116
112
|
resolveTargets,
|
|
117
113
|
runHeaderImageCommand
|
|
118
114
|
};
|
|
119
|
-
//# sourceMappingURL=header-image-
|
|
115
|
+
//# sourceMappingURL=header-image-H7ZJAV26.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/header-image.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { siteSlug, ACTIVE_STATUSES } from \"../../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../../reports/airtable/websites.js\";\nimport { uploadAttachment } from \"../../reports/airtable/attachments.js\";\nimport { generateHeaderImage } from \"../../reports/header-image/index.js\";\nimport type { StoredHeaderImage } from \"../../db/header-images.js\";\n\nexport type HeaderImageOptions = {\n all?: boolean;\n force?: boolean;\n writeBack?: boolean;\n outDir?: string;\n settleMs?: string;\n consentSelector?: string;\n /** Injected Turso store for the dual-write (#539, design D5): while Airtable\n * is still written (until the Phase 5 freeze), every upload ALSO lands the\n * bytes in sites.header_image* so the Turso read layer serves a real image.\n * Absent → Airtable-only (a local run without Turso env still works). */\n storeDb?: (siteId: string, img: StoredHeaderImage) => Promise<void>;\n};\n\n/** Which rows this invocation should act on. Pure, so it is unit-tested. */\nexport function resolveTargets(\n rows: readonly WebsiteRow[],\n // `| undefined` spelled out because `exactOptionalPropertyTypes` otherwise\n // rejects the caller passing through possibly-undefined CLI flags.\n opts: { site?: string | undefined; all?: boolean | undefined; force?: boolean | undefined },\n): WebsiteRow[] {\n if (opts.site) {\n const want = siteSlug(opts.site);\n return rows.filter((r) => siteSlug(r.name) === want);\n }\n if (!opts.all) return [];\n return rows.filter((r) => {\n if (!r.url) return false;\n if (r.status === null || !ACTIVE_STATUSES.has(r.status)) return false;\n return opts.force ? true : !r.headerImage;\n });\n}\n\n/** Parse the `--settle-ms <ms>` flag. Unset → undefined (the generator's own\n * default settle). `Number(\"abc\")` is NaN, which would sail through as a NaN\n * timeout and silently skip the settle entirely, so anything non-finite or\n * negative is rejected up front rather than producing a subtly-wrong capture. */\nexport function parseSettleMs(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) {\n throw Object.assign(\n new Error(`--settle-ms must be a non-negative number of milliseconds, got \"${value}\"`),\n { exitCode: 2 },\n );\n }\n return n;\n}\n\n/** Injected in tests so the file-writing branch can be exercised without\n * driving a real browser (mirrors `GenerateInput.shooter`). */\ntype Generate = typeof generateHeaderImage;\n\n/**\n * Generate a header image per target row and either upload it to Airtable or\n * write it locally for review. Split out of {@link runHeaderImageCommand} so\n * the local-output path is testable without an Airtable base.\n */\nexport async function generateForTargets(\n targets: readonly WebsiteRow[],\n opts: HeaderImageOptions,\n generate: Generate = generateHeaderImage,\n): Promise<{ output: string; code: number }> {\n const outDir = resolve(opts.outDir ?? \"reports\");\n const settleMs = parseSettleMs(opts.settleMs);\n // `reports/` does not exist on a fresh checkout, so the writeFile() below\n // would throw ENOENT for the first operator to run the default (local) path.\n // Create it once, before the loop, rather than per site.\n if (!opts.writeBack) await mkdir(outDir, { recursive: true });\n\n const lines: string[] = [];\n let failed = 0;\n\n for (const row of targets) {\n try {\n const gen = await generate({\n url: row.url,\n slug: siteSlug(row.name),\n // `exactOptionalPropertyTypes` rejects an explicit `settleMs: undefined`\n // against `settleMs?: number`, so omit the key entirely when unset.\n ...(settleMs === undefined ? {} : { settleMs }),\n ...(opts.consentSelector === undefined ? {} : { consentSelector: opts.consentSelector }),\n });\n if (opts.writeBack) {\n // replaceIn: the field must hold exactly the current header — see\n // uploadAttachment, where appending left readers on a stale [0].\n await uploadAttachment(row.id, \"Header image\", gen.bytes, gen.filename, gen.contentType, {\n replaceIn: \"Websites\",\n });\n let stored = \"\";\n if (opts.storeDb) {\n // Dual-write. A Turso failure must not void the Airtable upload —\n // but it must be VISIBLE, never a silent divergence.\n try {\n await opts.storeDb(row.id, {\n bytes: gen.bytes,\n filename: gen.filename,\n contentType: gen.contentType,\n generatedAt: new Date().toISOString(),\n });\n stored = \" + turso\";\n } catch (err) {\n stored = ` (⚠ turso store FAILED: ${err instanceof Error ? err.message : String(err)})`;\n }\n }\n lines.push(\n `✔ ${row.name} — uploaded ${gen.filename} (${(gen.bytes.byteLength / 1024 / 1024).toFixed(2)} MB)${stored}`,\n );\n } else {\n const path = resolve(outDir, gen.filename);\n await writeFile(path, gen.bytes);\n lines.push(`✔ ${row.name} — wrote ${path} (review, then re-run with --write-back)`);\n }\n } catch (err) {\n failed++;\n lines.push(`✖ ${row.name} — ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n lines.push(`${targets.length - failed}/${targets.length} generated.`);\n return { output: lines.join(\"\\n\"), code: failed > 0 ? 1 : 0 };\n}\n\n/**\n * `header-image [site]` — capture a site's live homepage and composite its\n * report header image. Defaults to writing the JPEG locally so the operator can\n * eyeball it; `--write-back` uploads it to the Websites row's Header image\n * field. `--all` backfills every live site that has no header image yet.\n */\nexport async function runHeaderImageCommand(\n site: string | undefined,\n opts: HeaderImageOptions,\n): Promise<{ output: string; code: number }> {\n // #646 step 4: the roster comes from Turso, which holds every site. That also\n // makes `--all` honest about what it backfills: `headerImage` on a Turso row\n // reflects `sites.header_image*`, the bytes the report actually renders from\n // (design D5), not the Airtable attachment the send path no longer needs.\n const { readFleetRoster } = await import(\"../../fleet/roster.js\");\n const rows = await readFleetRoster();\n const targets = resolveTargets(rows, { site, all: opts.all, force: opts.force });\n if (targets.length === 0) {\n return {\n output: site ? `No site matched \"${site}\".` : \"No sites need a header image.\",\n code: 1,\n };\n }\n // Wire the Turso dual-write when the env is present; a local run without it\n // still works Airtable-only (the store is per-site error-isolated above).\n let withStore = opts;\n if (opts.writeBack && !opts.storeDb && process.env.TURSO_DATABASE_URL) {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n const { storeHeaderImage } = await import(\"../../db/header-images.js\");\n const db = await openDb(readDbConfig());\n withStore = { ...opts, storeDb: (id, img) => storeHeaderImage(db, id, img) };\n }\n return generateForTargets(targets, withStore);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,21 +5,21 @@ import {
|
|
|
5
5
|
appendSkipNotice,
|
|
6
6
|
fleetWorkdir,
|
|
7
7
|
prepareFleetSites
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DYTYPKFN.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveSites
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FP6HOW6J.js";
|
|
12
12
|
import {
|
|
13
13
|
healthEndpoint
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LAG6IP7Y.js";
|
|
15
|
+
import "./chunk-TJLKWQOX.js";
|
|
15
16
|
import "./chunk-5CJVUPJU.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-SQ4FSNYL.js";
|
|
17
|
+
import "./chunk-LUF52EIY.js";
|
|
18
18
|
import "./chunk-Z3JXLULD.js";
|
|
19
19
|
import "./chunk-LBYOLBW7.js";
|
|
20
20
|
import "./chunk-RARREDQE.js";
|
|
21
21
|
import "./chunk-XXTZBPUY.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-WMF5S2MB.js";
|
|
23
23
|
|
|
24
24
|
// src/cli/commands/health-endpoint.ts
|
|
25
25
|
import { resolve } from "path";
|
|
@@ -52,4 +52,4 @@ async function runHealthEndpointCommand(site, opts) {
|
|
|
52
52
|
export {
|
|
53
53
|
runHealthEndpointCommand
|
|
54
54
|
};
|
|
55
|
-
//# sourceMappingURL=health-endpoint-
|
|
55
|
+
//# sourceMappingURL=health-endpoint-BJ6BKRHF.js.map
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mirrorHealthFields,
|
|
3
|
-
mirrorScheduleFields
|
|
4
|
-
} from "./chunk-GEUC5VGM.js";
|
|
5
|
-
import "./chunk-UIITN37J.js";
|
|
6
1
|
import {
|
|
7
2
|
TURSO_IS_AUTHORITATIVE
|
|
8
3
|
} from "./chunk-WQLNUEZL.js";
|
|
9
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
mirrorHealthFields,
|
|
6
|
+
mirrorScheduleFields
|
|
7
|
+
} from "./chunk-BINFZQ2T.js";
|
|
8
|
+
import "./chunk-LF2TJN4X.js";
|
|
9
|
+
import "./chunk-ASLDCWBF.js";
|
|
10
10
|
import "./chunk-VAD43MOR.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-ICSRWW5Q.js";
|
|
12
12
|
import {
|
|
13
13
|
openDb,
|
|
14
14
|
readDbConfig
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-MMSMZC7S.js";
|
|
16
|
+
import "./chunk-IOTOOTII.js";
|
|
17
17
|
import "./chunk-6VAL7XJT.js";
|
|
18
18
|
|
|
19
19
|
// src/audits/health-mirror.ts
|
|
@@ -43,4 +43,4 @@ export {
|
|
|
43
43
|
makeHealthMirrorBestEffort,
|
|
44
44
|
makeScheduleMirrorBestEffort
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=health-mirror-
|
|
46
|
+
//# sourceMappingURL=health-mirror-KU3G7V2Y.js.map
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
renderedHtmlUrl,
|
|
15
15
|
scheduleColumnFor,
|
|
16
16
|
siteValueFor
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-LF2TJN4X.js";
|
|
18
18
|
import "./chunk-VAD43MOR.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-ICSRWW5Q.js";
|
|
20
20
|
export {
|
|
21
21
|
EXCLUDED_WEBSITE_FIELDS,
|
|
22
22
|
HEALTH_BOOLEAN,
|
|
@@ -34,4 +34,4 @@ export {
|
|
|
34
34
|
scheduleColumnFor,
|
|
35
35
|
siteValueFor
|
|
36
36
|
};
|
|
37
|
-
//# sourceMappingURL=import-airtable-
|
|
37
|
+
//# sourceMappingURL=import-airtable-UBF5GYAL.js.map
|