@reddoorla/maintenance 0.68.0 → 0.71.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 (136) hide show
  1. package/dist/airtable-2PJ4NSAV.js +9 -0
  2. package/dist/{announce-72R3LRNT.js → announce-3JE5FKW7.js} +6 -6
  3. package/dist/blux-FKZYMKLF.js +2205 -0
  4. package/dist/blux-FKZYMKLF.js.map +1 -0
  5. package/dist/{bump-deps-QNM3H4GI.js → bump-deps-K3JMLMBB.js} +3 -2
  6. package/dist/bump-deps-K3JMLMBB.js.map +1 -0
  7. package/dist/{chunk-RINXPU3H.js → chunk-2PWQ6MPG.js} +2 -2
  8. package/dist/{chunk-H6YZBXBQ.js → chunk-52TDNN7K.js} +37 -15
  9. package/dist/chunk-52TDNN7K.js.map +1 -0
  10. package/dist/{chunk-XWPZ46IN.js → chunk-5QC5KJHR.js} +2 -2
  11. package/dist/{chunk-O6G4HHMF.js → chunk-6J25IIAI.js} +162 -42
  12. package/dist/chunk-6J25IIAI.js.map +1 -0
  13. package/dist/chunk-7MJBTV2R.js +145 -0
  14. package/dist/chunk-7MJBTV2R.js.map +1 -0
  15. package/dist/{chunk-6JFOAPW4.js → chunk-AYM2EFSK.js} +38 -6
  16. package/dist/chunk-AYM2EFSK.js.map +1 -0
  17. package/dist/{chunk-GKSXZIMX.js → chunk-CMQMNFFJ.js} +77 -3
  18. package/dist/chunk-CMQMNFFJ.js.map +1 -0
  19. package/dist/{chunk-6MT5W6UF.js → chunk-F6OE6MGQ.js} +6 -3
  20. package/dist/chunk-F6OE6MGQ.js.map +1 -0
  21. package/dist/{chunk-OTBA7DON.js → chunk-J2SHWT2O.js} +1 -1
  22. package/dist/chunk-J2SHWT2O.js.map +1 -0
  23. package/dist/{chunk-XVR7UM6Z.js → chunk-JNXCDUYB.js} +4 -4
  24. package/dist/{chunk-2E2X5BCG.js → chunk-KOAGMTRI.js} +3 -3
  25. package/dist/chunk-KRDLHAKK.js +17 -0
  26. package/dist/chunk-KRDLHAKK.js.map +1 -0
  27. package/dist/{chunk-LYCKN7ZR.js → chunk-LPMQXS2N.js} +8 -4
  28. package/dist/chunk-LPMQXS2N.js.map +1 -0
  29. package/dist/{chunk-WCN54744.js → chunk-M7HOQ4W7.js} +388 -8
  30. package/dist/chunk-M7HOQ4W7.js.map +1 -0
  31. package/dist/{chunk-LCKKIJ22.js → chunk-MPRLMGJU.js} +11 -3
  32. package/dist/chunk-MPRLMGJU.js.map +1 -0
  33. package/dist/chunk-PKVZMFEB.js +315 -0
  34. package/dist/chunk-PKVZMFEB.js.map +1 -0
  35. package/dist/{chunk-A5KYRGQA.js → chunk-PSJQE6T6.js} +4 -4
  36. package/dist/chunk-SL24DCGC.js +41 -0
  37. package/dist/chunk-SL24DCGC.js.map +1 -0
  38. package/dist/{chunk-MAYHLXCS.js → chunk-VR246V3G.js} +25 -5
  39. package/dist/chunk-VR246V3G.js.map +1 -0
  40. package/dist/{chunk-45AUGDFR.js → chunk-WN4NEHE7.js} +2 -7
  41. package/dist/chunk-WN4NEHE7.js.map +1 -0
  42. package/dist/{chunk-YQMYQAJX.js → chunk-Y7LZHPDF.js} +2 -2
  43. package/dist/{chunk-C3PDS623.js → chunk-YWRQEDB3.js} +53 -3
  44. package/dist/chunk-YWRQEDB3.js.map +1 -0
  45. package/dist/cli/bin.js +63 -19
  46. package/dist/cli/bin.js.map +1 -1
  47. package/dist/cli/commands/audit.d.ts +1 -1
  48. package/dist/cli/commands/audit.js +13 -11
  49. package/dist/cli/commands/audit.js.map +1 -1
  50. package/dist/client/index.d.ts +84 -0
  51. package/dist/client/index.js +47 -0
  52. package/dist/client/index.js.map +1 -0
  53. package/dist/configs/playwright-a11y.js +1 -1
  54. package/dist/{convert-to-pnpm-XH7NYRVK.js → convert-to-pnpm-6F4RAFEJ.js} +3 -2
  55. package/dist/convert-to-pnpm-6F4RAFEJ.js.map +1 -0
  56. package/dist/{digest-J7TDG62Q.js → digest-GZCZYJKC.js} +11 -11
  57. package/dist/{ensure-site-2PF522VL.js → ensure-site-H5VMBPLF.js} +2 -2
  58. package/dist/forms/index.d.ts +2 -0
  59. package/dist/forms/index.js +7 -1
  60. package/dist/forms/index.js.map +1 -1
  61. package/dist/{github-signals-PP7GWYIY.js → github-signals-SQECQNST.js} +8 -8
  62. package/dist/health-endpoint-QXWM6GW2.js +54 -0
  63. package/dist/health-endpoint-QXWM6GW2.js.map +1 -0
  64. package/dist/index.d.ts +132 -17
  65. package/dist/index.js +213 -70
  66. package/dist/index.js.map +1 -1
  67. package/dist/{init-WLR35YGN.js → init-KQOIPLVT.js} +12 -8
  68. package/dist/init-KQOIPLVT.js.map +1 -0
  69. package/dist/{launch-2HPQXWII.js → launch-RX4VI7F2.js} +13 -13
  70. package/dist/{lighthouse-airtable-UBSXKHAQ.js → lighthouse-airtable-TTXULEGU.js} +3 -3
  71. package/dist/{onboard-X6KJ5Z2V.js → onboard-TFBL62J3.js} +2 -2
  72. package/dist/{orchestrate-PVCGBH4K.js → orchestrate-DYA4XAQJ.js} +6 -6
  73. package/dist/{preflight-KWVYJ2DU.js → preflight-S3GYME3Q.js} +8 -8
  74. package/dist/probe-DJAMLVTT.js +46 -0
  75. package/dist/probe-DJAMLVTT.js.map +1 -0
  76. package/dist/recipes/sync-configs.d.ts +1 -1
  77. package/dist/recipes/sync-configs.js +2 -2
  78. package/dist/{renovate-dispatch-LG4RJGDY.js → renovate-dispatch-MZPAWVBL.js} +2 -2
  79. package/dist/{report-ZBFMTT76.js → report-BLZ67QAQ.js} +12 -12
  80. package/dist/run-migration-LJNNZWIV.js +205 -0
  81. package/dist/run-migration-LJNNZWIV.js.map +1 -0
  82. package/dist/{self-updating-JTTFOJ7Q.js → self-updating-GSH427XP.js} +2 -2
  83. package/dist/{selftest-LFZPZRJP.js → selftest-KPTL6KC4.js} +11 -11
  84. package/dist/smoke-suite-GP3W57JP.js +54 -0
  85. package/dist/smoke-suite-GP3W57JP.js.map +1 -0
  86. package/dist/{svelte-codemods-KJLNH6FQ.js → svelte-codemods-GYZZX742.js} +3 -2
  87. package/dist/svelte-codemods-GYZZX742.js.map +1 -0
  88. package/dist/{sync-configs-6GBZLGRQ.js → sync-configs-CQNOTJ2B.js} +5 -4
  89. package/dist/sync-configs-CQNOTJ2B.js.map +1 -0
  90. package/dist/{types-Dg4mHvyf.d.ts → types-0jreMCF5.d.ts} +2 -2
  91. package/dist/{upgrade-TPTE26FG.js → upgrade-RTAJZD56.js} +3 -2
  92. package/dist/upgrade-RTAJZD56.js.map +1 -0
  93. package/dist/{websites-6YTT5ZTO.js → websites-SVI4KYSH.js} +8 -2
  94. package/dist/{write-audits-to-airtable-IWOO4FFH.js → write-audits-to-airtable-3XFNWMUE.js} +4 -4
  95. package/package.json +8 -1
  96. package/dist/airtable-WGQQ3KAD.js +0 -9
  97. package/dist/bump-deps-QNM3H4GI.js.map +0 -1
  98. package/dist/chunk-45AUGDFR.js.map +0 -1
  99. package/dist/chunk-6JFOAPW4.js.map +0 -1
  100. package/dist/chunk-6MT5W6UF.js.map +0 -1
  101. package/dist/chunk-C3PDS623.js.map +0 -1
  102. package/dist/chunk-GKSXZIMX.js.map +0 -1
  103. package/dist/chunk-H6YZBXBQ.js.map +0 -1
  104. package/dist/chunk-LCKKIJ22.js.map +0 -1
  105. package/dist/chunk-LYCKN7ZR.js.map +0 -1
  106. package/dist/chunk-MAYHLXCS.js.map +0 -1
  107. package/dist/chunk-O6G4HHMF.js.map +0 -1
  108. package/dist/chunk-OTBA7DON.js.map +0 -1
  109. package/dist/chunk-WCN54744.js.map +0 -1
  110. package/dist/convert-to-pnpm-XH7NYRVK.js.map +0 -1
  111. package/dist/init-WLR35YGN.js.map +0 -1
  112. package/dist/svelte-codemods-KJLNH6FQ.js.map +0 -1
  113. package/dist/sync-configs-6GBZLGRQ.js.map +0 -1
  114. package/dist/upgrade-TPTE26FG.js.map +0 -1
  115. /package/dist/{airtable-WGQQ3KAD.js.map → airtable-2PJ4NSAV.js.map} +0 -0
  116. /package/dist/{announce-72R3LRNT.js.map → announce-3JE5FKW7.js.map} +0 -0
  117. /package/dist/{chunk-RINXPU3H.js.map → chunk-2PWQ6MPG.js.map} +0 -0
  118. /package/dist/{chunk-XWPZ46IN.js.map → chunk-5QC5KJHR.js.map} +0 -0
  119. /package/dist/{chunk-XVR7UM6Z.js.map → chunk-JNXCDUYB.js.map} +0 -0
  120. /package/dist/{chunk-2E2X5BCG.js.map → chunk-KOAGMTRI.js.map} +0 -0
  121. /package/dist/{chunk-A5KYRGQA.js.map → chunk-PSJQE6T6.js.map} +0 -0
  122. /package/dist/{chunk-YQMYQAJX.js.map → chunk-Y7LZHPDF.js.map} +0 -0
  123. /package/dist/{digest-J7TDG62Q.js.map → digest-GZCZYJKC.js.map} +0 -0
  124. /package/dist/{ensure-site-2PF522VL.js.map → ensure-site-H5VMBPLF.js.map} +0 -0
  125. /package/dist/{github-signals-PP7GWYIY.js.map → github-signals-SQECQNST.js.map} +0 -0
  126. /package/dist/{launch-2HPQXWII.js.map → launch-RX4VI7F2.js.map} +0 -0
  127. /package/dist/{lighthouse-airtable-UBSXKHAQ.js.map → lighthouse-airtable-TTXULEGU.js.map} +0 -0
  128. /package/dist/{onboard-X6KJ5Z2V.js.map → onboard-TFBL62J3.js.map} +0 -0
  129. /package/dist/{orchestrate-PVCGBH4K.js.map → orchestrate-DYA4XAQJ.js.map} +0 -0
  130. /package/dist/{preflight-KWVYJ2DU.js.map → preflight-S3GYME3Q.js.map} +0 -0
  131. /package/dist/{renovate-dispatch-LG4RJGDY.js.map → renovate-dispatch-MZPAWVBL.js.map} +0 -0
  132. /package/dist/{report-ZBFMTT76.js.map → report-BLZ67QAQ.js.map} +0 -0
  133. /package/dist/{self-updating-JTTFOJ7Q.js.map → self-updating-GSH427XP.js.map} +0 -0
  134. /package/dist/{selftest-LFZPZRJP.js.map → selftest-KPTL6KC4.js.map} +0 -0
  135. /package/dist/{websites-6YTT5ZTO.js.map → websites-SVI4KYSH.js.map} +0 -0
  136. /package/dist/{write-audits-to-airtable-IWOO4FFH.js.map → write-audits-to-airtable-3XFNWMUE.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/audits/a11y-airtable.ts","../src/audits/deps-airtable.ts","../src/audits/security-airtable.ts","../src/audits/domain-airtable.ts","../src/audits/browser-airtable.ts","../src/audits/netlify-deploy-airtable.ts","../src/audits/function-health-airtable.ts","../src/audits/smoke-airtable.ts","../src/audits/form-e2e-airtable.ts","../src/audits/write-audits-to-airtable.ts"],"sourcesContent":["import type { AuditResult } from \"../types.js\";\n\ntype A11yDetails = {\n totalViolations: number;\n byImpact: Partial<Record<\"minor\" | \"moderate\" | \"serious\" | \"critical\", number>>;\n};\n\n/** True when an a11y AuditResult carries real counts worth persisting.\n * Mirrors the `hasRealScores` policy on lighthouse: write whenever real\n * data exists, regardless of status (a \"warn\" or \"fail\" with concrete\n * violation counts is exactly what the dashboard needs to track). */\nexport function hasA11yCounts(result: AuditResult): boolean {\n if (result.audit !== \"a11y\") return false;\n const details = result.details as A11yDetails | undefined;\n return typeof details?.totalViolations === \"number\";\n}\n\nexport function a11yCountsFromResult(result: AuditResult): { violations: number } {\n if (result.audit !== \"a11y\") {\n throw new Error(`Expected an 'a11y' AuditResult, got '${result.audit}'`);\n }\n const details = result.details as A11yDetails | undefined;\n return { violations: details?.totalViolations ?? 0 };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { DepsDetails, DepsDriftEntry } from \"./deps.js\";\n\n/** True when a deps AuditResult carries the structured details (an entries\n * array, per {@link DepsDetails}). */\nexport function hasDepsCounts(result: AuditResult): boolean {\n if (result.audit !== \"deps\") return false;\n const d = result.details as Partial<DepsDetails> | undefined;\n return Array.isArray(d?.entries);\n}\n\nexport function depsCountsFromResult(result: AuditResult): {\n drifted: number;\n majorBehind: number;\n outdated: number | null;\n majorOutdated: number | null;\n} {\n if (result.audit !== \"deps\") {\n throw new Error(`Expected a 'deps' AuditResult, got '${result.audit}'`);\n }\n const details = (result.details ?? {}) as Partial<DepsDetails>;\n const entries = (details.entries ?? []) as DepsDriftEntry[];\n // \"drifted\" = drift !== \"same\", which intentionally includes \"newer\" (ahead of\n // baseline) — parity with the deps audit summary text.\n const drifted = entries.filter((e) => e.drift !== \"same\").length;\n const majorBehind = entries.filter((e) => e.drift === \"major\").length;\n // The real installed-version drift, distinct from declared-range drift. Null\n // when the audit couldn't determine it (no/stale lockfile) — kept null (not 0)\n // so the dashboard shows \"—\" rather than a misleading \"clean\".\n const outdated = details.outdated?.outdated ?? null;\n // How many of those outdated installs are a *major* behind the registry's\n // latest — surfaced separately from the total so the cockpit can show\n // \"N major\" available, distinct from the baseline-drift \"major\" count. Shares\n // the outdated signal's null-when-undetermined semantics.\n const majorOutdated = details.outdated?.major ?? null;\n return { drifted, majorBehind, outdated, majorOutdated };\n}\n","import type { AuditResult } from \"../types.js\";\nimport { normalizeSecurityAdvisory, type SecurityAdvisory } from \"../reports/airtable/websites.js\";\n\ntype SecurityDetails = {\n counts: { low: number; moderate: number; high: number; critical: number };\n advisories?: unknown;\n};\n\n/** True when a security AuditResult carries a counts object. Skipped runs\n * (no pnpm + no npm) have no details and return false. */\nexport function hasSecurityCounts(result: AuditResult): boolean {\n if (result.audit !== \"security\") return false;\n const details = result.details as SecurityDetails | undefined;\n return !!details && typeof details.counts === \"object\";\n}\n\nexport function securityCountsFromResult(result: AuditResult): {\n critical: number;\n high: number;\n moderate: number;\n low: number;\n} {\n if (result.audit !== \"security\") {\n throw new Error(`Expected a 'security' AuditResult, got '${result.audit}'`);\n }\n const details = result.details as SecurityDetails | undefined;\n const c = details?.counts ?? { low: 0, moderate: 0, high: 0, critical: 0 };\n return { critical: c.critical, high: c.high, moderate: c.moderate, low: c.low };\n}\n\n/** The known advisories behind the counts, normalized for persistence. Empty when the\n * run was clean or carried no advisory detail (npm/pnpm sometimes omit it). Malformed\n * entries are dropped via the shared `normalizeSecurityAdvisory`. */\nexport function advisoriesFromResult(result: AuditResult): SecurityAdvisory[] {\n if (result.audit !== \"security\") {\n throw new Error(`Expected a 'security' AuditResult, got '${result.audit}'`);\n }\n const details = result.details as SecurityDetails | undefined;\n const raw = details?.advisories;\n if (!Array.isArray(raw)) return [];\n return raw.map(normalizeSecurityAdvisory).filter((a): a is SecurityAdvisory => a !== null);\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { DomainResult } from \"../reports/airtable/websites.js\";\n\ntype DomainDetails = { resolved: boolean; certDaysRemaining: number | null; checkedAt: string };\n\n/** True when an AuditResult is a `domain` audit carrying a usable details payload (i.e. it\n * actually ran — a \"skip\" for a site with no deployed URL has no details). */\nexport function hasDomainResult(result: AuditResult): boolean {\n if (result.audit !== \"domain\") return false;\n const d = result.details as DomainDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable domain result (cert days + checked-at) from a `domain` audit. */\nexport function domainResultFromAudit(result: AuditResult): DomainResult {\n if (result.audit !== \"domain\") {\n throw new Error(`Expected a 'domain' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as DomainDetails | undefined;\n return {\n certDaysRemaining: typeof d?.certDaysRemaining === \"number\" ? d.certDaysRemaining : null,\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { BrowserAuditFields } from \"../reports/airtable/websites.js\";\n\ntype BrowserDetails = {\n desktopOk: boolean;\n mobileOk: boolean;\n linksOk: boolean;\n reachableOk: boolean;\n titleMetaOk: boolean;\n brokenLinks: number;\n checkedAt: string;\n};\n\n/** True when an AuditResult is a `browser` audit that actually ran (has a details payload — a\n * \"skip\" for a site with no deployed URL has none). */\nexport function hasBrowserResult(result: AuditResult): boolean {\n if (result.audit !== \"browser\") return false;\n const d = result.details as BrowserDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable browser verdicts from a `browser` audit. */\nexport function browserFieldsFromAudit(result: AuditResult): BrowserAuditFields {\n if (result.audit !== \"browser\") {\n throw new Error(`Expected a 'browser' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as BrowserDetails | undefined;\n return {\n desktopOk: d?.desktopOk === true,\n mobileOk: d?.mobileOk === true,\n linksOk: d?.linksOk === true,\n reachableOk: d?.reachableOk === true,\n titleMetaOk: d?.titleMetaOk === true,\n brokenLinks: typeof d?.brokenLinks === \"number\" ? d.brokenLinks : 0,\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { NetlifyDeployResult } from \"../reports/airtable/websites.js\";\n\ntype NetlifyDeployDetails = {\n state: string | null;\n deployedAt: string | null;\n logUrl: string | null;\n checkedAt: string;\n};\n\n/** True when an AuditResult is a `netlify-deploy` audit carrying a usable details\n * payload (i.e. it actually ran — a \"skip\" for a site with no Netlify id / no\n * token has no details). */\nexport function hasNetlifyDeployResult(result: AuditResult): boolean {\n if (result.audit !== \"netlify-deploy\") return false;\n const d = result.details as NetlifyDeployDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable deploy result (state + deployedAt + logUrl +\n * checkedAt) from a `netlify-deploy` audit. */\nexport function netlifyDeployResultFromAudit(result: AuditResult): NetlifyDeployResult {\n if (result.audit !== \"netlify-deploy\") {\n throw new Error(`Expected a 'netlify-deploy' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as NetlifyDeployDetails | undefined;\n return {\n state: typeof d?.state === \"string\" ? d.state : null,\n deployedAt: typeof d?.deployedAt === \"string\" ? d.deployedAt : null,\n logUrl: typeof d?.logUrl === \"string\" ? d.logUrl : null,\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { FunctionHealthResult } from \"../reports/airtable/websites.js\";\n\ntype FunctionHealthDetails = {\n ok: boolean;\n prismic: \"ok\" | \"error\" | \"skipped\" | null;\n forms: unknown;\n checkedAt: string;\n};\n\n/** True when an AuditResult is a `function-health` audit carrying a usable details payload (i.e. it\n * actually ran — a self-skip for an unreachable / non-JSON `/health`, or a site with no deployed\n * URL, has no details, so the writer preserves the prior verdict). */\nexport function hasFunctionHealthResult(result: AuditResult): boolean {\n if (result.audit !== \"function-health\") return false;\n const d = result.details as FunctionHealthDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable function-health verdicts. `functionHealth` from `ok`; `cmsReachable`\n * from the same body's `prismic` sub-status (R2.2): `\"ok\"` → `\"pass\"`, `\"error\"` → `\"fail\"`, and\n * anything else (`\"skipped\"` — a placeholder repo, or a raw `null` from a \"deployed but erroring\"\n * body / an unrecognized value) → `null` — the CMS probe never actually ran, so it must not red CMS\n * reachability for a site that simply hasn't wired Prismic yet. Only `functionHealth` keys off\n * `ok`. */\nexport function functionHealthResultFromAudit(result: AuditResult): FunctionHealthResult {\n if (result.audit !== \"function-health\") {\n throw new Error(`Expected a 'function-health' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as FunctionHealthDetails | undefined;\n const cmsReachable: \"pass\" | \"fail\" | null =\n d?.prismic === \"ok\" ? \"pass\" : d?.prismic === \"error\" ? \"fail\" : null;\n return {\n functionHealth: d?.ok === true ? \"pass\" : \"fail\",\n cmsReachable,\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { SmokeResult } from \"../reports/airtable/websites.js\";\n\ntype SmokeDetails = { ok: \"pass\" | \"fail\"; checkedAt: string };\n\n/** True when an AuditResult is a `smoke` audit that actually ran (has details with\n * a checkedAt — a \"skip\" for a missing `pnpm`/`test:smoke` script has none, so the\n * writer preserves the prior verdict). */\nexport function hasSmokeResult(result: AuditResult): boolean {\n if (result.audit !== \"smoke\") return false;\n const d = result.details as SmokeDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable smoke verdict (pass/fail + checked-at). */\nexport function smokeResultFromAudit(result: AuditResult): SmokeResult {\n if (result.audit !== \"smoke\") {\n throw new Error(`Expected a 'smoke' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as SmokeDetails | undefined;\n return {\n ok: d?.ok === \"fail\" ? \"fail\" : \"pass\",\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { FormE2eResult } from \"../reports/airtable/websites.js\";\n\ntype FormE2eDetails = { ok: \"pass\" | \"fail\" | null; formPresent: boolean; checkedAt: string };\n\n/** True when an AuditResult is a `form-e2e` audit that actually RAN (has details\n * with a checkedAt). Both a real verdict AND the no-form n/a case carry a checkedAt,\n * so both persist; only a no-deployed-URL skip (no details) is preserved-prior. */\nexport function hasFormE2eResult(result: AuditResult): boolean {\n if (result.audit !== \"form-e2e\") return false;\n const d = result.details as FormE2eDetails | undefined;\n return !!d && typeof d.checkedAt === \"string\";\n}\n\n/** Extract the Airtable-writable form-e2e verdict (pass/fail/null + checked-at). */\nexport function formE2eResultFromAudit(result: AuditResult): FormE2eResult {\n if (result.audit !== \"form-e2e\") {\n throw new Error(`Expected a 'form-e2e' AuditResult, got '${result.audit}'`);\n }\n const d = result.details as FormE2eDetails | undefined;\n const ok = d?.ok === \"pass\" ? \"pass\" : d?.ok === \"fail\" ? \"fail\" : null;\n return {\n ok,\n checkedAt: typeof d?.checkedAt === \"string\" ? d.checkedAt : new Date().toISOString(),\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { type WebsiteRow, siteSlug, updateAuditFields } from \"../reports/airtable/websites.js\";\nimport type {\n A11yCounts,\n DepsCounts,\n SecurityCounts,\n SecurityAdvisory,\n DomainResult,\n BrowserAuditFields,\n NetlifyDeployResult,\n FunctionHealthResult,\n SmokeResult,\n FormE2eResult,\n} from \"../reports/airtable/websites.js\";\nimport type { LighthouseScoreWriteback } from \"../reports/types.js\";\nimport { hasRealScores, lighthouseScoresFromResult } from \"./lighthouse-airtable.js\";\nimport { hasA11yCounts, a11yCountsFromResult } from \"./a11y-airtable.js\";\nimport { hasDepsCounts, depsCountsFromResult } from \"./deps-airtable.js\";\nimport {\n hasSecurityCounts,\n securityCountsFromResult,\n advisoriesFromResult,\n} from \"./security-airtable.js\";\nimport { hasDomainResult, domainResultFromAudit } from \"./domain-airtable.js\";\nimport { hasBrowserResult, browserFieldsFromAudit } from \"./browser-airtable.js\";\nimport { hasNetlifyDeployResult, netlifyDeployResultFromAudit } from \"./netlify-deploy-airtable.js\";\nimport {\n hasFunctionHealthResult,\n functionHealthResultFromAudit,\n} from \"./function-health-airtable.js\";\nimport { hasSmokeResult, smokeResultFromAudit } from \"./smoke-airtable.js\";\nimport { hasFormE2eResult, formE2eResultFromAudit } from \"./form-e2e-airtable.js\";\nimport { detectAuditEvents } from \"./fleet-event-detectors.js\";\nimport type { FleetEvent } from \"../db/fleet-events.js\";\n\ntype WriteSummary = {\n siteName: string;\n writes: Array<{\n audit:\n | \"lighthouse\"\n | \"a11y\"\n | \"deps\"\n | \"security\"\n | \"github-signals\"\n | \"domain\"\n | \"browser\"\n | \"netlify-deploy\"\n | \"function-health\"\n | \"smoke\"\n | \"form-e2e\";\n counts: object;\n }>;\n /** Fleet-activity events detected from this site's prior row vs the fresh audits.\n * Optional: only the fleet path records them; the single-site path ignores them. */\n events?: FleetEvent[];\n};\n\n/** Orchestrates the per-audit Airtable writes for `audit --write-airtable`.\n * Extracted from the CLI command so it can be unit-tested with a fake base\n * and so adding new audit types is a one-line addition here rather than\n * growing the CLI handler.\n *\n * Throws (with .exitCode set) on the failure modes the CLI surfaces today:\n * - 2: --only ran without lighthouse, or no Websites row matched the slug\n * - 1: lighthouse ran but produced no real scores (infrastructure failure).\n * The a11y/deps/security writes still complete FIRST — a Lighthouse\n * miss flags the site without discarding its other audit data.\n *\n * Precedence note: the Websites-row lookup (exitCode 2) is checked BEFORE the\n * no-scores gate (exitCode 1), so the rare no-row + no-scores combo surfaces\n * as exitCode 2. */\nexport async function writeAuditsToAirtable(args: {\n base: AirtableBase;\n websites: WebsiteRow[];\n slug: string;\n results: AuditResult[];\n}): Promise<WriteSummary> {\n const { base, websites, slug, results } = args;\n\n // Lighthouse is OPTIONAL. The checkout-free `--only lighthouse,domain,browser` nightly includes\n // it, but a standalone `--only security` (checkout-ful) sweep legitimately has none. We write\n // whatever audits produced values; only the lighthouse-MISS flag below is lighthouse-specific\n // (so a real Lighthouse infra failure still reds the run without discarding other audit data).\n const lhResult = results.find((r) => r.audit === \"lighthouse\");\n const target = websites.find((w) => siteSlug(w.name) === slug);\n if (!target) {\n throw Object.assign(new Error(`No Websites row matched slug \"${slug}\"`), { exitCode: 2 });\n }\n\n const writes: WriteSummary[\"writes\"] = [];\n const audits: {\n scores?: LighthouseScoreWriteback;\n a11y?: A11yCounts;\n deps?: DepsCounts;\n security?: SecurityCounts;\n securityAdvisories?: SecurityAdvisory[];\n domain?: DomainResult;\n browser?: BrowserAuditFields;\n netlifyDeploy?: NetlifyDeployResult;\n functionHealth?: FunctionHealthResult;\n smoke?: SmokeResult;\n formE2e?: FormE2eResult;\n } = {};\n\n // Collect every audit that produced real values into ONE merged input, then do a\n // SINGLE atomic Airtable update (was: up to four sequential updates on the same\n // row — a mid-sequence failure left it half-written yet reported fully failed, at\n // 4× the request volume). Lighthouse is the most timeout-prone audit: a Lighthouse\n // miss must NOT discard the site's valid a11y/deps/security results (morning-brief\n // 2026-06-10 MEDIUM-E). So include Lighthouse scores only when real, include the\n // other audits whenever present, write all of them in one update, then — if\n // Lighthouse missed — throw AFTER that atomic write so the site is still flagged\n // (exitCode 1 / collected in FleetWriteResult.failed) without losing its other data.\n const lhHasScores = lhResult ? hasRealScores(lhResult) : false;\n if (lhResult && lhHasScores) {\n const scores = lighthouseScoresFromResult(lhResult);\n audits.scores = scores;\n writes.push({ audit: \"lighthouse\", counts: scores });\n }\n\n const a11y = results.find((r) => r.audit === \"a11y\");\n if (a11y && hasA11yCounts(a11y)) {\n const counts = a11yCountsFromResult(a11y);\n audits.a11y = counts;\n writes.push({ audit: \"a11y\", counts });\n }\n\n const deps = results.find((r) => r.audit === \"deps\");\n if (deps && hasDepsCounts(deps)) {\n const counts = depsCountsFromResult(deps);\n audits.deps = counts;\n writes.push({ audit: \"deps\", counts });\n }\n\n const sec = results.find((r) => r.audit === \"security\");\n if (sec && hasSecurityCounts(sec)) {\n const counts = securityCountsFromResult(sec);\n audits.security = counts;\n // Persist the advisory list alongside the counts so the dashboard can show which packages\n // are vulnerable. An empty array (clean run) clears any stale list.\n audits.securityAdvisories = advisoriesFromResult(sec);\n writes.push({ audit: \"security\", counts });\n }\n\n const dom = results.find((r) => r.audit === \"domain\");\n if (dom && hasDomainResult(dom)) {\n const result = domainResultFromAudit(dom);\n audits.domain = result;\n writes.push({ audit: \"domain\", counts: result });\n }\n\n const browser = results.find((r) => r.audit === \"browser\");\n if (browser && hasBrowserResult(browser)) {\n const fields = browserFieldsFromAudit(browser);\n audits.browser = fields;\n writes.push({ audit: \"browser\", counts: fields });\n }\n\n const netlifyDeploy = results.find((r) => r.audit === \"netlify-deploy\");\n if (netlifyDeploy && hasNetlifyDeployResult(netlifyDeploy)) {\n const result = netlifyDeployResultFromAudit(netlifyDeploy);\n audits.netlifyDeploy = result;\n writes.push({ audit: \"netlify-deploy\", counts: result });\n }\n\n const functionHealth = results.find((r) => r.audit === \"function-health\");\n if (functionHealth && hasFunctionHealthResult(functionHealth)) {\n const result = functionHealthResultFromAudit(functionHealth);\n audits.functionHealth = result;\n writes.push({ audit: \"function-health\", counts: result });\n }\n\n const smoke = results.find((r) => r.audit === \"smoke\");\n if (smoke && hasSmokeResult(smoke)) {\n const result = smokeResultFromAudit(smoke);\n audits.smoke = result;\n writes.push({ audit: \"smoke\", counts: result });\n }\n\n const formE2e = results.find((r) => r.audit === \"form-e2e\");\n if (formE2e && hasFormE2eResult(formE2e)) {\n const result = formE2eResultFromAudit(formE2e);\n audits.formE2e = result;\n writes.push({ audit: \"form-e2e\", counts: result });\n }\n\n // One atomic write of everything that ran. Skip the call only if there is nothing\n // to write at all (no real scores AND no other audit produced values) — an empty\n // update is a wasted request.\n if (Object.keys(audits).length > 0) {\n await updateAuditFields(base, target.id, audits);\n }\n\n // Detect fleet-activity transitions from the prior row (`target`, loaded before this\n // write) vs the fresh audits. Computed here where both are in hand; recorded by the\n // caller (fleet path) — single-site callers simply ignore `events`.\n const events = detectAuditEvents(\n target,\n {\n ...(audits.security !== undefined ? { security: audits.security } : {}),\n ...(audits.domain !== undefined ? { domain: audits.domain } : {}),\n },\n new Date().toISOString(),\n );\n\n // Lighthouse-miss flag: only when lighthouse WAS requested (in results) but produced no scores —\n // an infra failure worth reding the run, AFTER persisting the other audits. A sweep that never\n // ran lighthouse (e.g. `--only security`) skips this entirely.\n if (lhResult && !lhHasScores) {\n // Enumerate what WAS persisted so the failure (surfaced to the single-site\n // CLI operator via console.error) reads as a partial write, not a total one.\n const persisted = writes.map((w) => w.audit);\n throw Object.assign(\n new Error(\n `Lighthouse audit produced no scores; ${\n persisted.length ? `wrote ${persisted.join(\"/\")} but refused Lighthouse` : \"wrote nothing\"\n }. Summary: ${lhResult.summary}`,\n ),\n { exitCode: 1 },\n );\n }\n\n return { siteName: target.name, writes, events };\n}\n\nexport type FleetWriteResult = {\n written: WriteSummary[];\n failed: Array<{ slug: string; error: string }>;\n};\n\n/** Render the fleet write-back outcome for the CLI/CI. Beyond the human-readable\n * lines, it emits a single deterministic, machine-parseable line —\n * `FLEET_WRITE_SUMMARY wrote=N failed=M total=T` — that the nightly workflow\n * greps to decide pass/fail. Keying CI on this line (not the prose, and not a\n * \"wrote ≥ 1\" heuristic) lets the gate tolerate a single known flake while\n * still reding on a total or mass write-back failure. */\nexport function formatFleetWriteSummary(result: FleetWriteResult): string {\n const wrote = result.written.length;\n const failed = result.failed.length;\n const total = wrote + failed;\n let out = `→ wrote ${wrote} site(s) to Airtable`;\n if (failed > 0) {\n out += `\\n⚠ ${failed} site(s) not written: ${result.failed\n .map((f) => `${f.slug} (${f.error})`)\n .join(\"; \")}`;\n }\n out += `\\nFLEET_WRITE_SUMMARY wrote=${wrote} failed=${failed} total=${total}`;\n return out;\n}\n\n/** Write each site's pooled audit results back to its own Websites row,\n * best-effort. Results are grouped by `result.site` (the slug the fleet\n * inventory stamped as Site.name). A per-site failure (no scores, no matching\n * row) is collected — not thrown — so one bad site never aborts the batch. */\nexport async function writeFleetAuditsToAirtable(args: {\n base: AirtableBase;\n websites: WebsiteRow[];\n results: AuditResult[];\n}): Promise<FleetWriteResult> {\n const { base, websites, results } = args;\n\n const bySlug = new Map<string, AuditResult[]>();\n for (const r of results) {\n const arr = bySlug.get(r.site) ?? [];\n arr.push(r);\n bySlug.set(r.site, arr);\n }\n\n const written: WriteSummary[] = [];\n const failed: FleetWriteResult[\"failed\"] = [];\n // Serial on purpose: even at one (now atomic) update call per site, Airtable's\n // ~5 req/sec limit means a Promise.all fan-out across the fleet would burst and\n // trip 429s (silently filed as failures). Below a few dozen sites, serial trades\n // wall-clock for safety. (morning-brief 2026-06-09 MEDIUM-3.) Add a bounded pool\n // when the fleet grows.\n for (const [slug, siteResults] of bySlug) {\n try {\n written.push(await writeAuditsToAirtable({ base, websites, slug, results: siteResults }));\n } catch (e) {\n failed.push({ slug, error: (e as Error).message });\n }\n }\n return { written, failed };\n}\n"],"mappings":"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","names":[]}
