@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.
Files changed (237) hide show
  1. package/README.md +5 -6
  2. package/dist/{announce-45HVP4J4.js → announce-5ZD2SBGD.js} +39 -31
  3. package/dist/announce-5ZD2SBGD.js.map +1 -0
  4. package/dist/attachments-JFGFQRXQ.js +10 -0
  5. package/dist/{blux-RUFMNOUJ.js → blux-NXQLMS4W.js} +5 -5
  6. package/dist/{bump-deps-EL3TKMIL.js → bump-deps-775VLDYB.js} +6 -6
  7. package/dist/{chunk-IMYL67JV.js → chunk-3RLDX3Q2.js} +5 -5
  8. package/dist/{chunk-CA6ADGOC.js → chunk-6PGXKN6F.js} +2 -2
  9. package/dist/{chunk-Y5BVR4HW.js → chunk-6THEZQLA.js} +3 -3
  10. package/dist/chunk-ASLDCWBF.js +41 -0
  11. package/dist/chunk-ASLDCWBF.js.map +1 -0
  12. package/dist/{chunk-DVJNYPFR.js → chunk-AYDQQBNG.js} +2 -2
  13. package/dist/{chunk-GEUC5VGM.js → chunk-BINFZQ2T.js} +49 -4
  14. package/dist/chunk-BINFZQ2T.js.map +1 -0
  15. package/dist/{chunk-72FJZ6LO.js → chunk-DRNZVORX.js} +26 -22
  16. package/dist/chunk-DRNZVORX.js.map +1 -0
  17. package/dist/{chunk-SHGIDTYF.js → chunk-DYTYPKFN.js} +2 -2
  18. package/dist/{chunk-NVLREB5R.js → chunk-EN6CTBUN.js} +7 -7
  19. package/dist/{chunk-ZH5LOQET.js → chunk-FHFXJ3EM.js} +7 -5
  20. package/dist/chunk-FHFXJ3EM.js.map +1 -0
  21. package/dist/{chunk-H445EH33.js → chunk-FP6HOW6J.js} +10 -7
  22. package/dist/chunk-FP6HOW6J.js.map +1 -0
  23. package/dist/{chunk-RRE74VWI.js → chunk-HSMN35LU.js} +2 -2
  24. package/dist/chunk-ICSRWW5Q.js +148 -0
  25. package/dist/chunk-ICSRWW5Q.js.map +1 -0
  26. package/dist/{chunk-XG2A6GIG.js → chunk-IDBPII6G.js} +4 -4
  27. package/dist/{chunk-XG2A6GIG.js.map → chunk-IDBPII6G.js.map} +1 -1
  28. package/dist/{chunk-T76MA2FH.js → chunk-IOTOOTII.js} +12 -1
  29. package/dist/{chunk-T76MA2FH.js.map → chunk-IOTOOTII.js.map} +1 -1
  30. package/dist/{chunk-ECDJDHKT.js → chunk-JHJNNJLG.js} +2 -2
  31. package/dist/{chunk-XY7YV2YQ.js → chunk-L3S3QZXL.js} +2 -2
  32. package/dist/{chunk-XED2DNAI.js → chunk-LAG6IP7Y.js} +7 -7
  33. package/dist/{chunk-P4A6D6HO.js → chunk-LDHRIQUW.js} +32 -18
  34. package/dist/chunk-LDHRIQUW.js.map +1 -0
  35. package/dist/{chunk-UIITN37J.js → chunk-LF2TJN4X.js} +2 -2
  36. package/dist/chunk-LF2TJN4X.js.map +1 -0
  37. package/dist/{chunk-SQ4FSNYL.js → chunk-LUF52EIY.js} +2 -2
  38. package/dist/{chunk-MZZDMBDT.js → chunk-LZMAG73U.js} +19 -19
  39. package/dist/chunk-LZMAG73U.js.map +1 -0
  40. package/dist/{chunk-6INOBNRO.js → chunk-MMSMZC7S.js} +2 -2
  41. package/dist/{chunk-CGQZFQAY.js → chunk-MVKNU4QD.js} +2 -3
  42. package/dist/chunk-MVKNU4QD.js.map +1 -0
  43. package/dist/{chunk-62YWOFVN.js → chunk-PARKXDXI.js} +3 -3
  44. package/dist/{chunk-62YWOFVN.js.map → chunk-PARKXDXI.js.map} +1 -1
  45. package/dist/{chunk-44SDGPGQ.js → chunk-PZZ6HM5T.js} +3 -3
  46. package/dist/chunk-RQJFGK3A.js +51 -0
  47. package/dist/chunk-RQJFGK3A.js.map +1 -0
  48. package/dist/{chunk-KN52PM63.js → chunk-TC4GANMX.js} +47 -149
  49. package/dist/chunk-TC4GANMX.js.map +1 -0
  50. package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
  51. package/dist/chunk-TEMQPSCM.js.map +1 -0
  52. package/dist/{chunk-4UTJ4G6P.js → chunk-TJLKWQOX.js} +2 -2
  53. package/dist/{chunk-VEIIUCYL.js → chunk-TV76A6GN.js} +2 -2
  54. package/dist/{chunk-D3CTOCUR.js → chunk-TVZ6QCOE.js} +3 -3
  55. package/dist/{chunk-3EA43S3R.js → chunk-U2ZI6G7H.js} +12 -12
  56. package/dist/chunk-UPE6D4AT.js +69 -0
  57. package/dist/chunk-UPE6D4AT.js.map +1 -0
  58. package/dist/{chunk-OTEVKRAH.js → chunk-VMGHQUTV.js} +7 -16
  59. package/dist/chunk-VMGHQUTV.js.map +1 -0
  60. package/dist/chunk-VQMAAQ5M.js +53 -0
  61. package/dist/chunk-VQMAAQ5M.js.map +1 -0
  62. package/dist/{chunk-EASYCD6F.js → chunk-WMF5S2MB.js} +1 -1
  63. package/dist/{chunk-EASYCD6F.js.map → chunk-WMF5S2MB.js.map} +1 -1
  64. package/dist/{chunk-37B4LVB3.js → chunk-X56LAKPR.js} +3 -3
  65. package/dist/chunk-X56LAKPR.js.map +1 -0
  66. package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
  67. package/dist/chunk-YJBSB4LP.js.map +1 -0
  68. package/dist/{chunk-EQQ4H6L3.js → chunk-YXWBM26P.js} +2 -2
  69. package/dist/cli/bin.js +92 -50
  70. package/dist/cli/bin.js.map +1 -1
  71. package/dist/cli/commands/audit.d.ts +33 -3
  72. package/dist/cli/commands/audit.js +25 -22
  73. package/dist/cli/commands/audit.js.map +1 -1
  74. package/dist/client-DGTO65WB.js +11 -0
  75. package/dist/{convert-to-pnpm-4DON7IFS.js → convert-to-pnpm-KLWA53N5.js} +6 -6
  76. package/dist/{db-5K4CDEGJ.js → db-II5WAK4D.js} +13 -13
  77. package/dist/{digest-5AJGWS33.js → digest-CWKK2DMR.js} +28 -27
  78. package/dist/digest-CWKK2DMR.js.map +1 -0
  79. package/dist/{digest-collectors-MC5NWEUA.js → digest-collectors-CYTN2EUX.js} +15 -12
  80. package/dist/ensure-site-2QZHFVQ3.js +70 -0
  81. package/dist/ensure-site-2QZHFVQ3.js.map +1 -0
  82. package/dist/ensure-site-JEKV2ODE.js +105 -0
  83. package/dist/ensure-site-JEKV2ODE.js.map +1 -0
  84. package/dist/{ensure-site-XESZHZEB.js → ensure-site-OO4UPWNQ.js} +19 -42
  85. package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
  86. package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
  87. package/dist/{fleet-state-GJWGKTLD.js → fleet-state-RJROYXKO.js} +17 -7
  88. package/dist/{forms-notify-target-4NPLX2UG.js → forms-notify-target-X6UEOI2M.js} +17 -13
  89. package/dist/forms-notify-target-X6UEOI2M.js.map +1 -0
  90. package/dist/{github-signals-DMU74SAX.js → github-signals-QIRTHHET.js} +20 -15
  91. package/dist/github-signals-QIRTHHET.js.map +1 -0
  92. package/dist/{header-image-BRTOLHJF.js → header-image-H7ZJAV26.js} +13 -17
  93. package/dist/header-image-H7ZJAV26.js.map +1 -0
  94. package/dist/{health-endpoint-UCLMSSCN.js → health-endpoint-BJ6BKRHF.js} +7 -7
  95. package/dist/{health-mirror-ONRT4DQ5.js → health-mirror-KU3G7V2Y.js} +10 -10
  96. package/dist/{import-airtable-IIK2QUUF.js → import-airtable-UBF5GYAL.js} +3 -3
  97. package/dist/index.d.ts +102 -494
  98. package/dist/index.js +60 -49
  99. package/dist/index.js.map +1 -1
  100. package/dist/{init-3LMZTCVD.js → init-GOWDA5QS.js} +15 -15
  101. package/dist/{launch-XTDMC6LD.js → launch-7N3X2MXB.js} +36 -34
  102. package/dist/launch-7N3X2MXB.js.map +1 -0
  103. package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
  104. package/dist/{chunk-IHHAYXKY.js → match-harness-GBCDSVPS.js} +70 -4
  105. package/dist/match-harness-GBCDSVPS.js.map +1 -0
  106. package/dist/migrate-XZ7U5DFC.js +7 -0
  107. package/dist/notify-K3WVH3DM.js +1 -1
  108. package/dist/{onboard-CZYBWVWH.js → onboard-JQQIMQW7.js} +6 -6
  109. package/dist/{orchestrate-TGDFGY52.js → orchestrate-L2A5BZFY.js} +13 -10
  110. package/dist/parity-2JGVUCZD.js +14 -0
  111. package/dist/{preflight-POZZI3GN.js → preflight-O7Q6FWHU.js} +35 -11
  112. package/dist/preflight-O7Q6FWHU.js.map +1 -0
  113. package/dist/{prismic-ci-AHY5ULJR.js → prismic-ci-NAPWOXXD.js} +11 -11
  114. package/dist/prismic-ci-NAPWOXXD.js.map +1 -0
  115. package/dist/{prismic-models-URYGODNY.js → prismic-models-LL6ORTQ2.js} +19 -16
  116. package/dist/prismic-models-LL6ORTQ2.js.map +1 -0
  117. package/dist/{prospect-audit-AJEE3Z4L.js → prospect-audit-FMPGQIRQ.js} +2 -2
  118. package/dist/{protection-audit-3J3UEOVT.js → protection-audit-YXJKFU6O.js} +3 -3
  119. package/dist/{protection-audit-3J3UEOVT.js.map → protection-audit-YXJKFU6O.js.map} +1 -1
  120. package/dist/recipes/sync-configs.js +3 -3
  121. package/dist/{render-from-row-65DC6ZP3.js → render-from-row-75JTED2Z.js} +6 -4
  122. package/dist/{renovate-dispatch-ONJMBUHJ.js → renovate-dispatch-2IHAZDVA.js} +13 -8
  123. package/dist/{renovate-dispatch-ONJMBUHJ.js.map → renovate-dispatch-2IHAZDVA.js.map} +1 -1
  124. package/dist/{report-RC5LGPU3.js → report-IAHXN5CW.js} +102 -58
  125. package/dist/report-IAHXN5CW.js.map +1 -0
  126. package/dist/{report-mirror-KKZZ6RMZ.js → report-mirror-4QM57LMS.js} +28 -10
  127. package/dist/report-mirror-4QM57LMS.js.map +1 -0
  128. package/dist/roster-GDNSIRAK.js +19 -0
  129. package/dist/roster-GDNSIRAK.js.map +1 -0
  130. package/dist/{self-updating-RKKIYNV4.js → self-updating-ZUWPGYUP.js} +7 -7
  131. package/dist/{selftest-AFLR7YV7.js → selftest-G4W3LIBQ.js} +21 -17
  132. package/dist/{selftest-AFLR7YV7.js.map → selftest-G4W3LIBQ.js.map} +1 -1
  133. package/dist/site-create-ZU3ACBTO.js +45 -0
  134. package/dist/site-create-ZU3ACBTO.js.map +1 -0
  135. package/dist/{site-mirror-H2FYMN55.js → site-mirror-WR7ACXOV.js} +10 -10
  136. package/dist/site-mirror-WR7ACXOV.js.map +1 -0
  137. package/dist/{smoke-suite-BWHR3XX5.js → smoke-suite-WAUPI6EJ.js} +7 -7
  138. package/dist/{submissions-II5J35QM.js → submissions-2SHCFOKD.js} +11 -6
  139. package/dist/submissions-2SHCFOKD.js.map +1 -0
  140. package/dist/{svelte-codemods-QWGIRNN6.js → svelte-codemods-VOLOKKFI.js} +6 -6
  141. package/dist/{sync-NKPDCMYW.js → sync-F4USL4K5.js} +6 -6
  142. package/dist/{sync-configs-HGLQV55H.js → sync-configs-OXSWKVZ2.js} +6 -6
  143. package/dist/turso-KQKBBQCK.js +24 -0
  144. package/dist/turso-KQKBBQCK.js.map +1 -0
  145. package/dist/{upgrade-WZ44IARG.js → upgrade-EVAL3JTO.js} +6 -6
  146. package/dist/util/git.d.ts +1 -1
  147. package/dist/util/git.js +1 -1
  148. package/dist/{websites-6DHIHJ2D.js → websites-Y6FN2XFD.js} +26 -21
  149. package/dist/{write-audits-to-airtable-BIK6LNVC.js → write-audits-to-airtable-TI3JMWSY.js} +6 -4
  150. package/package.json +1 -1
  151. package/dist/airtable-SJV4ZSMS.js +0 -9
  152. package/dist/announce-45HVP4J4.js.map +0 -1
  153. package/dist/attachments-NLZIJ5NU.js +0 -9
  154. package/dist/chunk-37B4LVB3.js.map +0 -1
  155. package/dist/chunk-72FJZ6LO.js.map +0 -1
  156. package/dist/chunk-74QRWE6G.js +0 -53
  157. package/dist/chunk-74QRWE6G.js.map +0 -1
  158. package/dist/chunk-CGQZFQAY.js.map +0 -1
  159. package/dist/chunk-F3JWOPTG.js.map +0 -1
  160. package/dist/chunk-GEUC5VGM.js.map +0 -1
  161. package/dist/chunk-H445EH33.js.map +0 -1
  162. package/dist/chunk-IHHAYXKY.js.map +0 -1
  163. package/dist/chunk-IY6256GK.js.map +0 -1
  164. package/dist/chunk-KN52PM63.js.map +0 -1
  165. package/dist/chunk-MZZDMBDT.js.map +0 -1
  166. package/dist/chunk-OTEVKRAH.js.map +0 -1
  167. package/dist/chunk-P4A6D6HO.js.map +0 -1
  168. package/dist/chunk-UIITN37J.js.map +0 -1
  169. package/dist/chunk-YFAXOD7Z.js +0 -42
  170. package/dist/chunk-YFAXOD7Z.js.map +0 -1
  171. package/dist/chunk-ZH5LOQET.js.map +0 -1
  172. package/dist/client-L7NAUG6J.js +0 -11
  173. package/dist/digest-5AJGWS33.js.map +0 -1
  174. package/dist/ensure-site-XESZHZEB.js.map +0 -1
  175. package/dist/fleet-events-cF2Om88w.d.ts +0 -24
  176. package/dist/forms-notify-target-4NPLX2UG.js.map +0 -1
  177. package/dist/github-signals-DMU74SAX.js.map +0 -1
  178. package/dist/header-image-BRTOLHJF.js.map +0 -1
  179. package/dist/launch-XTDMC6LD.js.map +0 -1
  180. package/dist/lighthouse-airtable-3NOFB42B.js +0 -12
  181. package/dist/match-harness-OW45CB5H.js +0 -77
  182. package/dist/match-harness-OW45CB5H.js.map +0 -1
  183. package/dist/migrate-NE3DUMTM.js +0 -7
  184. package/dist/parity-AEKYSLN6.js +0 -14
  185. package/dist/preflight-POZZI3GN.js.map +0 -1
  186. package/dist/prismic-ci-AHY5ULJR.js.map +0 -1
  187. package/dist/prismic-models-URYGODNY.js.map +0 -1
  188. package/dist/report-RC5LGPU3.js.map +0 -1
  189. package/dist/report-mirror-KKZZ6RMZ.js.map +0 -1
  190. package/dist/site-mirror-H2FYMN55.js.map +0 -1
  191. package/dist/submissions-II5J35QM.js.map +0 -1
  192. package/dist/write-audits-to-airtable-BIK6LNVC.js.map +0 -1
  193. /package/dist/{airtable-SJV4ZSMS.js.map → attachments-JFGFQRXQ.js.map} +0 -0
  194. /package/dist/{blux-RUFMNOUJ.js.map → blux-NXQLMS4W.js.map} +0 -0
  195. /package/dist/{bump-deps-EL3TKMIL.js.map → bump-deps-775VLDYB.js.map} +0 -0
  196. /package/dist/{chunk-IMYL67JV.js.map → chunk-3RLDX3Q2.js.map} +0 -0
  197. /package/dist/{chunk-CA6ADGOC.js.map → chunk-6PGXKN6F.js.map} +0 -0
  198. /package/dist/{chunk-Y5BVR4HW.js.map → chunk-6THEZQLA.js.map} +0 -0
  199. /package/dist/{chunk-DVJNYPFR.js.map → chunk-AYDQQBNG.js.map} +0 -0
  200. /package/dist/{chunk-SHGIDTYF.js.map → chunk-DYTYPKFN.js.map} +0 -0
  201. /package/dist/{chunk-NVLREB5R.js.map → chunk-EN6CTBUN.js.map} +0 -0
  202. /package/dist/{chunk-RRE74VWI.js.map → chunk-HSMN35LU.js.map} +0 -0
  203. /package/dist/{chunk-ECDJDHKT.js.map → chunk-JHJNNJLG.js.map} +0 -0
  204. /package/dist/{chunk-XY7YV2YQ.js.map → chunk-L3S3QZXL.js.map} +0 -0
  205. /package/dist/{chunk-XED2DNAI.js.map → chunk-LAG6IP7Y.js.map} +0 -0
  206. /package/dist/{chunk-SQ4FSNYL.js.map → chunk-LUF52EIY.js.map} +0 -0
  207. /package/dist/{chunk-6INOBNRO.js.map → chunk-MMSMZC7S.js.map} +0 -0
  208. /package/dist/{chunk-44SDGPGQ.js.map → chunk-PZZ6HM5T.js.map} +0 -0
  209. /package/dist/{chunk-4UTJ4G6P.js.map → chunk-TJLKWQOX.js.map} +0 -0
  210. /package/dist/{chunk-VEIIUCYL.js.map → chunk-TV76A6GN.js.map} +0 -0
  211. /package/dist/{chunk-D3CTOCUR.js.map → chunk-TVZ6QCOE.js.map} +0 -0
  212. /package/dist/{chunk-3EA43S3R.js.map → chunk-U2ZI6G7H.js.map} +0 -0
  213. /package/dist/{chunk-EQQ4H6L3.js.map → chunk-YXWBM26P.js.map} +0 -0
  214. /package/dist/{attachments-NLZIJ5NU.js.map → client-DGTO65WB.js.map} +0 -0
  215. /package/dist/{convert-to-pnpm-4DON7IFS.js.map → convert-to-pnpm-KLWA53N5.js.map} +0 -0
  216. /package/dist/{db-5K4CDEGJ.js.map → db-II5WAK4D.js.map} +0 -0
  217. /package/dist/{client-L7NAUG6J.js.map → digest-collectors-CYTN2EUX.js.map} +0 -0
  218. /package/dist/{digest-collectors-MC5NWEUA.js.map → fleet-state-RJROYXKO.js.map} +0 -0
  219. /package/dist/{health-endpoint-UCLMSSCN.js.map → health-endpoint-BJ6BKRHF.js.map} +0 -0
  220. /package/dist/{health-mirror-ONRT4DQ5.js.map → health-mirror-KU3G7V2Y.js.map} +0 -0
  221. /package/dist/{fleet-state-GJWGKTLD.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
  222. /package/dist/{init-3LMZTCVD.js.map → init-GOWDA5QS.js.map} +0 -0
  223. /package/dist/{import-airtable-IIK2QUUF.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
  224. /package/dist/{lighthouse-airtable-3NOFB42B.js.map → migrate-XZ7U5DFC.js.map} +0 -0
  225. /package/dist/{onboard-CZYBWVWH.js.map → onboard-JQQIMQW7.js.map} +0 -0
  226. /package/dist/{migrate-NE3DUMTM.js.map → orchestrate-L2A5BZFY.js.map} +0 -0
  227. /package/dist/{orchestrate-TGDFGY52.js.map → parity-2JGVUCZD.js.map} +0 -0
  228. /package/dist/{prospect-audit-AJEE3Z4L.js.map → prospect-audit-FMPGQIRQ.js.map} +0 -0
  229. /package/dist/{parity-AEKYSLN6.js.map → render-from-row-75JTED2Z.js.map} +0 -0
  230. /package/dist/{self-updating-RKKIYNV4.js.map → self-updating-ZUWPGYUP.js.map} +0 -0
  231. /package/dist/{smoke-suite-BWHR3XX5.js.map → smoke-suite-WAUPI6EJ.js.map} +0 -0
  232. /package/dist/{svelte-codemods-QWGIRNN6.js.map → svelte-codemods-VOLOKKFI.js.map} +0 -0
  233. /package/dist/{sync-NKPDCMYW.js.map → sync-F4USL4K5.js.map} +0 -0
  234. /package/dist/{sync-configs-HGLQV55H.js.map → sync-configs-OXSWKVZ2.js.map} +0 -0
  235. /package/dist/{upgrade-WZ44IARG.js.map → upgrade-EVAL3JTO.js.map} +0 -0
  236. /package/dist/{render-from-row-65DC6ZP3.js.map → websites-Y6FN2XFD.js.map} +0 -0
  237. /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-IY6256GK.js.map
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-6INOBNRO.js";
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-EQQ4H6L3.js.map
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 "airtable" to read from Websites table'
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-airtable [slug]",
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 "airtable" to read from Websites table'
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-HGLQV55H.js")).runSyncConfigsCommand(site, opts),
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 "airtable" to read from Websites table'
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-EL3TKMIL.js")).runBumpDepsCommand(site, opts),
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("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable"').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
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-RKKIYNV4.js")).runSelfUpdatingCommand(site, opts),
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("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable"').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
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-AHY5ULJR.js")).runPrismicCiCommand(site, opts),
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("--fleet <inventory>", 'Inventory file (.json or .mjs/.js), or "airtable". Read-only.').option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").option("--write-airtable", "Fleet mode: persist each site's verdict to its Websites row").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-URYGODNY.js")).runPrismicModelsCommand(site, opts),
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 "airtable" to read from Websites table'
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-WZ44IARG.js")).runUpgradeCommand(upgrade, site, opts),
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 "airtable" to read from Websites table'
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-4DON7IFS.js")).runConvertToPnpmCommand(site, opts),
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 "airtable" to read from Websites table'
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-QWGIRNN6.js")).runSvelteCodemodsCommand(site, opts),
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 "airtable" to read from Websites table'
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-UCLMSSCN.js")).runHealthEndpointCommand(site, opts),
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 "airtable" to read from Websites table'
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-OW45CB5H.js")).runMatchHarnessCommand(site, opts),
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 "airtable" to read from Websites table'
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-BWHR3XX5.js")).runSmokeSuiteCommand(site, opts),
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 "airtable" to read from Websites table'
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-CZYBWVWH.js")).runOnboardCommand(site, opts),
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 "airtable" to read from Websites table'
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-3LMZTCVD.js")).runInitCommand(site, opts), opts)
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-XTDMC6LD.js")).runLaunchCommand(site, opts),
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-airtable", "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(
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-BRTOLHJF.js")).runHeaderImageCommand(site, opts),
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-45HVP4J4.js")).runAnnounceCommand(site, opts),
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-4NPLX2UG.js")).runFormsNotifyTargetCommand(
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-AFLR7YV7.js")).runSelftestCommand(kind, site, opts),
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-POZZI3GN.js")).runPreflightCommand(site, opts),
343
+ async () => (await import("../preflight-O7Q6FWHU.js")).runPreflightCommand(site, opts),
305
344
  opts
306
345
  )
307
346
  );
308
- cli.command("ensure-site <slug>", "Create/verify the Airtable Websites row for a new site.").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(
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-XESZHZEB.js")).runEnsureSiteCommand(slug, opts),
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-AJEE3Z4L.js")).runProspectAuditCommand(url, opts),
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-RC5LGPU3.js")).runReportCommand(site, opts),
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 Airtable."
365
- ).option("--fleet", "Run across every site in the Airtable inventory.").option("--write-airtable", "Write each site's signals back to its Websites row.").action(
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-DMU74SAX.js")).runGitHubSignalsCommand({
409
+ async () => (await import("../github-signals-QIRTHHET.js")).runGitHubSignalsCommand({
368
410
  fleet: opts.fleet,
369
- writeAirtable: opts.writeAirtable
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-3J3UEOVT.js")).runProtectionAuditCommand({
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-5K4CDEGJ.js")).runDbCommand(action, opts), opts)
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-II5J35QM.js")).runSubmissionsCommand(action, opts),
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-ONJMBUHJ.js")).runRenovateDispatchCommand({
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-H2FYMN55.js")).makeSiteMirror()
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-RUFMNOUJ.js")).runBluxCommand(action, dir, opts),
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
  };