@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audits/lighthouse-airtable.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport type { LighthouseScoreWriteback } from \"../reports/types.js\";\nimport { siteSlug } from \"../reports/airtable/websites.js\";\n\nconst LIGHTHOUSE_CATEGORIES = [\"performance\", \"accessibility\", \"best-practices\", \"seo\"] as const;\n\n/**\n * True when the result carries real lighthouse scores worth persisting.\n *\n * The `audit --write-back` policy used to refuse on any `status: \"fail\"`,\n * but that conflates two very different failure modes:\n * 1. Infrastructure failure (no lhr-*.json written, spawn timeout, etc.)\n * → `details.summary` empty → all-zeros would corrupt the dashboard\n * 2. Assertion failure (scores below threshold, e.g. best-practices < 0.9)\n * → `details.summary` has real numbers → tracking these IS the point\n *\n * The dashboard exists to surface drift over time. Refusing to write the\n * very numbers the dashboard wants to plot — just because one assertion\n * tripped — defeats the purpose. Write whenever real scores exist.\n */\nexport function hasRealScores(result: AuditResult): boolean {\n if (result.audit !== \"lighthouse\") return false;\n const details = (result.details ?? {}) as { summary?: Record<string, number> };\n const summary = details.summary ?? {};\n return LIGHTHOUSE_CATEGORIES.some(\n (k) => typeof summary[k] === \"number\" && !Number.isNaN(summary[k]),\n );\n}\n\n/**\n * Extract the four Lighthouse scores (as integer percentages) from a\n * `lighthouse` AuditResult. LHCI manifest summaries are floats in [0,1];\n * multiply + round. A category absent from the summary — e.g. Lighthouse\n * errored its audit (NO_LCP nulls the whole performance category) — yields\n * `null`, NOT 0, so the write path can clear the Airtable cell (\"—\") instead\n * of persisting a misleading zero. (`hasRealScores` still gates the write on\n * at least one real number, so an all-null infra failure is refused upstream.)\n */\nexport function lighthouseScoresFromResult(result: AuditResult): LighthouseScoreWriteback {\n if (result.audit !== \"lighthouse\") {\n throw new Error(`Expected a 'lighthouse' AuditResult, got '${result.audit}'`);\n }\n const details = (result.details ?? {}) as { summary?: Record<string, number> };\n const summary = details.summary ?? {};\n const toPct = (n: number | undefined): number | null =>\n typeof n === \"number\" && !Number.isNaN(n) ? Math.round(n * 100) : null;\n return {\n performance: toPct(summary[\"performance\"]),\n accessibility: toPct(summary[\"accessibility\"]),\n bestPractices: toPct(summary[\"best-practices\"]),\n seo: toPct(summary[\"seo\"]),\n };\n}\n\n/**\n * Derive a site slug from the cwd's package.json#name. Used by\n * `audit lighthouse --write-back` when the operator doesn't pass an\n * explicit slug — the cwd is the site checkout, so package.json#name is\n * usually the canonical site name.\n */\nexport async function resolveSlugFromCwd(cwd: string): Promise<string> {\n try {\n const pkgPath = join(cwd, \"package.json\");\n const raw = await readFile(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string };\n if (!pkg.name) throw new Error(\"package.json has no 'name' field\");\n return siteSlug(pkg.name);\n } catch (e) {\n throw new Error(\n `Could not derive site slug from ${cwd}/package.json: ${(e as Error).message}. ` +\n `Pass --write-back=<slug> explicitly.`,\n { cause: e },\n );\n }\n}\n"],"mappings":";;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AAKrB,IAAM,wBAAwB,CAAC,eAAe,iBAAiB,kBAAkB,KAAK;AAgB/E,SAAS,cAAc,QAA8B;AAC1D,MAAI,OAAO,UAAU,aAAc,QAAO;AAC1C,QAAM,UAAW,OAAO,WAAW,CAAC;AACpC,QAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,SAAO,sBAAsB;AAAA,IAC3B,CAAC,MAAM,OAAO,QAAQ,CAAC,MAAM,YAAY,CAAC,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,EACnE;AACF;AAWO,SAAS,2BAA2B,QAA+C;AACxF,MAAI,OAAO,UAAU,cAAc;AACjC,UAAM,IAAI,MAAM,6CAA6C,OAAO,KAAK,GAAG;AAAA,EAC9E;AACA,QAAM,UAAW,OAAO,WAAW,CAAC;AACpC,QAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,QAAM,QAAQ,CAAC,MACb,OAAO,MAAM,YAAY,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,IAAI;AACpE,SAAO;AAAA,IACL,aAAa,MAAM,QAAQ,aAAa,CAAC;AAAA,IACzC,eAAe,MAAM,QAAQ,eAAe,CAAC;AAAA,IAC7C,eAAe,MAAM,QAAQ,gBAAgB,CAAC;AAAA,IAC9C,KAAK,MAAM,QAAQ,KAAK,CAAC;AAAA,EAC3B;AACF;AAQA,eAAsB,mBAAmB,KAA8B;AACrE,MAAI;AACF,UAAM,UAAU,KAAK,KAAK,cAAc;AACxC,UAAM,MAAM,MAAM,SAAS,SAAS,OAAO;AAC3C,UAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,QAAI,CAAC,IAAI,KAAM,OAAM,IAAI,MAAM,kCAAkC;AACjE,WAAO,SAAS,IAAI,IAAI;AAAA,EAC1B,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR,mCAAmC,GAAG,kBAAmB,EAAY,OAAO;AAAA,MAE5E,EAAE,OAAO,EAAE;AAAA,IACb;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
skipsAirtableShadow
|
|
3
|
+
} from "./chunk-RQJFGK3A.js";
|
|
4
|
+
|
|
1
5
|
// src/reports/airtable/attachments.ts
|
|
2
6
|
function looksLikeHtml(bytes) {
|
|
3
7
|
const start = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0;
|
|
@@ -23,6 +27,7 @@ async function fetchAttachmentBytes(url) {
|
|
|
23
27
|
return { bytes, contentType };
|
|
24
28
|
}
|
|
25
29
|
async function uploadAttachment(recordId, fieldName, body, filename, contentType, opts = {}) {
|
|
30
|
+
if (skipsAirtableShadow("uploadAttachment", recordId)) return;
|
|
26
31
|
const apiKey = process.env.AIRTABLE_PAT;
|
|
27
32
|
const baseId = process.env.AIRTABLE_BASE_ID;
|
|
28
33
|
if (!apiKey || !baseId) {
|
|
@@ -79,4 +84,4 @@ export {
|
|
|
79
84
|
fetchAttachmentBytes,
|
|
80
85
|
uploadAttachment
|
|
81
86
|
};
|
|
82
|
-
//# sourceMappingURL=chunk-
|
|
87
|
+
//# sourceMappingURL=chunk-YJBSB4LP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reports/airtable/attachments.ts"],"sourcesContent":["import { skipsAirtableShadow } from \"../../fleet/site-id.js\";\n\n/** Cheap HTML sniff: an Airtable signed-URL \"200\" that is really a login/error page\n * starts with `<!doctype html`, `<html`, or `<head` after an optional UTF-8 BOM /\n * leading whitespace. We only need to catch the common error-page case, not parse\n * HTML. */\nfunction looksLikeHtml(bytes: Uint8Array): boolean {\n // Inspect the first ~64 bytes as ASCII (1 byte → 1 char; enough for a doctype /\n // opening tag). Skip a leading UTF-8 BOM (bytes EF BB BF) by index, then strip any\n // leading ASCII whitespace, and match the common HTML openers case-insensitively.\n const start = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;\n const head = Buffer.from(bytes.slice(start, start + 64))\n .toString(\"ascii\")\n .replace(/^[\\s]+/, \"\")\n .toLowerCase();\n return head.startsWith(\"<!doctype html\") || head.startsWith(\"<html\") || head.startsWith(\"<head\");\n}\n\nexport async function fetchAttachmentBytes(\n url: string,\n): Promise<{ bytes: Uint8Array; contentType: string }> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch Airtable attachment ${res.status} ${res.statusText} (url=${url})`,\n );\n }\n const contentType = res.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const ab = await res.arrayBuffer();\n const bytes = new Uint8Array(ab);\n // Sanity-gate the body: a 200 that is actually an HTML error/login page (expired\n // signed URL, auth wall) would otherwise be attached as the \"image\" and ship a\n // broken header. Accept an explicit image/* content-type; otherwise reject anything\n // that sniffs as HTML — so the send fails loudly rather than emailing a broken image.\n const isImageType = contentType.toLowerCase().startsWith(\"image/\");\n if (!isImageType && looksLikeHtml(bytes)) {\n throw new Error(\n `Airtable attachment did not return image data (content-type=\"${contentType}\", ` +\n `body looks like an HTML page — the signed URL may have expired) (url=${url})`,\n );\n }\n return { bytes, contentType };\n}\n\n/**\n * Upload bytes (or a string) as an attachment to a specific record + field.\n * Uses Airtable's content.airtable.com upload endpoint (base64 body) because\n * the standard SDK only accepts public URLs for attachments, and we don't\n * host the generated content anywhere public.\n *\n * Docs: https://airtable.com/developers/web/api/upload-attachment\n *\n * Requires AIRTABLE_PAT + AIRTABLE_BASE_ID in env (same as the rest of the\n * reports module). The fieldName is URL-encoded for the request path.\n */\n/**\n * Upload one file into an Airtable attachment field.\n *\n * ⚠️ Airtable's uploadAttachment endpoint APPENDS — it never replaces. Every\n * re-upload therefore stacks another file in the field while readers\n * (`websites.ts`, `db/header-images.ts`) take attachment [0], the OLDEST. Left\n * alone that means a field silently serves a stale image forever: beachfront\n * accumulated four headers and kept sending the first, which is how a\n * pre-clean-plate header reached a 2026-08-24 announcement and got a second\n * headline printed over its baked one.\n *\n * Pass `{ replaceIn: \"<table>\" }` for a field that should hold exactly one\n * current file (the site header). Omit it where history is wanted (per-period\n * report previews, which are one row per period anyway).\n *\n * The table is part of the option rather than a separate flag because the prune\n * PATCHes `/v0/{baseId}/{table}/{recordId}` — an earlier version omitted the\n * table segment entirely and 403'd on every call, so `replace` cannot be asked\n * for without supplying it.\n */\nexport async function uploadAttachment(\n recordId: string,\n fieldName: string,\n body: Uint8Array | string,\n filename: string,\n contentType: string,\n opts: { replaceIn?: string } = {},\n): Promise<void> {\n // #646 step 3: a `site_<ULID>` site was created in Turso and has no Airtable\n // record to attach to. Checked BEFORE the credential gate — skipping needs no\n // creds, and a run without them must not fail on a write it was never going to make.\n if (skipsAirtableShadow(\"uploadAttachment\", recordId)) return;\n const apiKey = process.env.AIRTABLE_PAT;\n const baseId = process.env.AIRTABLE_BASE_ID;\n if (!apiKey || !baseId) {\n throw new Error(\"AIRTABLE_PAT and AIRTABLE_BASE_ID must be set\");\n }\n const base64 =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf-8\").toString(\"base64\")\n : Buffer.from(body).toString(\"base64\");\n const payload = { contentType, file: base64, filename };\n const url = `https://content.airtable.com/v0/${baseId}/${recordId}/${encodeURIComponent(fieldName)}/uploadAttachment`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n throw new Error(`Airtable upload failed: ${res.status} ${res.statusText} ${await res.text()}`);\n }\n if (!opts.replaceIn) return;\n\n // The upload response carries the record with the field's full, post-append\n // attachment list. Keep only the last entry (the one just uploaded) by\n // PATCHing the field to that id — Airtable preserves an attachment referenced\n // by id alone and drops any omitted. Best-effort: the file IS uploaded by this\n // point, so a prune failure must not fail the caller; it only leaves the\n // field over-full, which the next successful run tidies.\n try {\n const body = (await res.json()) as {\n fields?: Record<string, Array<{ id?: string }> | undefined>;\n };\n // Airtable keys this response's `fields` by FIELD ID, not field name — verified\n // live 2026-08-24: {\"id\":\"rec…\",\"createdTime\":…,\"fields\":{\"fldBUAW180p8MIpvh\":[…]}}.\n // Looking it up by NAME therefore yielded undefined, `list` was [], and the\n // `length <= 1` guard returned silently — so `replace` never pruned anything on a\n // real call, while a name-keyed test stub kept it green. Eleven site header fields\n // stacked before the discrepancy surfaced. The endpoint writes exactly ONE field,\n // so its response carries exactly one entry: take it whatever it is keyed by, and\n // fall back to the name in case Airtable ever switches.\n const lists = Object.values(body.fields ?? {}).filter((v) => Array.isArray(v));\n const list = body.fields?.[fieldName] ?? (lists.length === 1 ? lists[0] : undefined);\n if (!list) {\n // Never return quietly here: silence is what let the no-op ship.\n console.warn(\n `⚠ attachment prune skipped for \"${fieldName}\": upload response carried no ` +\n `attachment list (fields keys: ${Object.keys(body.fields ?? {}).join(\", \") || \"none\"})`,\n );\n return;\n }\n if (list.length <= 1) return;\n const newest = list[list.length - 1]?.id;\n if (!newest) {\n console.warn(`⚠ kept ${list.length} attachments in \"${fieldName}\": newest entry has no id`);\n return;\n }\n // /v0/{baseId}/{table}/{recordId} — the table segment is REQUIRED. Without it\n // Airtable answers 403 Forbidden (not 404), which reads like a token-scope\n // problem and sent this in the wrong direction once already.\n const patchUrl = `https://api.airtable.com/v0/${baseId}/${encodeURIComponent(opts.replaceIn)}/${recordId}`;\n const patch = await fetch(patchUrl, {\n method: \"PATCH\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ fields: { [fieldName]: [{ id: newest }] } }),\n });\n if (!patch.ok) {\n console.warn(\n `⚠ kept ${list.length} attachments in \"${fieldName}\" — prune failed: ${patch.status} ${patch.statusText}`,\n );\n }\n } catch (e) {\n console.warn(`⚠ attachment prune skipped for \"${fieldName}\": ${(e as Error).message}`);\n }\n}\n"],"mappings":"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","names":["body"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
openDb,
|
|
3
3
|
readDbConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MMSMZC7S.js";
|
|
5
5
|
|
|
6
6
|
// src/db/fleet-events.ts
|
|
7
7
|
async function recordFleetEvent(db, e) {
|
|
@@ -47,4 +47,4 @@ async function recordFleetEventsBestEffort(events, now, open = () => openDb(read
|
|
|
47
47
|
export {
|
|
48
48
|
recordFleetEventsBestEffort
|
|
49
49
|
};
|
|
50
|
-
//# sourceMappingURL=chunk-
|
|
50
|
+
//# sourceMappingURL=chunk-YXWBM26P.js.map
|
package/dist/cli/bin.js
CHANGED
|
@@ -8,6 +8,36 @@ import { dirname as dirname2 } from "path";
|
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { cac } from "cac";
|
|
10
10
|
|
|
11
|
+
// src/cli/retired-flags.ts
|
|
12
|
+
var RETIRED_FLAGS = {
|
|
13
|
+
"--write-airtable": "--write-back"
|
|
14
|
+
};
|
|
15
|
+
function rewriteRetiredFlags(argv, note = (message) => console.error(message)) {
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
const out = argv.slice(0, 2);
|
|
18
|
+
let terminated = false;
|
|
19
|
+
for (const token of argv.slice(2)) {
|
|
20
|
+
if (terminated || token === "--") {
|
|
21
|
+
terminated = true;
|
|
22
|
+
out.push(token);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const eq = token.indexOf("=");
|
|
26
|
+
const name = eq === -1 ? token : token.slice(0, eq);
|
|
27
|
+
const next = Object.hasOwn(RETIRED_FLAGS, name) ? RETIRED_FLAGS[name] : void 0;
|
|
28
|
+
if (next === void 0) {
|
|
29
|
+
out.push(token);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!seen.has(name)) {
|
|
33
|
+
seen.add(name);
|
|
34
|
+
note(`note: ${name} is now ${next}; the old spelling still works.`);
|
|
35
|
+
}
|
|
36
|
+
out.push(eq === -1 ? next : `${next}${token.slice(eq)}`);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
// src/cli/version.ts
|
|
12
42
|
import { readFileSync, existsSync } from "fs";
|
|
13
43
|
import { dirname, join } from "path";
|
|
@@ -91,9 +121,9 @@ cli.command("list-recipes", "Print the available recipes.").action(() => {
|
|
|
91
121
|
});
|
|
92
122
|
cli.command("audit [site]", "Run audits against a site (default: cwd).").option("--only <names>", "Comma-separated audit names (e.g. deps,lighthouse)").option("--json", "Machine-readable JSON output").option(
|
|
93
123
|
"--fleet <inventory>",
|
|
94
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
124
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
95
125
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").option(
|
|
96
|
-
"--write-
|
|
126
|
+
"--write-back [slug]",
|
|
97
127
|
"After lighthouse runs, write pScore/rScore/bpScore/seoScore + timestamp to the matching Websites row. Slug defaults to cwd's package.json#name."
|
|
98
128
|
).option("--fail-on-violations", "Exit non-zero if any a11y violations are found (for CI gates)").option(
|
|
99
129
|
"--url <url>",
|
|
@@ -109,37 +139,43 @@ cli.command("audit [site]", "Run audits against a site (default: cwd).").option(
|
|
|
109
139
|
);
|
|
110
140
|
cli.command("sync-configs [site]", "Sync canonical configs into a site.").option("--only <names>", "Comma-separated config names (e.g. eslint,prettier)").option("--dry", "Print diff without writing").option(
|
|
111
141
|
"--fleet <inventory>",
|
|
112
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
142
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
113
143
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
114
144
|
async (site, opts) => runOrExit(
|
|
115
|
-
async () => (await import("../sync-configs-
|
|
145
|
+
async () => (await import("../sync-configs-OXSWKVZ2.js")).runSyncConfigsCommand(site, opts),
|
|
116
146
|
opts
|
|
117
147
|
)
|
|
118
148
|
);
|
|
119
149
|
cli.command("bump-deps [site]", "Bump dependencies.").option("--group <group>", "patch | minor | major", { default: "minor" }).option(
|
|
120
150
|
"--fleet <inventory>",
|
|
121
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
151
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
122
152
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
123
153
|
async (site, opts) => runOrExit(
|
|
124
|
-
async () => (await import("../bump-deps-
|
|
154
|
+
async () => (await import("../bump-deps-775VLDYB.js")).runBumpDepsCommand(site, opts),
|
|
125
155
|
opts
|
|
126
156
|
)
|
|
127
157
|
);
|
|
128
158
|
cli.command(
|
|
129
159
|
"self-updating [site]",
|
|
130
160
|
"Bootstrap a repo to keep itself updated (Renovate + protection; platform auto-merge off; ci.yml is the starter's, not managed here)."
|
|
131
|
-
).option("--dry", "List what would be enabled without writing or opening PRs").option(
|
|
161
|
+
).option("--dry", "List what would be enabled without writing or opening PRs").option(
|
|
162
|
+
"--fleet <inventory>",
|
|
163
|
+
'Inventory file (.json or .mjs/.js), or "turso" ("airtable" is a deprecated alias)'
|
|
164
|
+
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
132
165
|
async (site, opts) => runOrExit(
|
|
133
|
-
async () => (await import("../self-updating-
|
|
166
|
+
async () => (await import("../self-updating-ZUWPGYUP.js")).runSelfUpdatingCommand(site, opts),
|
|
134
167
|
opts
|
|
135
168
|
)
|
|
136
169
|
);
|
|
137
170
|
cli.command(
|
|
138
171
|
"prismic-ci [site]",
|
|
139
172
|
"Add the Prismic model delivery workflow to a site via PR (one PR per repo; never pushes to main)."
|
|
140
|
-
).option("--dry", "List the sites that would be offered the workflow, without opening any PR").option(
|
|
173
|
+
).option("--dry", "List the sites that would be offered the workflow, without opening any PR").option(
|
|
174
|
+
"--fleet <inventory>",
|
|
175
|
+
'Inventory file (.json or .mjs/.js), or "turso" ("airtable" is a deprecated alias)'
|
|
176
|
+
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
141
177
|
async (site, opts) => runOrExit(
|
|
142
|
-
async () => (await import("../prismic-ci-
|
|
178
|
+
async () => (await import("../prismic-ci-NAPWOXXD.js")).runPrismicCiCommand(site, opts),
|
|
143
179
|
opts
|
|
144
180
|
)
|
|
145
181
|
);
|
|
@@ -149,21 +185,24 @@ cli.command(
|
|
|
149
185
|
).option(
|
|
150
186
|
"--apply",
|
|
151
187
|
"Push local models to Prismic. Never deletes \u2014 remote-only models are reported."
|
|
152
|
-
).option("--pull", "Write remote-only models into the repo (single-site; review + PR the result).").option("--tokens", "Print the per-site write-token doctor: which env var, present?, reads?").option(
|
|
188
|
+
).option("--pull", "Write remote-only models into the repo (single-site; review + PR the result).").option("--tokens", "Print the per-site write-token doctor: which env var, present?, reads?").option(
|
|
189
|
+
"--fleet <inventory>",
|
|
190
|
+
'Inventory file (.json or .mjs/.js), or "turso" ("airtable" is a deprecated alias). Read-only.'
|
|
191
|
+
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").option("--write-back", "Fleet mode: persist each site's verdict to its Websites row").option(
|
|
153
192
|
"--comment-file <path>",
|
|
154
193
|
"Write the report to this file (the CI workflow posts it as a PR comment)"
|
|
155
194
|
).action(
|
|
156
195
|
async (site, opts) => runOrExit(
|
|
157
|
-
async () => (await import("../prismic-models-
|
|
196
|
+
async () => (await import("../prismic-models-LL6ORTQ2.js")).runPrismicModelsCommand(site, opts),
|
|
158
197
|
opts
|
|
159
198
|
)
|
|
160
199
|
);
|
|
161
200
|
cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to-5).").example("reddoor-maint upgrade svelte-4-to-5 ./my-site").option(
|
|
162
201
|
"--fleet <inventory>",
|
|
163
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
202
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
164
203
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
165
204
|
async (upgrade, site, opts) => runOrExit(
|
|
166
|
-
async () => (await import("../upgrade-
|
|
205
|
+
async () => (await import("../upgrade-EVAL3JTO.js")).runUpgradeCommand(upgrade, site, opts),
|
|
167
206
|
opts
|
|
168
207
|
)
|
|
169
208
|
);
|
|
@@ -172,19 +211,19 @@ cli.command(
|
|
|
172
211
|
"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts)."
|
|
173
212
|
).option(
|
|
174
213
|
"--fleet <inventory>",
|
|
175
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
214
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
176
215
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
177
216
|
async (site, opts) => runOrExit(
|
|
178
|
-
async () => (await import("../convert-to-pnpm-
|
|
217
|
+
async () => (await import("../convert-to-pnpm-KLWA53N5.js")).runConvertToPnpmCommand(site, opts),
|
|
179
218
|
opts
|
|
180
219
|
)
|
|
181
220
|
);
|
|
182
221
|
cli.command("svelte-codemods [site]", "Apply Svelte 5 gotcha codemods to an already-migrated site.").option(
|
|
183
222
|
"--fleet <inventory>",
|
|
184
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
223
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
185
224
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
186
225
|
async (site, opts) => runOrExit(
|
|
187
|
-
async () => (await import("../svelte-codemods-
|
|
226
|
+
async () => (await import("../svelte-codemods-VOLOKKFI.js")).runSvelteCodemodsCommand(site, opts),
|
|
188
227
|
opts
|
|
189
228
|
)
|
|
190
229
|
);
|
|
@@ -193,10 +232,10 @@ cli.command(
|
|
|
193
232
|
"Write src/routes/health/+server.ts (function-health probe for the report gate)."
|
|
194
233
|
).option(
|
|
195
234
|
"--fleet <inventory>",
|
|
196
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
235
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
197
236
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
198
237
|
async (site, opts) => runOrExit(
|
|
199
|
-
async () => (await import("../health-endpoint-
|
|
238
|
+
async () => (await import("../health-endpoint-BJ6BKRHF.js")).runHealthEndpointCommand(site, opts),
|
|
200
239
|
opts
|
|
201
240
|
)
|
|
202
241
|
);
|
|
@@ -205,10 +244,10 @@ cli.command(
|
|
|
205
244
|
"Install the matching-a-page gate harness (dev-guarded /dev/match/[uid] route + matching/ scripts) for a live-reference rebuild."
|
|
206
245
|
).option("--ref <url>", "Reference (live) site URL the harness gates against \u2014 required").option("--cand <url>", "Candidate origin (default http://localhost:5173)").option("--matrix <list>", "Breakpoint matrix, comma-separated (default 1440,834,390)").option(
|
|
207
246
|
"--fleet <inventory>",
|
|
208
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
247
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
209
248
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
210
249
|
async (site, opts) => runOrExit(
|
|
211
|
-
async () => (await import("../match-harness-
|
|
250
|
+
async () => (await import("../match-harness-GBCDSVPS.js")).runMatchHarnessCommand(site, opts),
|
|
212
251
|
opts
|
|
213
252
|
)
|
|
214
253
|
);
|
|
@@ -217,10 +256,10 @@ cli.command(
|
|
|
217
256
|
"Add the smoke suite (test:smoke + playwright config + /health smoke routes)."
|
|
218
257
|
).option(
|
|
219
258
|
"--fleet <inventory>",
|
|
220
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
259
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
221
260
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
222
261
|
async (site, opts) => runOrExit(
|
|
223
|
-
async () => (await import("../smoke-suite-
|
|
262
|
+
async () => (await import("../smoke-suite-WAUPI6EJ.js")).runSmokeSuiteCommand(site, opts),
|
|
224
263
|
opts
|
|
225
264
|
)
|
|
226
265
|
);
|
|
@@ -229,10 +268,10 @@ cli.command(
|
|
|
229
268
|
"Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm)."
|
|
230
269
|
).option("--audits <names>", "Comma-separated audit subset: lighthouse,a11y (default: both)").option(
|
|
231
270
|
"--fleet <inventory>",
|
|
232
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
271
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
233
272
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
234
273
|
async (site, opts) => runOrExit(
|
|
235
|
-
async () => (await import("../onboard-
|
|
274
|
+
async () => (await import("../onboard-JQQIMQW7.js")).runOnboardCommand(site, opts),
|
|
236
275
|
opts
|
|
237
276
|
)
|
|
238
277
|
);
|
|
@@ -241,25 +280,25 @@ cli.command(
|
|
|
241
280
|
"One-shot guided onboarding: convert-to-pnpm \u2192 onboard \u2192 sync-configs \u2192 svelte-codemods \u2192 a11y-fixtures-page \u2192 health-endpoint \u2192 smoke-suite \u2192 audit."
|
|
242
281
|
).option(
|
|
243
282
|
"--fleet <inventory>",
|
|
244
|
-
'Inventory file (.json or .mjs/.js), or "
|
|
283
|
+
'Inventory file (.json or .mjs/.js), or "turso" to read the fleet roster from the database ("airtable" is a deprecated alias)'
|
|
245
284
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
246
|
-
async (site, opts) => runOrExit(async () => (await import("../init-
|
|
285
|
+
async (site, opts) => runOrExit(async () => (await import("../init-GOWDA5QS.js")).runInitCommand(site, opts), opts)
|
|
247
286
|
);
|
|
248
287
|
cli.command(
|
|
249
288
|
"launch <site>",
|
|
250
289
|
"Bootstrap + first-audit a site, then draft its launch email for approval."
|
|
251
290
|
).action(
|
|
252
291
|
async (site, opts) => runOrExit(
|
|
253
|
-
async () => (await import("../launch-
|
|
292
|
+
async () => (await import("../launch-7N3X2MXB.js")).runLaunchCommand(site, opts),
|
|
254
293
|
opts
|
|
255
294
|
)
|
|
256
295
|
);
|
|
257
|
-
cli.command("header-image [site]", "Generate a site's report header image from its live homepage.").option("--all", "Every live site with no Header image yet (backfill)").option("--force", "With --all, regenerate sites that already have one").option("--write-
|
|
296
|
+
cli.command("header-image [site]", "Generate a site's report header image from its live homepage.").option("--all", "Every live site with no Header image yet (backfill)").option("--force", "With --all, regenerate sites that already have one").option("--write-back", "Upload to the Websites row instead of writing a local file").option("--out-dir <path>", "Directory for local output (default: reports/)").option("--settle-ms <ms>", "Override the post-load settle delay for slow/animated homepages").option(
|
|
258
297
|
"--consent-selector <css>",
|
|
259
298
|
"Extra CSS selector(s) to hide before the shutter (consent UI the heuristic misses)"
|
|
260
299
|
).action(
|
|
261
300
|
async (site, opts) => runOrExit(
|
|
262
|
-
async () => (await import("../header-image-
|
|
301
|
+
async () => (await import("../header-image-H7ZJAV26.js")).runHeaderImageCommand(site, opts),
|
|
263
302
|
opts
|
|
264
303
|
)
|
|
265
304
|
);
|
|
@@ -268,7 +307,7 @@ cli.command(
|
|
|
268
307
|
"Draft the monthly-report announcement email for maintenance sites (all, or one) for approval."
|
|
269
308
|
).action(
|
|
270
309
|
async (site, opts) => runOrExit(
|
|
271
|
-
async () => (await import("../announce-
|
|
310
|
+
async () => (await import("../announce-5ZD2SBGD.js")).runAnnounceCommand(site, opts),
|
|
272
311
|
opts
|
|
273
312
|
)
|
|
274
313
|
);
|
|
@@ -277,7 +316,7 @@ cli.command(
|
|
|
277
316
|
"Show who a form submission would email, and optionally flip the pre-launch guard (read-back confirmed)."
|
|
278
317
|
).option("--set <on|off>", "Flip the guard: on = route to operator, off = restore.").option("--restore <status>", "Status to restore with --set off. Required; never inferred.").action(
|
|
279
318
|
async (site, opts) => runOrExit(
|
|
280
|
-
async () => (await import("../forms-notify-target-
|
|
319
|
+
async () => (await import("../forms-notify-target-X6UEOI2M.js")).runFormsNotifyTargetCommand(
|
|
281
320
|
site,
|
|
282
321
|
opts
|
|
283
322
|
),
|
|
@@ -289,7 +328,7 @@ cli.command(
|
|
|
289
328
|
"Operator self-tests. kind=email: preview a report email for a site (or --all) to yourself."
|
|
290
329
|
).option("--type <type>", "Report type: announcement (default) | maintenance | testing | launch").option("--to <addr>", "Recipient(s), comma-separated. Default: OPERATOR_EMAIL.").option("--all", "Send a preview for every maintenance site (to --to/operator).").option("--dry-run", "Render only; write reports/<slug>/selftest-<type>.html; do not send.").action(
|
|
291
330
|
async (kind, site, opts) => runOrExit(
|
|
292
|
-
async () => (await import("../selftest-
|
|
331
|
+
async () => (await import("../selftest-G4W3LIBQ.js")).runSelftestCommand(kind, site, opts),
|
|
293
332
|
opts
|
|
294
333
|
)
|
|
295
334
|
);
|
|
@@ -301,13 +340,16 @@ cli.command(
|
|
|
301
340
|
"Fleet mode: announcement\u2192maintenance sites; maintenance/testing\u2192all schedulable sites."
|
|
302
341
|
).action(
|
|
303
342
|
async (site, opts) => runOrExit(
|
|
304
|
-
async () => (await import("../preflight-
|
|
343
|
+
async () => (await import("../preflight-O7Q6FWHU.js")).runPreflightCommand(site, opts),
|
|
305
344
|
opts
|
|
306
345
|
)
|
|
307
346
|
);
|
|
308
|
-
cli.command(
|
|
347
|
+
cli.command(
|
|
348
|
+
"ensure-site <slug>",
|
|
349
|
+
"Create/verify a site's fleet row in Turso (new sites get a site_<ULID> id)."
|
|
350
|
+
).option("--name <display name>", "Human Name for the row (client-facing copy uses it verbatim).").option("--url <url>", "Deployed URL (e.g. the Netlify site URL).").option("--contact <email>", "point of contact \u2014 the client address reports resolve to.").option("--git-repo <owner/repo>", "GitHub identity. Default on create: reddoorla/<slug>.").action(
|
|
309
351
|
async (slug, opts) => runOrExit(
|
|
310
|
-
async () => (await import("../ensure-site-
|
|
352
|
+
async () => (await import("../ensure-site-2QZHFVQ3.js")).runEnsureSiteCommand(slug, opts),
|
|
311
353
|
opts
|
|
312
354
|
)
|
|
313
355
|
);
|
|
@@ -334,7 +376,7 @@ cli.command(
|
|
|
334
376
|
"Comma-separated buyer questions, chosen by hand. Blank asks the goal's fixed set. A chosen set gets its own version key, so comparisons never mix sets."
|
|
335
377
|
).action(
|
|
336
378
|
async (url, opts) => runOrExit(
|
|
337
|
-
async () => (await import("../prospect-audit-
|
|
379
|
+
async () => (await import("../prospect-audit-FMPGQIRQ.js")).runProspectAuditCommand(url, opts),
|
|
338
380
|
opts
|
|
339
381
|
)
|
|
340
382
|
);
|
|
@@ -355,18 +397,18 @@ cli.command("report [site]", "Draft or send maintenance/testing reports.").optio
|
|
|
355
397
|
"Regenerate one UNSENT report's stored HTML from its current row, so the console preview shows edited commentary. Refuses a sent report."
|
|
356
398
|
).action(
|
|
357
399
|
async (site, opts) => runOrExit(
|
|
358
|
-
async () => (await import("../report-
|
|
400
|
+
async () => (await import("../report-IAHXN5CW.js")).runReportCommand(site, opts),
|
|
359
401
|
opts
|
|
360
402
|
)
|
|
361
403
|
);
|
|
362
404
|
cli.command(
|
|
363
405
|
"github-signals",
|
|
364
|
-
"Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write
|
|
365
|
-
).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-
|
|
406
|
+
"Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write each site's row."
|
|
407
|
+
).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-back", "Write each site's signals back to its Websites row.").action(
|
|
366
408
|
async (opts) => runOrExit(
|
|
367
|
-
async () => (await import("../github-signals-
|
|
409
|
+
async () => (await import("../github-signals-QIRTHHET.js")).runGitHubSignalsCommand({
|
|
368
410
|
fleet: opts.fleet,
|
|
369
|
-
|
|
411
|
+
writeBack: opts.writeBack
|
|
370
412
|
}),
|
|
371
413
|
opts
|
|
372
414
|
)
|
|
@@ -376,7 +418,7 @@ cli.command(
|
|
|
376
418
|
"Verify every public org repo carries a sound branch ruleset (empty bypass, PR-required)."
|
|
377
419
|
).option("--org <org>", "GitHub org to sweep (enumerated from the API, never a hand list).").action(
|
|
378
420
|
async (opts) => runOrExit(
|
|
379
|
-
async () => (await import("../protection-audit-
|
|
421
|
+
async () => (await import("../protection-audit-YXJKFU6O.js")).runProtectionAuditCommand({
|
|
380
422
|
org: opts.org
|
|
381
423
|
}),
|
|
382
424
|
opts
|
|
@@ -404,7 +446,7 @@ cli.command(
|
|
|
404
446
|
"--by <who>",
|
|
405
447
|
"replay-deadletters --abandon: who decided (default: OPERATOR_EMAIL, else 'operator')."
|
|
406
448
|
).action(
|
|
407
|
-
async (action, opts) => runOrExit(async () => (await import("../db-
|
|
449
|
+
async (action, opts) => runOrExit(async () => (await import("../db-II5WAK4D.js")).runDbCommand(action, opts), opts)
|
|
408
450
|
);
|
|
409
451
|
cli.command(
|
|
410
452
|
"submissions <action>",
|
|
@@ -414,7 +456,7 @@ cli.command(
|
|
|
414
456
|
"Write the re-buckets: status \u2192 spam_auto, spam_score/spam_reason replaced with the new verdict + 'retro-rescore' marker."
|
|
415
457
|
).action(
|
|
416
458
|
async (action, opts) => runOrExit(
|
|
417
|
-
async () => (await import("../submissions-
|
|
459
|
+
async () => (await import("../submissions-2SHCFOKD.js")).runSubmissionsCommand(action, opts),
|
|
418
460
|
opts
|
|
419
461
|
)
|
|
420
462
|
);
|
|
@@ -423,10 +465,10 @@ cli.command(
|
|
|
423
465
|
"Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns."
|
|
424
466
|
).option("--fleet", "Run across every active, repo-backed site in the Airtable inventory.").action(
|
|
425
467
|
async (opts) => runOrExit(
|
|
426
|
-
async () => (await import("../renovate-dispatch-
|
|
468
|
+
async () => (await import("../renovate-dispatch-2IHAZDVA.js")).runRenovateDispatchCommand({
|
|
427
469
|
fleet: opts.fleet,
|
|
428
470
|
// #539 Phase 5: mirror the auto-fix counter into site_health.
|
|
429
|
-
siteMirror: await (await import("../site-mirror-
|
|
471
|
+
siteMirror: await (await import("../site-mirror-WR7ACXOV.js")).makeSiteMirror()
|
|
430
472
|
}),
|
|
431
473
|
opts
|
|
432
474
|
)
|
|
@@ -451,7 +493,7 @@ cli.command(
|
|
|
451
493
|
"freeze: JSON declaring slots the template has no token for (video poster, a panel rebuilt from a baked image). Keys must start with 'x.'"
|
|
452
494
|
).action(
|
|
453
495
|
async (action, dir, opts) => runOrExit(
|
|
454
|
-
async () => (await import("../blux-
|
|
496
|
+
async () => (await import("../blux-NXQLMS4W.js")).runBluxCommand(action, dir, opts),
|
|
455
497
|
opts
|
|
456
498
|
)
|
|
457
499
|
);
|
|
@@ -476,7 +518,7 @@ cli.on("command:*", () => {
|
|
|
476
518
|
);
|
|
477
519
|
process.exit(1);
|
|
478
520
|
});
|
|
479
|
-
cli.parse();
|
|
521
|
+
cli.parse(rewriteRetiredFlags(process.argv));
|
|
480
522
|
export {
|
|
481
523
|
runOrExit
|
|
482
524
|
};
|