@@ -3,9 +3,10 @@ import {
3
3
  } from "./chunk-U5SZ4FOP.js";
4
4
  import {
5
5
  ACTIVE_STATUSES,
6
+ isPreLaunch,
6
7
  listWebsites,
7
8
  siteSlug
8
- } from "./chunk-C3PDS623.js";
9
+ } from "./chunk-YWRQEDB3.js";
9
10
 
10
11
  // src/inventory/airtable.ts
11
12
  function fromAirtableBase(base, opts = {}) {
@@ -17,7 +18,9 @@ function fromAirtableBase(base, opts = {}) {
17
18
  );
18
19
  }
19
20
  const websites = await listWebsites(base);
20
- return websites.filter((w) => w.status !== null && ACTIVE_STATUSES.has(w.status) && w.url.length > 0).flatMap((w) => {
21
+ return websites.filter(
22
+ (w) => w.status !== null && ACTIVE_STATUSES.has(w.status) && !isPreLaunch(w.status) && w.url.length > 0
23
+ ).flatMap((w) => {
21
24
  const slug = siteSlug(w.name);
22
25
  if (slug.length === 0) {
23
26
  console.warn(
@@ -47,4 +50,4 @@ function fromAirtableBase(base, opts = {}) {
47
50
  export {
48
51
  fromAirtableBase
49
52
  };
50
- //# sourceMappingURL=chunk-6MT5W6UF.js.map
53
+ //# sourceMappingURL=chunk-F6OE6MGQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/inventory/airtable.ts"],"sourcesContent":["import type { Site, InventoryProvider } from \"../types.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport {\n listWebsites,\n siteSlug,\n ACTIVE_STATUSES,\n isPreLaunch,\n} from \"../reports/airtable/websites.js\";\nimport { isHttpUrl } from \"../util/url.js\";\n\nexport type AirtableInventoryOptions = {\n /**\n * Local workdir to compute each site's path as `{workdir}/{slug}`.\n * Defaults to REDDOOR_FLEET_WORKDIR env var if not provided.\n * Airtable doesn't store local checkout paths, so this is required.\n */\n workdir?: string;\n};\n\n/**\n * Read sites from the Airtable Websites table as an InventoryProvider.\n * Each row becomes one Site; `path` is computed as `{workdir}/{slug}`.\n * Only LIVE `maintenance` sites that have a `url` are included — pre-launch\n * stages (\"launch period\" / \"in development\", via isPreLaunch) are excluded so a\n * not-yet-live site is never audited as production (its deploy/domain/uptime/CMS\n * audits would fail against nothing and red its Airtable row) nor swept by other\n * `--fleet airtable` ops; it re-enters the fleet when a Launch report flips its\n * Status to \"maintenance\". The production URL is exposed as `Site.deployedUrl` so\n * the lighthouse audit can run against it with no checkout. `repoUrl` is\n * intentionally NOT set from `url` — a clone source must come from `gitRepo`\n * (`owner/repo`), never the production URL.\n */\nexport function fromAirtableBase(\n base: AirtableBase,\n opts: AirtableInventoryOptions = {},\n): InventoryProvider {\n return async (): Promise<Site[]> => {\n const workdir = opts.workdir ?? process.env.REDDOOR_FLEET_WORKDIR;\n if (!workdir) {\n throw new Error(\n \"fromAirtableBase requires `workdir` option or REDDOOR_FLEET_WORKDIR env (sites need a local path)\",\n );\n }\n const websites = await listWebsites(base);\n return websites\n .filter(\n (w) =>\n w.status !== null &&\n ACTIVE_STATUSES.has(w.status) &&\n !isPreLaunch(w.status) &&\n w.url.length > 0,\n )\n .flatMap((w) => {\n const slug = siteSlug(w.name);\n // An empty slug (a Name with no slug-able characters) can't form a stable\n // path and — fatally — can't be matched back to its Websites row on\n // write-back: every empty-slug site would collapse under the \"\" key and\n // mis-write or fail. Skip it loudly rather than silently mis-map it.\n if (slug.length === 0) {\n console.warn(\n `[inventory] skipping \"${w.name}\" (row ${w.id}): Name has no slug-able characters (empty slug)`,\n );\n return [];\n }\n const site: Site = {\n path: `${workdir}/${slug}`,\n name: slug,\n meta: { airtableRowId: w.id, displayName: w.name },\n };\n // Scheme-allowlist the Airtable `url` before exposing it as the\n // deployed-audit target (it's handed straight to Chrome/lhci). A\n // `file://`/`gopher://`/internal-host value would be a local-file read\n // or SSRF — skip the deployed audit for that site rather than trust it.\n if (isHttpUrl(w.url)) {\n site.deployedUrl = w.url;\n } else {\n console.warn(\n `[inventory] skipping deployed audit for \"${w.name}\": url is not http(s): ${JSON.stringify(w.url)}`,\n );\n }\n if (w.gitRepo) site.gitRepo = w.gitRepo;\n // Expose the Netlify site id (operator-set identity column) so the\n // netlify-deploy audit can query the API with no checkout. Absent → that\n // audit skips for this site. Not derived from the URL.\n if (w.netlifyId) site.netlifyId = w.netlifyId;\n return [site];\n });\n };\n}\n"],"mappings":";;;;;;;;;;;AAgCO,SAAS,iBACd,MACA,OAAiC,CAAC,GACf;AACnB,SAAO,YAA6B;AAClC,UAAM,UAAU,KAAK,WAAW,QAAQ,IAAI;AAC5C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,WAAW,MAAM,aAAa,IAAI;AACxC,WAAO,SACJ;AAAA,MACC,CAAC,MACC,EAAE,WAAW,QACb,gBAAgB,IAAI,EAAE,MAAM,KAC5B,CAAC,YAAY,EAAE,MAAM,KACrB,EAAE,IAAI,SAAS;AAAA,IACnB,EACC,QAAQ,CAAC,MAAM;AACd,YAAM,OAAO,SAAS,EAAE,IAAI;AAK5B,UAAI,KAAK,WAAW,GAAG;AACrB,gBAAQ;AAAA,UACN,yBAAyB,EAAE,IAAI,UAAU,EAAE,EAAE;AAAA,QAC/C;AACA,eAAO,CAAC;AAAA,MACV;AACA,YAAM,OAAa;AAAA,QACjB,MAAM,GAAG,OAAO,IAAI,IAAI;AAAA,QACxB,MAAM;AAAA,QACN,MAAM,EAAE,eAAe,EAAE,IAAI,aAAa,EAAE,KAAK;AAAA,MACnD;AAKA,UAAI,UAAU,EAAE,GAAG,GAAG;AACpB,aAAK,cAAc,EAAE;AAAA,MACvB,OAAO;AACL,gBAAQ;AAAA,UACN,4CAA4C,EAAE,IAAI,0BAA0B,KAAK,UAAU,EAAE,GAAG,CAAC;AAAA,QACnG;AAAA,MACF;AACA,UAAI,EAAE,QAAS,MAAK,UAAU,EAAE;AAIhC,UAAI,EAAE,UAAW,MAAK,YAAY,EAAE;AACpC,aAAO,CAAC,IAAI;AAAA,IACd,CAAC;AAAA,EACL;AACF;","names":[]}
@@ -42,4 +42,4 @@ async function recordFleetEventsBestEffort(events, now, open = () => openDb(read
42
42
  export {
43
43
  recordFleetEventsBestEffort
44
44
  };
45
- //# sourceMappingURL=chunk-OTBA7DON.js.map
45
+ //# sourceMappingURL=chunk-J2SHWT2O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/fleet-events.ts","../src/audits/fleet-events-writer.ts"],"sourcesContent":["import type { Selectable } from \"kysely\";\nimport type { Db } from \"./client.js\";\nimport type { FleetEventsTable } from \"./schema.js\";\n\nexport type FleetEventType =\n | \"pr_automerged\"\n | \"vuln_cleared\"\n | \"ci_recovered\"\n | \"site_launched\"\n | \"fleet_swept\"\n | \"cert_renewed\"\n | \"report_sent_with_override\";\n\n/** One recorded fleet activity event. `id` is deterministic (see the detectors)\n * so re-runs INSERT OR IGNORE without duplicating. `siteId`/`siteName` are null\n * for fleet-wide rollups; `data` is optional structured detail (e.g. the PR url). */\nexport type FleetEvent = {\n id: string;\n ts: string;\n type: FleetEventType;\n siteId: string | null;\n siteName: string | null;\n summary: string;\n data: unknown | null;\n};\n\n/** Tolerant JSON parse for the stored `data` column — a malformed value (should\n * never happen, we wrote it) degrades to null rather than throwing the read. */\nfunction parseData(raw: string | null): unknown | null {\n if (raw === null) return null;\n try {\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\nfunction rowToEvent(r: Selectable<FleetEventsTable>): FleetEvent {\n return {\n id: r.id,\n ts: r.ts,\n type: r.type as FleetEventType,\n siteId: r.site_id,\n siteName: r.site_name,\n summary: r.summary,\n data: parseData(r.data),\n };\n}\n\n/** Idempotent append: INSERT OR IGNORE on the deterministic primary key, so an\n * overlapping/re-run sweep never duplicates an event. The first write wins. */\nexport async function recordFleetEvent(db: Db, e: FleetEvent): Promise<void> {\n await db\n .insertInto(\"fleet_events\")\n .values({\n id: e.id,\n ts: e.ts,\n type: e.type,\n site_id: e.siteId,\n site_name: e.siteName,\n summary: e.summary,\n data: e.data == null ? null : JSON.stringify(e.data),\n created_at: new Date().toISOString(),\n })\n .onConflict((oc) => oc.column(\"id\").doNothing())\n .execute();\n}\n\n/** Recent events at/after `sinceIso`, newest-first, capped at `limit`. */\nexport async function listFleetEvents(\n db: Db,\n opts: { sinceIso: string; limit: number },\n): Promise<FleetEvent[]> {\n const rows = await db\n .selectFrom(\"fleet_events\")\n .selectAll()\n .where(\"ts\", \">=\", opts.sinceIso)\n .orderBy(\"ts\", \"desc\")\n .limit(opts.limit)\n .execute();\n return rows.map(rowToEvent);\n}\n\n/** Retention prune: delete events strictly older than `beforeIso`. */\nexport async function pruneFleetEvents(db: Db, beforeIso: string): Promise<void> {\n await db.deleteFrom(\"fleet_events\").where(\"ts\", \"<\", beforeIso).execute();\n}\n","import { openDb, readDbConfig, type Db } from \"../db/client.js\";\nimport { recordFleetEvent, pruneFleetEvents, type FleetEvent } from \"../db/fleet-events.js\";\n\nconst RETENTION_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** Persist a batch of fleet events + prune the 30-day window — best-effort.\n * Opens its OWN libSQL connection (the producers carry only Airtable creds today).\n * A missing TURSO_* (creds not yet added to the workflow) or any write error is\n * swallowed with a console.error: recording fleet activity must NEVER fail the\n * sweep that produced it. The feed ships dark until the creds are present.\n * `open` is injectable for tests. */\nexport async function recordFleetEventsBestEffort(\n events: FleetEvent[],\n now: Date,\n open: () => Promise<Db> = () => openDb(readDbConfig()),\n): Promise<void> {\n if (events.length === 0) return;\n let db: Db;\n try {\n db = await open();\n } catch (e) {\n console.error(`[fleet-events] skipped ${events.length} event(s): no libSQL (${String(e)})`);\n return;\n }\n try {\n for (const e of events) await recordFleetEvent(db, e);\n await pruneFleetEvents(db, new Date(now.getTime() - RETENTION_MS).toISOString());\n } catch (e) {\n console.error(`[fleet-events] write failed: ${String(e)}`);\n }\n}\n"],"mappings":";;;;;;AAmDA,eAAsB,iBAAiB,IAAQ,GAA8B;AAC3E,QAAM,GACH,WAAW,cAAc,EACzB,OAAO;AAAA,IACN,IAAI,EAAE;AAAA,IACN,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,SAAS,EAAE;AAAA,IACX,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,MAAM,EAAE,QAAQ,OAAO,OAAO,KAAK,UAAU,EAAE,IAAI;AAAA,IACnD,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,CAAC,EACA,WAAW,CAAC,OAAO,GAAG,OAAO,IAAI,EAAE,UAAU,CAAC,EAC9C,QAAQ;AACb;AAkBA,eAAsB,iBAAiB,IAAQ,WAAkC;AAC/E,QAAM,GAAG,WAAW,cAAc,EAAE,MAAM,MAAM,KAAK,SAAS,EAAE,QAAQ;AAC1E;;;ACnFA,IAAM,eAAe,KAAK,KAAK,KAAK,KAAK;AAQzC,eAAsB,4BACpB,QACA,KACA,OAA0B,MAAM,OAAO,aAAa,CAAC,GACtC;AACf,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI;AACJ,MAAI;AACF,SAAK,MAAM,KAAK;AAAA,EAClB,SAAS,GAAG;AACV,YAAQ,MAAM,0BAA0B,OAAO,MAAM,yBAAyB,OAAO,CAAC,CAAC,GAAG;AAC1F;AAAA,EACF;AACA,MAAI;AACF,eAAW,KAAK,OAAQ,OAAM,iBAAiB,IAAI,CAAC;AACpD,UAAM,iBAAiB,IAAI,IAAI,KAAK,IAAI,QAAQ,IAAI,YAAY,EAAE,YAAY,CAAC;AAAA,EACjF,SAAS,GAAG;AACV,YAAQ,MAAM,gCAAgC,OAAO,CAAC,CAAC,EAAE;AAAA,EAC3D;AACF;","names":[]}
@@ -1,10 +1,10 @@
1
- import {
2
- withRecipe
3
- } from "./chunk-XKOZUVK2.js";
4
1
  import {
5
2
  ALL_TEMPLATES,
6
3
  templatesByName
7
4
  } from "./chunk-CASCWQQX.js";
5
+ import {
6
+ withRecipe
7
+ } from "./chunk-XKOZUVK2.js";
8
8
  import {
9
9
  listTrackedFiles,
10
10
  removeFromIndex
@@ -208,4 +208,4 @@ export {
208
208
  isConfigName,
209
209
  syncConfigs
210
210
  };
211
- //# sourceMappingURL=chunk-XVR7UM6Z.js.map
211
+ //# sourceMappingURL=chunk-JNXCDUYB.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  approveBlockers
3
- } from "./chunk-MAYHLXCS.js";
3
+ } from "./chunk-VR246V3G.js";
4
4
  import {
5
5
  siteSlug
6
- } from "./chunk-C3PDS623.js";
6
+ } from "./chunk-YWRQEDB3.js";
7
7
 
8
8
  // src/alerts/digest-collectors.ts
9
9
  function dashboardUrl(baseUrl, siteName) {
@@ -248,4 +248,4 @@ export {
248
248
  readDigestState,
249
249
  writeDigestState
250
250
  };
251
- //# sourceMappingURL=chunk-2E2X5BCG.js.map
251
+ //# sourceMappingURL=chunk-KOAGMTRI.js.map
@@ -0,0 +1,17 @@
1
+ // src/recipes/_prettier.ts
2
+ var PRETTIER_FLAG_NOTE = "could not prettier-format written files (prettier unavailable?) \u2014 verify CI formatting";
3
+ async function formatWithPrettier(spawn, cwd, relPaths) {
4
+ if (relPaths.length === 0) return true;
5
+ try {
6
+ const res = await spawn("pnpm", ["exec", "prettier", "--write", ...relPaths], { cwd });
7
+ return res.code === 0;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
13
+ export {
14
+ PRETTIER_FLAG_NOTE,
15
+ formatWithPrettier
16
+ };
17
+ //# sourceMappingURL=chunk-KRDLHAKK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recipes/_prettier.ts"],"sourcesContent":["import type { SpawnFn } from \"../audits/util/spawn.js\";\n\n/** Flag note recipes surface when best-effort formatting couldn't run, so the\n * operator knows to eyeball CI's format check for that site. */\nexport const PRETTIER_FLAG_NOTE =\n \"could not prettier-format written files (prettier unavailable?) — verify CI formatting\";\n\n/**\n * Format recipe-written files with the SITE's own prettier\n * (`pnpm exec prettier --write`), so they match that site's config — quote\n * style, tabs vs spaces, and printWidth all vary across the fleet, and a single\n * hardcoded template style reds the CI format check on the sites that differ.\n *\n * Best-effort by contract: returns `false` (never throws) when prettier isn't\n * installed or exits non-zero, so a recipe can commit the file unformatted and\n * flag it rather than fail the whole rollout. Files in the site's\n * `.prettierignore` are skipped by prettier itself. Returns `true` immediately\n * (no spawn) when `relPaths` is empty, so a recipe that wrote nothing this run\n * doesn't shell out.\n *\n * Only pass paths the recipe actually wrote/changed — never operator files it\n * left untouched, which must not be reformatted.\n */\nexport async function formatWithPrettier(\n spawn: SpawnFn,\n cwd: string,\n relPaths: readonly string[],\n): Promise<boolean> {\n if (relPaths.length === 0) return true;\n try {\n const res = await spawn(\"pnpm\", [\"exec\", \"prettier\", \"--write\", ...relPaths], { cwd });\n return res.code === 0;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAIO,IAAM,qBACX;AAkBF,eAAsB,mBACpB,OACA,KACA,UACkB;AAClB,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,QAAQ,CAAC,QAAQ,YAAY,WAAW,GAAG,QAAQ,GAAG,EAAE,IAAI,CAAC;AACrF,WAAO,IAAI,SAAS;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -5,6 +5,8 @@ var a11yRoutes = [
5
5
  { path: "/dev/animate-in", name: "animate-in demo" }
6
6
  ];
7
7
  var smokeRoutes = [{ path: "/", name: "home" }];
8
+ var smokePort = process.env.REDDOOR_SMOKE_PORT;
9
+ var port = smokePort || "5173";
8
10
  var playwrightA11yConfig = defineConfig({
9
11
  testDir: "tests",
10
12
  testMatch: /.*\.spec\.ts$/,
@@ -13,7 +15,7 @@ var playwrightA11yConfig = defineConfig({
13
15
  retries: process.env.CI ? 2 : 0,
14
16
  reporter: process.env.CI ? "github" : "list",
15
17
  use: {
16
- baseURL: "http://localhost:5173",
18
+ baseURL: `http://localhost:${port}`,
17
19
  trace: "on-first-retry"
18
20
  },
19
21
  projects: [
@@ -24,8 +26,10 @@ var playwrightA11yConfig = defineConfig({
24
26
  ],
25
27
  webServer: {
26
28
  // Portable across pnpm and npm sites — pnpm respects `npm run` too.
27
- command: "npm run vite:dev",
28
- url: "http://localhost:5173/dev/a11y-fixtures",
29
+ // `--strictPort` only when a port was explicitly allocated: fail loudly
30
+ // rather than let vite drift to a free port the baseURL doesn't point at.
31
+ command: smokePort ? `npm run vite:dev -- --port ${smokePort} --strictPort` : "npm run vite:dev",
32
+ url: `http://localhost:${port}/dev/a11y-fixtures`,
29
33
  reuseExistingServer: !process.env.CI,
30
34
  timeout: 12e4
31
35
  }
@@ -38,4 +42,4 @@ export {
38
42
  playwrightA11yConfig,
39
43
  playwright_a11y_default
40
44
  };
41
- //# sourceMappingURL=chunk-LYCKN7ZR.js.map
45
+ //# sourceMappingURL=chunk-LPMQXS2N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/configs/playwright-a11y.ts"],"sourcesContent":["import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\n// Routes smoke-loaded for client-side (hydration) errors only — NOT axe-scanned.\n// Catches the class of bug where build + SSR succeed but client hydration throws\n// and blanks the page (data-dynamiq 2026-06-09: a Svelte 4->5 `run()` referenced\n// a `$state` declared after it → TDZ ReferenceError on hydrate). `/` is the one\n// route every site has; real routes carry a11y debt we don't gate on here, so we\n// assert only that they don't crash on hydrate.\nexport const smokeRoutes: A11yRoute[] = [{ path: \"/\", name: \"home\" }];\n\n// R1.1 (health-gate): the central `smoke` audit (src/audits/smoke.ts) allocates\n// a free port and passes it as REDDOOR_SMOKE_PORT so a zombie vite already\n// squatting the default 5173 can't silently hijack the run and green a stale\n// build. The per-site R1.1 config template honors it, but sites whose\n// playwright.config.ts merely re-exports this shared base (pre-R1.1 adopters\n// the smoke-suite recipe flags-but-never-rewrites) would otherwise ignore it —\n// so honor it here too and every re-exporter inherits the port binding on its\n// next package bump. Unset (local `pnpm test:smoke`) → the fixed 5173.\nconst smokePort = process.env.REDDOOR_SMOKE_PORT;\nconst port = smokePort || \"5173\";\n\nexport const playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: `http://localhost:${port}`,\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n // `--strictPort` only when a port was explicitly allocated: fail loudly\n // rather than let vite drift to a free port the baseURL doesn't point at.\n command: smokePort\n ? `npm run vite:dev -- --port ${smokePort} --strictPort`\n : \"npm run vite:dev\",\n url: `http://localhost:${port}/dev/a11y-fixtures`,\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n"],"mappings":";AAAA,SAAS,cAAc,eAA0C;AAI1D,IAAM,aAA0B;AAAA,EACrC,EAAE,MAAM,sBAAsB,MAAM,gBAAgB;AAAA,EACpD,EAAE,MAAM,mBAAmB,MAAM,kBAAkB;AACrD;AAQO,IAAM,cAA2B,CAAC,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AAUpE,IAAM,YAAY,QAAQ,IAAI;AAC9B,IAAM,OAAO,aAAa;AAEnB,IAAM,uBAA6C,aAAa;AAAA,EACrE,SAAS;AAAA,EACT,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY,CAAC,CAAC,QAAQ,IAAI;AAAA,EAC1B,SAAS,QAAQ,IAAI,KAAK,IAAI;AAAA,EAC9B,UAAU,QAAQ,IAAI,KAAK,WAAW;AAAA,EACtC,KAAK;AAAA,IACH,SAAS,oBAAoB,IAAI;AAAA,IACjC,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,MAAM;AAAA,MACN,KAAK,EAAE,GAAG,QAAQ,gBAAgB,EAAE;AAAA,IACtC;AAAA,EACF;AAAA,EACA,WAAW;AAAA;AAAA;AAAA;AAAA,IAIT,SAAS,YACL,8BAA8B,SAAS,kBACvC;AAAA,IACJ,KAAK,oBAAoB,IAAI;AAAA,IAC7B,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS;AAAA,EACX;AACF,CAAC;AAED,IAAO,0BAAQ;","names":[]}