@reddoorla/maintenance 0.64.0 → 0.65.1

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 (105) hide show
  1. package/README.md +17 -10
  2. package/dist/airtable-GGZJUGQB.js +9 -0
  3. package/dist/{announce-QEON4O7E.js → announce-BRBITWUP.js} +24 -9
  4. package/dist/announce-BRBITWUP.js.map +1 -0
  5. package/dist/{bump-deps-2U6GIHM5.js → bump-deps-4BODIOEF.js} +6 -4
  6. package/dist/bump-deps-4BODIOEF.js.map +1 -0
  7. package/dist/{chunk-BMDBOPN3.js → chunk-4A5PYCGB.js} +2 -2
  8. package/dist/{chunk-BLD6AYJO.js → chunk-7RGQ4KLE.js} +5 -2
  9. package/dist/chunk-7RGQ4KLE.js.map +1 -0
  10. package/dist/{chunk-YYUY2OVP.js → chunk-CSTLFSGM.js} +2 -2
  11. package/dist/{chunk-SAOCVNX7.js → chunk-FH7WQHD7.js} +2 -2
  12. package/dist/{chunk-L5F664ZA.js → chunk-G2YRXV26.js} +2 -2
  13. package/dist/chunk-GASBX52O.js +12 -0
  14. package/dist/chunk-GASBX52O.js.map +1 -0
  15. package/dist/{chunk-3HM4S422.js → chunk-IRRZG66U.js} +18 -18
  16. package/dist/chunk-IRRZG66U.js.map +1 -0
  17. package/dist/{chunk-4REUHZAW.js → chunk-LM42CATB.js} +3 -3
  18. package/dist/{chunk-ZEX2T6JH.js → chunk-MQM5YP4J.js} +2 -2
  19. package/dist/{chunk-U3MF6RCS.js → chunk-PBDZGBGL.js} +11 -7
  20. package/dist/chunk-PBDZGBGL.js.map +1 -0
  21. package/dist/{chunk-6ZKM7OIJ.js → chunk-PTPTVVY7.js} +6 -4
  22. package/dist/chunk-PTPTVVY7.js.map +1 -0
  23. package/dist/{chunk-NPJFHBAX.js → chunk-SWSFB6IV.js} +3 -3
  24. package/dist/{chunk-KTPPKO2C.js → chunk-UCXZYS2W.js} +2 -2
  25. package/dist/chunk-VPJ7N6P7.js +27 -0
  26. package/dist/chunk-VPJ7N6P7.js.map +1 -0
  27. package/dist/{chunk-SS7BKOIX.js → chunk-WCN54744.js} +265 -15
  28. package/dist/chunk-WCN54744.js.map +1 -0
  29. package/dist/{chunk-P7VJM46I.js → chunk-XGT3I5AK.js} +4 -4
  30. package/dist/{chunk-J37CZORZ.js → chunk-XUKSP67U.js} +4 -4
  31. package/dist/{chunk-OSRFDGQM.js → chunk-YEGHUODU.js} +18 -1
  32. package/dist/chunk-YEGHUODU.js.map +1 -0
  33. package/dist/cli/bin.js +16 -16
  34. package/dist/cli/bin.js.map +1 -1
  35. package/dist/cli/commands/audit.js +10 -8
  36. package/dist/cli/commands/audit.js.map +1 -1
  37. package/dist/{client-GTMJTXBK.js → client-C2GOZD2D.js} +2 -2
  38. package/dist/{convert-to-pnpm-JG3767ZQ.js → convert-to-pnpm-S4TNJD53.js} +6 -4
  39. package/dist/convert-to-pnpm-S4TNJD53.js.map +1 -0
  40. package/dist/{db-Y6BVTNH7.js → db-YBLQR2YY.js} +2 -2
  41. package/dist/{digest-ABJROO4H.js → digest-R3ROSGLP.js} +3 -3
  42. package/dist/{github-signals-XTJLKOXJ.js → github-signals-R4FNU34K.js} +9 -9
  43. package/dist/index.d.ts +64 -10
  44. package/dist/index.js +18 -11
  45. package/dist/index.js.map +1 -1
  46. package/dist/{init-DMAIKUQE.js → init-GLT5C6S5.js} +25 -6
  47. package/dist/init-GLT5C6S5.js.map +1 -0
  48. package/dist/{launch-IBP23PNY.js → launch-CQM5D6QJ.js} +10 -9
  49. package/dist/{launch-IBP23PNY.js.map → launch-CQM5D6QJ.js.map} +1 -1
  50. package/dist/{lighthouse-airtable-HTEW6BBT.js → lighthouse-airtable-5FHELRCA.js} +3 -3
  51. package/dist/{onboard-TTIC3YZI.js → onboard-ILC3DWY4.js} +10 -6
  52. package/dist/onboard-ILC3DWY4.js.map +1 -0
  53. package/dist/{orchestrate-CYYZ737H.js → orchestrate-45URUFEK.js} +6 -6
  54. package/dist/{renovate-dispatch-QDDMTATD.js → renovate-dispatch-G4T73IUR.js} +3 -3
  55. package/dist/{report-QNE7HFOZ.js → report-BZ6QNFNM.js} +6 -6
  56. package/dist/{self-updating-2RLUP4AA.js → self-updating-DEZNP5OK.js} +9 -6
  57. package/dist/self-updating-DEZNP5OK.js.map +1 -0
  58. package/dist/{selftest-5ELLRH6Q.js → selftest-TY7X5ARI.js} +8 -8
  59. package/dist/{svelte-codemods-BPLKSVIA.js → svelte-codemods-DMLMOKSD.js} +6 -4
  60. package/dist/svelte-codemods-DMLMOKSD.js.map +1 -0
  61. package/dist/{sync-configs-VFRV3TJ4.js → sync-configs-DWKXETFO.js} +10 -4
  62. package/dist/sync-configs-DWKXETFO.js.map +1 -0
  63. package/dist/{upgrade-WCFD2TRN.js → upgrade-RUX2ODMW.js} +6 -8
  64. package/dist/upgrade-RUX2ODMW.js.map +1 -0
  65. package/dist/{websites-HM2NMZMR.js → websites-CSVLSEVL.js} +2 -2
  66. package/dist/{write-audits-to-airtable-QF26PCAR.js → write-audits-to-airtable-JCV7FYAW.js} +4 -4
  67. package/package.json +1 -1
  68. package/dist/airtable-HHAWHBNM.js +0 -9
  69. package/dist/announce-QEON4O7E.js.map +0 -1
  70. package/dist/bump-deps-2U6GIHM5.js.map +0 -1
  71. package/dist/chunk-3HM4S422.js.map +0 -1
  72. package/dist/chunk-6ZKM7OIJ.js.map +0 -1
  73. package/dist/chunk-BLD6AYJO.js.map +0 -1
  74. package/dist/chunk-OSRFDGQM.js.map +0 -1
  75. package/dist/chunk-SS7BKOIX.js.map +0 -1
  76. package/dist/chunk-U3MF6RCS.js.map +0 -1
  77. package/dist/convert-to-pnpm-JG3767ZQ.js.map +0 -1
  78. package/dist/init-DMAIKUQE.js.map +0 -1
  79. package/dist/onboard-TTIC3YZI.js.map +0 -1
  80. package/dist/self-updating-2RLUP4AA.js.map +0 -1
  81. package/dist/svelte-codemods-BPLKSVIA.js.map +0 -1
  82. package/dist/sync-configs-VFRV3TJ4.js.map +0 -1
  83. package/dist/upgrade-WCFD2TRN.js.map +0 -1
  84. /package/dist/{airtable-HHAWHBNM.js.map → airtable-GGZJUGQB.js.map} +0 -0
  85. /package/dist/{chunk-BMDBOPN3.js.map → chunk-4A5PYCGB.js.map} +0 -0
  86. /package/dist/{chunk-YYUY2OVP.js.map → chunk-CSTLFSGM.js.map} +0 -0
  87. /package/dist/{chunk-SAOCVNX7.js.map → chunk-FH7WQHD7.js.map} +0 -0
  88. /package/dist/{chunk-L5F664ZA.js.map → chunk-G2YRXV26.js.map} +0 -0
  89. /package/dist/{chunk-4REUHZAW.js.map → chunk-LM42CATB.js.map} +0 -0
  90. /package/dist/{chunk-ZEX2T6JH.js.map → chunk-MQM5YP4J.js.map} +0 -0
  91. /package/dist/{chunk-NPJFHBAX.js.map → chunk-SWSFB6IV.js.map} +0 -0
  92. /package/dist/{chunk-KTPPKO2C.js.map → chunk-UCXZYS2W.js.map} +0 -0
  93. /package/dist/{chunk-P7VJM46I.js.map → chunk-XGT3I5AK.js.map} +0 -0
  94. /package/dist/{chunk-J37CZORZ.js.map → chunk-XUKSP67U.js.map} +0 -0
  95. /package/dist/{client-GTMJTXBK.js.map → client-C2GOZD2D.js.map} +0 -0
  96. /package/dist/{db-Y6BVTNH7.js.map → db-YBLQR2YY.js.map} +0 -0
  97. /package/dist/{digest-ABJROO4H.js.map → digest-R3ROSGLP.js.map} +0 -0
  98. /package/dist/{github-signals-XTJLKOXJ.js.map → github-signals-R4FNU34K.js.map} +0 -0
  99. /package/dist/{lighthouse-airtable-HTEW6BBT.js.map → lighthouse-airtable-5FHELRCA.js.map} +0 -0
  100. /package/dist/{orchestrate-CYYZ737H.js.map → orchestrate-45URUFEK.js.map} +0 -0
  101. /package/dist/{renovate-dispatch-QDDMTATD.js.map → renovate-dispatch-G4T73IUR.js.map} +0 -0
  102. /package/dist/{report-QNE7HFOZ.js.map → report-BZ6QNFNM.js.map} +0 -0
  103. /package/dist/{selftest-5ELLRH6Q.js.map → selftest-TY7X5ARI.js.map} +0 -0
  104. /package/dist/{websites-HM2NMZMR.js.map → websites-CSVLSEVL.js.map} +0 -0
  105. /package/dist/{write-audits-to-airtable-QF26PCAR.js.map → write-audits-to-airtable-JCV7FYAW.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { cac } from \"cac\";\nimport type { AuditName, RecipeName } from \"../types.js\";\nimport { loadCredentialsIntoEnv } from \"../util/credentials.js\";\nimport { resolvePackageVersion } from \"./version.js\";\n\n// Command modules are loaded LAZILY (dynamic `import()` inside each `.action()`),\n// never eagerly at the top. An eager `import { runReportCommand } from\n// \"./commands/report.js\"` would pull EVERY command's transitive dependency chain\n// into the CLI's startup graph — report/announce/launch drag in mjml + resend +\n// @google-analytics/data, `db` drags in the libSQL/kysely stack, etc. Those heavy\n// packages are `devDependencies` (this repo's CLI/functions/audits use them); a\n// consuming fleet site installs @reddoorla/maintenance only for `./forms` +\n// `./configs/*` and runs just `reddoor-maint audit --only a11y` in CI. Lazy\n// loading keeps that path (and `--help`/`--version`) free of the report/db\n// chains, so those packages never land in a consumer's node_modules. The\n// smoke-dist gate asserts bin.js's STATIC import closure stays free of them.\n\n// Load credentials from ~/.config/reddoor-maint/credentials.env before any\n// command runs, so AIRTABLE_PAT/AIRTABLE_BASE_ID/RESEND_API_KEY/etc. are\n// available from any cwd. Shell-exported env vars still win. Silent on\n// missing file — commands that need the credentials will fail with their\n// own clear error.\nloadCredentialsIntoEnv();\n\nconst here = dirname(fileURLToPath(import.meta.url));\nconst version = resolvePackageVersion(here);\n\nconst AUDIT_DESCRIPTIONS: Record<AuditName, string> = {\n deps: \"Diff site package.json against the bundled baseline version map.\",\n lighthouse: \"Run @lhci/cli autorun using the canonical lighthouserc.\",\n a11y: \"Playwright + axe against the canonical a11y routes.\",\n security: \"pnpm audit (falls back to npm audit), prod-deps by default.\",\n lint: \"ESLint + Prettier using the canonical configs.\",\n domain: \"DNS resolve + TLS cert expiry against the deployed URL (checkout-free).\",\n browser:\n \"Playwright across desktop engines + mobile devices + link-check against the deployed URL (checkout-free).\",\n \"netlify-deploy\":\n \"Latest production deploy health via the Netlify API by site id (checkout-free; needs NETLIFY_PAT).\",\n};\n\nconst RECIPE_DESCRIPTIONS: Record<RecipeName, string> = {\n \"sync-configs\": \"Overwrite a site's canonical configs to match @reddoorla/maintenance.\",\n \"bump-deps\": \"Bump dependencies and commit the lockfile change.\",\n \"svelte-4-to-5\": \"Run the 7-commit Svelte 4 → 5 upgrade recipe.\",\n \"svelte-codemods\":\n \"Apply Svelte 5 gotcha codemods to an already-migrated site (state_referenced_locally, etc.).\",\n \"convert-to-pnpm\": \"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).\",\n onboard: \"Install @reddoorla/maintenance + audit deps on a site (preferred first step).\",\n \"a11y-fixtures-page\":\n \"Write src/routes/dev/a11y-fixtures/+page.svelte (stub for lhci + axe targets).\",\n \"self-updating\":\n \"Bootstrap CI + Renovate + auto-merge per repo (writes workflows, opens PR, sets RENOVATE_TOKEN).\",\n init: \"Run the full onboarding chain (convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit).\",\n};\n\n/** Run a command thunk and surface its result, falling back to a clean error\n * message on throw. Wraps the ~10-line try/catch every `.action()` used to\n * duplicate. `verbose` flips between full stack and message-only.\n *\n * On success it sets `process.exitCode` and RETURNS rather than calling\n * `process.exit()` right after `console.log()`. `process.exit()` does not wait\n * for stdout to flush when stdout is a pipe, so a large `--json` payload piped\n * to another process would get truncated mid-write. Setting `exitCode` and\n * returning lets Node drain stdout and exit naturally with the right code. A\n * non-zero `code` still yields a non-zero process exit.\n *\n * The error path keeps `process.exit()` — error messages are small (one line),\n * always go to stderr, and exiting immediately is the desired fail-fast. */\nexport async function runOrExit(\n fn: () => Promise<{ output: string; code: number }>,\n opts: { verbose?: boolean },\n): Promise<void> {\n try {\n const { output, code } = await fn();\n console.log(output);\n process.exitCode = code;\n return;\n } catch (err) {\n const e = err as { exitCode?: number; message?: string; stack?: string };\n console.error(opts.verbose ? (e.stack ?? e.message) : (e.message ?? String(err)));\n process.exit(e.exitCode ?? 1);\n }\n}\n\nconst cli = cac(\"reddoor-maint\");\n\ncli.option(\"--cwd <path>\", \"Override working directory (default: process.cwd())\");\ncli.option(\"--verbose\", \"Verbose output (full stack on errors)\");\n\ncli.command(\"list-audits\", \"Print the available audits.\").action(() => {\n for (const [name, desc] of Object.entries(AUDIT_DESCRIPTIONS)) {\n console.log(`${name.padEnd(12)} ${desc}`);\n }\n});\n\ncli.command(\"list-recipes\", \"Print the available recipes.\").action(() => {\n for (const [name, desc] of Object.entries(RECIPE_DESCRIPTIONS)) {\n console.log(`${name.padEnd(16)} ${desc}`);\n }\n});\n\ncli\n .command(\"audit [site]\", \"Run audits against a site (default: cwd).\")\n .option(\"--only <names>\", \"Comma-separated audit names (e.g. deps,lighthouse)\")\n .option(\"--json\", \"Machine-readable JSON output\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .option(\n \"--write-airtable [slug]\",\n \"After lighthouse runs, write pScore/rScore/bpScore/seoScore + timestamp to the matching Websites row. Slug defaults to cwd's package.json#name.\",\n )\n .option(\"--fail-on-violations\", \"Exit non-zero if any a11y violations are found (for CI gates)\")\n .option(\n \"--url <url>\",\n \"Audit this deployed URL with lighthouse (no dev server); single-site. Pair with --only lighthouse — other audits still use the local checkout.\",\n )\n .option(\n \"--concurrency <n>\",\n \"Max sites to audit in parallel in --fleet mode (default: all at once). Use 1 for sequential (CI).\",\n )\n .action(\n async (\n site,\n opts: {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n writeAirtable?: string | boolean;\n failOnViolations?: boolean;\n url?: string;\n concurrency?: string;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/audit.js\")).runAuditCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"sync-configs [site]\", \"Sync canonical configs into a site.\")\n .option(\"--only <names>\", \"Comma-separated config names (e.g. eslint,prettier)\")\n .option(\"--dry\", \"Print diff without writing\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n only?: string;\n dry?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/sync-configs.js\")).runSyncConfigsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"bump-deps [site]\", \"Bump dependencies.\")\n .option(\"--group <group>\", \"patch | minor | major\", { default: \"minor\" })\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n group?: string;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/bump-deps.js\")).runBumpDepsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"self-updating [site]\",\n \"Bootstrap a repo to keep itself updated (CI + Renovate + auto-merge).\",\n )\n .option(\"--dry\", \"List what would be enabled without writing or opening PRs\")\n .option(\"--fleet <inventory>\", 'Inventory file (.json or .mjs/.js), or \"airtable\"')\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n dry?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () =>\n (await import(\"./commands/self-updating.js\")).runSelfUpdatingCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"upgrade <upgrade> [site]\", \"Run a named upgrade recipe (svelte-4-to-5).\")\n .example(\"reddoor-maint upgrade svelte-4-to-5 ./my-site\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n upgrade: string,\n site: string | undefined,\n opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/upgrade.js\")).runUpgradeCommand(upgrade, site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"convert-to-pnpm [site]\",\n \"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).\",\n )\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/convert-to-pnpm.js\")).runConvertToPnpmCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"svelte-codemods [site]\", \"Apply Svelte 5 gotcha codemods to an already-migrated site.\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/svelte-codemods.js\")).runSvelteCodemodsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"onboard [site]\",\n \"Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm).\",\n )\n .option(\"--audits <names>\", \"Comma-separated audit subset: lighthouse,a11y (default: both)\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n audits?: string;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/onboard.js\")).runOnboardCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"init [site]\",\n \"One-shot guided onboarding: convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit.\",\n )\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(async () => (await import(\"./commands/init.js\")).runInitCommand(site, opts), opts),\n );\n\ncli\n .command(\n \"launch <site>\",\n \"Bootstrap + first-audit a site, then draft its launch email for approval.\",\n )\n .action(async (site: string, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () => (await import(\"./commands/launch.js\")).runLaunchCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"announce [site]\",\n \"Draft the monthly-report announcement email for maintenance sites (all, or one) for approval.\",\n )\n .action(async (site: string | undefined, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () => (await import(\"./commands/announce.js\")).runAnnounceCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"selftest <kind> [site]\",\n \"Operator self-tests. kind=email: preview a report email for a site (or --all) to yourself.\",\n )\n .option(\"--type <type>\", \"Report type: announcement (default) | maintenance | testing | launch\")\n .option(\"--to <addr>\", \"Recipient(s), comma-separated. Default: OPERATOR_EMAIL.\")\n .option(\"--all\", \"Send a preview for every maintenance site (to --to/operator).\")\n .option(\"--dry-run\", \"Render only; write reports/<slug>/selftest-<type>.html; do not send.\")\n .action(\n async (\n kind: string,\n site: string | undefined,\n opts: {\n type?: string;\n to?: string;\n all?: boolean;\n dryRun?: boolean;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/selftest.js\")).runSelftestCommand(kind, site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"report [site]\", \"Draft or send maintenance/testing reports.\")\n .option(\"--due\", \"Scan all Websites and draft overdue reports.\")\n .option(\"--type <type>\", \"Single-site draft report type: Maintenance (default) or Testing.\")\n .option(\n \"--preview\",\n \"Single-site dry run; writes reports/<slug>/draft.html, never touches Airtable.\",\n )\n .option(\n \"--send-ready\",\n \"Send all Reports with Draft ready=true AND Approved to send=true AND Sent at IS NULL.\",\n )\n .option(\n \"--digest\",\n \"Email the operator one daily digest of reports ready for approval (skips when empty).\",\n )\n .action(\n async (\n site,\n opts: {\n due?: boolean;\n type?: string;\n preview?: boolean;\n sendReady?: boolean;\n digest?: boolean;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/report.js\")).runReportCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"github-signals\",\n \"Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write Airtable.\",\n )\n .option(\"--fleet\", \"Run across every site in the Airtable inventory.\")\n .option(\"--write-airtable\", \"Write each site's signals back to its Websites row.\")\n .action(\n async (opts: { fleet?: boolean; writeAirtable?: boolean; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/github-signals.js\")).runGitHubSignalsCommand({\n fleet: opts.fleet,\n writeAirtable: opts.writeAirtable,\n }),\n opts,\n ),\n );\n\ncli\n .command(\n \"db <action>\",\n \"Migrate / backfill / reconcile the libSQL store (migrate | backfill | reconcile).\",\n )\n .action(async (action: string, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(async () => (await import(\"./commands/db.js\")).runDbCommand(action, opts), opts),\n );\n\ncli\n .command(\n \"renovate-dispatch\",\n \"Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns.\",\n )\n .option(\"--fleet\", \"Run across every active, repo-backed site in the Airtable inventory.\")\n .action(async (opts: { fleet?: boolean; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/renovate-dispatch.js\")).runRenovateDispatchCommand({\n fleet: opts.fleet,\n }),\n opts,\n ),\n );\n\ncli.help();\ncli.version(version);\n\n// A typo'd / unrecognized subcommand (e.g. `reddoor-maint auditt`) otherwise\n// falls through cac with no matched command and exits 0 — a cron/CI typo would\n// \"succeed\" silently. cac emits `command:*` at the end of parse() exactly when\n// a positional arg was given but matched no command (a bare `reddoor-maint`,\n// `--help`, and `--version` do NOT trigger it: they have no leading positional\n// or are handled before this fires). Turn that into a clear stderr error +\n// non-zero exit. process.argv[2] is the first positional, i.e. the bad command.\ncli.on(\"command:*\", () => {\n const unknown = cli.args[0] ?? process.argv[2] ?? \"\";\n console.error(\n `error: unknown command '${unknown}'. Run 'reddoor-maint --help' to see available commands.`,\n );\n process.exit(1);\n});\n\ncli.parse();\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Read the @reddoorla/maintenance package version, given the directory of a\n * file that lives inside the package. Walks UP looking for the first\n * `package.json` whose `name` matches. Defensive against bundling-layout\n * changes — the older \"two levels up\" assumption held for `dist/cli/bin.js`\n * but would silently mis-read (or return \"unknown\") if tsup ever moved bin\n * to a different depth. Same walk-up pattern as the self-version helper.\n *\n * Returns \"unknown\" when no matching package.json is reachable (Yarn PnP\n * setups stash manifests inside .zip caches; the readFileSync there fails\n * before any name check).\n */\nexport function resolvePackageVersion(fromDir: string): string {\n try {\n let dir = fromDir;\n while (true) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n const raw = readFileSync(candidate, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n if (pkg.name === \"@reddoorla/maintenance\") {\n return pkg.version ?? \"unknown\";\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return \"unknown\";\n dir = parent;\n }\n } catch {\n return \"unknown\";\n }\n}\n"],"mappings":"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","names":["dirname","dirname"]}
1
+ {"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { cac } from \"cac\";\nimport type { AuditName, RecipeName } from \"../types.js\";\nimport { loadCredentialsIntoEnv } from \"../util/credentials.js\";\nimport { resolvePackageVersion } from \"./version.js\";\n\n// Command modules are loaded LAZILY (dynamic `import()` inside each `.action()`),\n// never eagerly at the top. An eager `import { runReportCommand } from\n// \"./commands/report.js\"` would pull EVERY command's transitive dependency chain\n// into the CLI's startup graph — report/announce/launch drag in mjml + resend +\n// @google-analytics/data, `db` drags in the libSQL/kysely stack, etc. Those heavy\n// packages are `devDependencies` (this repo's CLI/functions/audits use them); a\n// consuming fleet site installs @reddoorla/maintenance only for `./forms` +\n// `./configs/*` and runs just `reddoor-maint audit --only a11y` in CI. Lazy\n// loading keeps that path (and `--help`/`--version`) free of the report/db\n// chains, so those packages never land in a consumer's node_modules. The\n// smoke-dist gate asserts bin.js's STATIC import closure stays free of them.\n\n// Load credentials from ~/.config/reddoor-maint/credentials.env before any\n// command runs, so AIRTABLE_PAT/AIRTABLE_BASE_ID/RESEND_API_KEY/etc. are\n// available from any cwd. Shell-exported env vars still win. Silent on\n// missing file — commands that need the credentials will fail with their\n// own clear error.\nloadCredentialsIntoEnv();\n\nconst here = dirname(fileURLToPath(import.meta.url));\nconst version = resolvePackageVersion(here);\n\nconst AUDIT_DESCRIPTIONS: Record<AuditName, string> = {\n deps: \"Diff site package.json against the bundled baseline version map.\",\n lighthouse: \"Run @lhci/cli autorun using the canonical lighthouserc.\",\n a11y: \"Playwright + axe against the canonical a11y routes.\",\n security:\n \"GitHub Dependabot alerts (prod+dev) for repo-backed sites; falls back to pnpm/npm audit (prod-deps) with no repo/token.\",\n lint: \"ESLint + Prettier using the canonical configs.\",\n domain: \"DNS resolve + TLS cert expiry against the deployed URL (checkout-free).\",\n browser:\n \"Playwright across desktop engines + mobile devices + link-check against the deployed URL (checkout-free).\",\n \"netlify-deploy\":\n \"Latest production deploy health via the Netlify API by site id (checkout-free; needs NETLIFY_PAT).\",\n};\n\nconst RECIPE_DESCRIPTIONS: Record<RecipeName, string> = {\n \"sync-configs\": \"Overwrite a site's canonical configs to match @reddoorla/maintenance.\",\n \"bump-deps\": \"Bump dependencies and commit the lockfile change.\",\n \"svelte-4-to-5\": \"Run the 7-commit Svelte 4 → 5 upgrade recipe.\",\n \"svelte-codemods\":\n \"Apply Svelte 5 gotcha codemods to an already-migrated site (state_referenced_locally, etc.).\",\n \"convert-to-pnpm\": \"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).\",\n onboard: \"Install @reddoorla/maintenance + audit deps on a site (preferred first step).\",\n \"a11y-fixtures-page\":\n \"Write src/routes/dev/a11y-fixtures/+page.svelte (stub for lhci + axe targets).\",\n \"self-updating\":\n \"Bootstrap CI + Renovate + auto-merge per repo (writes workflows, opens PR, sets RENOVATE_TOKEN).\",\n init: \"Run the full onboarding chain (convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit).\",\n};\n\n/** Run a command thunk and surface its result, falling back to a clean error\n * message on throw. Wraps the ~10-line try/catch every `.action()` used to\n * duplicate. `verbose` flips between full stack and message-only.\n *\n * On success it sets `process.exitCode` and RETURNS rather than calling\n * `process.exit()` right after `console.log()`. `process.exit()` does not wait\n * for stdout to flush when stdout is a pipe, so a large `--json` payload piped\n * to another process would get truncated mid-write. Setting `exitCode` and\n * returning lets Node drain stdout and exit naturally with the right code. A\n * non-zero `code` still yields a non-zero process exit.\n *\n * The error path keeps `process.exit()` — error messages are small (one line),\n * always go to stderr, and exiting immediately is the desired fail-fast. */\nexport async function runOrExit(\n fn: () => Promise<{ output: string; code: number }>,\n opts: { verbose?: boolean },\n): Promise<void> {\n try {\n const { output, code } = await fn();\n console.log(output);\n process.exitCode = code;\n return;\n } catch (err) {\n const e = err as { exitCode?: number; message?: string; stack?: string };\n console.error(opts.verbose ? (e.stack ?? e.message) : (e.message ?? String(err)));\n process.exit(e.exitCode ?? 1);\n }\n}\n\nconst cli = cac(\"reddoor-maint\");\n\ncli.option(\"--cwd <path>\", \"Override working directory (default: process.cwd())\");\ncli.option(\"--verbose\", \"Verbose output (full stack on errors)\");\n\ncli.command(\"list-audits\", \"Print the available audits.\").action(() => {\n for (const [name, desc] of Object.entries(AUDIT_DESCRIPTIONS)) {\n console.log(`${name.padEnd(12)} ${desc}`);\n }\n});\n\ncli.command(\"list-recipes\", \"Print the available recipes.\").action(() => {\n for (const [name, desc] of Object.entries(RECIPE_DESCRIPTIONS)) {\n console.log(`${name.padEnd(16)} ${desc}`);\n }\n});\n\ncli\n .command(\"audit [site]\", \"Run audits against a site (default: cwd).\")\n .option(\"--only <names>\", \"Comma-separated audit names (e.g. deps,lighthouse)\")\n .option(\"--json\", \"Machine-readable JSON output\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .option(\n \"--write-airtable [slug]\",\n \"After lighthouse runs, write pScore/rScore/bpScore/seoScore + timestamp to the matching Websites row. Slug defaults to cwd's package.json#name.\",\n )\n .option(\"--fail-on-violations\", \"Exit non-zero if any a11y violations are found (for CI gates)\")\n .option(\n \"--url <url>\",\n \"Audit this deployed URL with lighthouse (no dev server); single-site. Pair with --only lighthouse — other audits still use the local checkout.\",\n )\n .option(\n \"--concurrency <n>\",\n \"Max sites to audit in parallel in --fleet mode (default: all at once). Use 1 for sequential (CI).\",\n )\n .action(\n async (\n site,\n opts: {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n writeAirtable?: string | boolean;\n failOnViolations?: boolean;\n url?: string;\n concurrency?: string;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/audit.js\")).runAuditCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"sync-configs [site]\", \"Sync canonical configs into a site.\")\n .option(\"--only <names>\", \"Comma-separated config names (e.g. eslint,prettier)\")\n .option(\"--dry\", \"Print diff without writing\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n only?: string;\n dry?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/sync-configs.js\")).runSyncConfigsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"bump-deps [site]\", \"Bump dependencies.\")\n .option(\"--group <group>\", \"patch | minor | major\", { default: \"minor\" })\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n group?: string;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/bump-deps.js\")).runBumpDepsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"self-updating [site]\",\n \"Bootstrap a repo to keep itself updated (CI + Renovate + auto-merge).\",\n )\n .option(\"--dry\", \"List what would be enabled without writing or opening PRs\")\n .option(\"--fleet <inventory>\", 'Inventory file (.json or .mjs/.js), or \"airtable\"')\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n dry?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () =>\n (await import(\"./commands/self-updating.js\")).runSelfUpdatingCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"upgrade <upgrade> [site]\", \"Run a named upgrade recipe (svelte-4-to-5).\")\n .example(\"reddoor-maint upgrade svelte-4-to-5 ./my-site\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n upgrade: string,\n site: string | undefined,\n opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/upgrade.js\")).runUpgradeCommand(upgrade, site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"convert-to-pnpm [site]\",\n \"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).\",\n )\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/convert-to-pnpm.js\")).runConvertToPnpmCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"svelte-codemods [site]\", \"Apply Svelte 5 gotcha codemods to an already-migrated site.\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/svelte-codemods.js\")).runSvelteCodemodsCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"onboard [site]\",\n \"Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm).\",\n )\n .option(\"--audits <names>\", \"Comma-separated audit subset: lighthouse,a11y (default: both)\")\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (\n site,\n opts: {\n audits?: string;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/onboard.js\")).runOnboardCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"init [site]\",\n \"One-shot guided onboarding: convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit.\",\n )\n .option(\n \"--fleet <inventory>\",\n 'Inventory file (.json or .mjs/.js), or \"airtable\" to read from Websites table',\n )\n .option(\"--workdir <path>\", \"Clone target for fleet mode (default ~/.reddoor-maint/sites)\")\n .action(\n async (site, opts: { fleet?: string; workdir?: string; cwd?: string; verbose?: boolean }) =>\n runOrExit(async () => (await import(\"./commands/init.js\")).runInitCommand(site, opts), opts),\n );\n\ncli\n .command(\n \"launch <site>\",\n \"Bootstrap + first-audit a site, then draft its launch email for approval.\",\n )\n .action(async (site: string, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () => (await import(\"./commands/launch.js\")).runLaunchCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"announce [site]\",\n \"Draft the monthly-report announcement email for maintenance sites (all, or one) for approval.\",\n )\n .action(async (site: string | undefined, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () => (await import(\"./commands/announce.js\")).runAnnounceCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"selftest <kind> [site]\",\n \"Operator self-tests. kind=email: preview a report email for a site (or --all) to yourself.\",\n )\n .option(\"--type <type>\", \"Report type: announcement (default) | maintenance | testing | launch\")\n .option(\"--to <addr>\", \"Recipient(s), comma-separated. Default: OPERATOR_EMAIL.\")\n .option(\"--all\", \"Send a preview for every maintenance site (to --to/operator).\")\n .option(\"--dry-run\", \"Render only; write reports/<slug>/selftest-<type>.html; do not send.\")\n .action(\n async (\n kind: string,\n site: string | undefined,\n opts: {\n type?: string;\n to?: string;\n all?: boolean;\n dryRun?: boolean;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/selftest.js\")).runSelftestCommand(kind, site, opts),\n opts,\n ),\n );\n\ncli\n .command(\"report [site]\", \"Draft or send maintenance/testing reports.\")\n .option(\"--due\", \"Scan all Websites and draft overdue reports.\")\n .option(\"--type <type>\", \"Single-site draft report type: Maintenance (default) or Testing.\")\n .option(\n \"--preview\",\n \"Single-site dry run; writes reports/<slug>/draft.html, never touches Airtable.\",\n )\n .option(\n \"--send-ready\",\n \"Send all Reports with Draft ready=true AND Approved to send=true AND Sent at IS NULL.\",\n )\n .option(\n \"--digest\",\n \"Email the operator one daily digest of reports ready for approval (skips when empty).\",\n )\n .action(\n async (\n site,\n opts: {\n due?: boolean;\n type?: string;\n preview?: boolean;\n sendReady?: boolean;\n digest?: boolean;\n cwd?: string;\n verbose?: boolean;\n },\n ) =>\n runOrExit(\n async () => (await import(\"./commands/report.js\")).runReportCommand(site, opts),\n opts,\n ),\n );\n\ncli\n .command(\n \"github-signals\",\n \"Sweep the fleet for GitHub signals (Renovate-failing/CI/last-commit) and write Airtable.\",\n )\n .option(\"--fleet\", \"Run across every site in the Airtable inventory.\")\n .option(\"--write-airtable\", \"Write each site's signals back to its Websites row.\")\n .action(\n async (opts: { fleet?: boolean; writeAirtable?: boolean; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/github-signals.js\")).runGitHubSignalsCommand({\n fleet: opts.fleet,\n writeAirtable: opts.writeAirtable,\n }),\n opts,\n ),\n );\n\ncli\n .command(\n \"db <action>\",\n \"Migrate / backfill / reconcile the libSQL store (migrate | backfill | reconcile).\",\n )\n .action(async (action: string, opts: { cwd?: string; verbose?: boolean }) =>\n runOrExit(async () => (await import(\"./commands/db.js\")).runDbCommand(action, opts), opts),\n );\n\ncli\n .command(\n \"renovate-dispatch\",\n \"Trigger Renovate on fleet sites the security sweep flagged with critical/high vulns.\",\n )\n .option(\"--fleet\", \"Run across every active, repo-backed site in the Airtable inventory.\")\n .action(async (opts: { fleet?: boolean; cwd?: string; verbose?: boolean }) =>\n runOrExit(\n async () =>\n (await import(\"./commands/renovate-dispatch.js\")).runRenovateDispatchCommand({\n fleet: opts.fleet,\n }),\n opts,\n ),\n );\n\ncli.help();\ncli.version(version);\n\n// A typo'd / unrecognized subcommand (e.g. `reddoor-maint auditt`) otherwise\n// falls through cac with no matched command and exits 0 — a cron/CI typo would\n// \"succeed\" silently. cac emits `command:*` at the end of parse() exactly when\n// a positional arg was given but matched no command (a bare `reddoor-maint`,\n// `--help`, and `--version` do NOT trigger it: they have no leading positional\n// or are handled before this fires). Turn that into a clear stderr error +\n// non-zero exit. process.argv[2] is the first positional, i.e. the bad command.\ncli.on(\"command:*\", () => {\n const unknown = cli.args[0] ?? process.argv[2] ?? \"\";\n console.error(\n `error: unknown command '${unknown}'. Run 'reddoor-maint --help' to see available commands.`,\n );\n process.exit(1);\n});\n\ncli.parse();\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Read the @reddoorla/maintenance package version, given the directory of a\n * file that lives inside the package. Walks UP looking for the first\n * `package.json` whose `name` matches. Defensive against bundling-layout\n * changes — the older \"two levels up\" assumption held for `dist/cli/bin.js`\n * but would silently mis-read (or return \"unknown\") if tsup ever moved bin\n * to a different depth. Same walk-up pattern as the self-version helper.\n *\n * Returns \"unknown\" when no matching package.json is reachable (Yarn PnP\n * setups stash manifests inside .zip caches; the readFileSync there fails\n * before any name check).\n */\nexport function resolvePackageVersion(fromDir: string): string {\n try {\n let dir = fromDir;\n while (true) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n const raw = readFileSync(candidate, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n if (pkg.name === \"@reddoorla/maintenance\") {\n return pkg.version ?? \"unknown\";\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return \"unknown\";\n dir = parent;\n }\n } catch {\n return \"unknown\";\n }\n}\n"],"mappings":"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","names":["dirname","dirname"]}
@@ -8,17 +8,19 @@ import {
8
8
  } from "../../chunk-4NAOR44V.js";
9
9
  import {
10
10
  resolveSites
11
- } from "../../chunk-SAOCVNX7.js";
11
+ } from "../../chunk-FH7WQHD7.js";
12
12
  import {
13
13
  ALL_AUDIT_NAMES,
14
14
  runOneAudit
15
- } from "../../chunk-SS7BKOIX.js";
15
+ } from "../../chunk-WCN54744.js";
16
16
  import "../../chunk-LKGVSM2O.js";
17
17
  import {
18
18
  recordFleetEventsBestEffort
19
- } from "../../chunk-L5F664ZA.js";
20
- import "../../chunk-6ZKM7OIJ.js";
19
+ } from "../../chunk-G2YRXV26.js";
20
+ import "../../chunk-PTPTVVY7.js";
21
21
  import "../../chunk-XKKTK6WA.js";
22
+ import "../../chunk-GASBX52O.js";
23
+ import "../../chunk-YEGHUODU.js";
22
24
  import "../../chunk-FDIFTIXC.js";
23
25
  import "../../chunk-HVOOCK6L.js";
24
26
  import "../../chunk-XXTZBPUY.js";
@@ -229,9 +231,9 @@ ${skipNotice}`;
229
231
  let writeBackFailed = false;
230
232
  if (opts.writeAirtable !== void 0) {
231
233
  const { openBase, readAirtableConfig } = await import("../../client-EPVO3FEH.js");
232
- const { listWebsites } = await import("../../websites-HM2NMZMR.js");
234
+ const { listWebsites } = await import("../../websites-CSVLSEVL.js");
233
235
  if (opts.fleet !== void 0) {
234
- const { writeFleetAuditsToAirtable, formatFleetWriteSummary } = await import("../../write-audits-to-airtable-QF26PCAR.js");
236
+ const { writeFleetAuditsToAirtable, formatFleetWriteSummary } = await import("../../write-audits-to-airtable-JCV7FYAW.js");
235
237
  const base = openBase(readAirtableConfig());
236
238
  const websites = await listWebsites(base);
237
239
  const fleetWrite = await writeFleetAuditsToAirtable({ base, websites, results });
@@ -247,8 +249,8 @@ ${skipNotice}`;
247
249
 
248
250
  ${formatFleetWriteSummary(fleetWrite)}`;
249
251
  } else {
250
- const { resolveSlugFromCwd } = await import("../../lighthouse-airtable-HTEW6BBT.js");
251
- const { writeAuditsToAirtable } = await import("../../write-audits-to-airtable-QF26PCAR.js");
252
+ const { resolveSlugFromCwd } = await import("../../lighthouse-airtable-5FHELRCA.js");
253
+ const { writeAuditsToAirtable } = await import("../../write-audits-to-airtable-JCV7FYAW.js");
252
254
  const slug = typeof opts.writeAirtable === "string" && opts.writeAirtable.length > 0 ? opts.writeAirtable : await resolveSlugFromCwd(cwd);
253
255
  let writeSummary = null;
254
256
  await new Listr(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/audit.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { Listr } from \"listr2\";\nimport { runOneAudit, ALL_AUDIT_NAMES } from \"../../audits/index.js\";\nimport type { AuditName, AuditResult, Site } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport {\n prepareFleetSites,\n formatSkippedNotice,\n type SkippedSite,\n} from \"../fleet/prepare-sites.js\";\nimport { isHttpUrl } from \"../../util/url.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport { fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\n\nexport type AuditCommandOptions = {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n /**\n * After running, push the lighthouse scores to the matching Websites row\n * in Airtable. `true` (no value) = derive slug from cwd/package.json#name;\n * string = explicit slug (e.g. \"med-solutions-of-texas\").\n */\n writeAirtable?: string | boolean;\n /** Exit non-zero if any a11y violations are found (overrides warn). For CI gates. */\n failOnViolations?: boolean;\n /** Audit this deployed URL directly (lighthouse only; single-site). */\n url?: string;\n /** Max sites to audit in parallel (fleet mode). Unset = all at once;\n * `1` = sequential (used by the nightly CI workflow). */\n concurrency?: string;\n};\n\nfunction parseOnly(value: string | undefined): AuditName[] | undefined {\n if (!value) return undefined;\n const names = value.split(\",\").map((s) => s.trim());\n for (const n of names) {\n if (!ALL_AUDIT_NAMES.includes(n as AuditName)) {\n throw Object.assign(new Error(`unknown audit in --only: ${n}`), { exitCode: 2 });\n }\n }\n return names as AuditName[];\n}\n\nfunction formatTable(results: AuditResult[]): string {\n return results\n .map((r) => `${r.audit.padEnd(12)} ${r.status.padEnd(5)} ${r.site}\\n ${r.summary}`)\n .join(\"\\n\");\n}\n\nexport function auditExitCode(results: AuditResult[], failOnViolations: boolean): number {\n if (results.some((r) => r.status === \"fail\")) return 1;\n if (failOnViolations) {\n const a11yViolations = results\n .filter((r) => r.audit === \"a11y\")\n .reduce(\n (n, r) =>\n n + ((r.details as { totalViolations?: number } | undefined)?.totalViolations ?? 0),\n 0,\n );\n if (a11yViolations > 0) return 1;\n }\n return 0;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1_000) return `${ms}ms`;\n const totalSeconds = Math.round(ms / 1_000);\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}m${s.toString().padStart(2, \"0\")}s`;\n}\n\ntype Renderer = \"default\" | \"silent\";\n\n/** Parse the `--concurrency <n>` flag into a Listr `concurrent` value. Unset →\n * `true` (all sites in parallel, the interactive default). A positive integer\n * bounds how many sites audit at once — `--concurrency 1` runs sequentially,\n * which is what the nightly CI workflow uses so ~10 deployed-Lighthouse runs\n * don't saturate a 2-core runner (one flaked locally at full parallelism). */\nexport function parseConcurrency(value: string | undefined): boolean | number {\n if (value === undefined) return true;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 1) {\n throw Object.assign(new Error(`--concurrency must be a positive integer, got \"${value}\"`), {\n exitCode: 2,\n });\n }\n return n;\n}\n\n/** Build the audit-progress task list. Single-site → each audit is a sibling\n * task. Fleet → each site is a task whose `output` shows X/N audits done as\n * they complete (audits-per-site still run in parallel). Results are pushed\n * into the shared `results` array; tasks throw on `fail` status so listr2\n * paints them red, but `exitOnError: false` keeps other tasks running. */\nfunction buildAuditTasks(\n sites: Site[],\n which: AuditName[],\n results: AuditResult[],\n renderer: Renderer,\n concurrency: boolean | number,\n) {\n const singleSite = sites.length === 1;\n\n if (singleSite) {\n const site = sites[0]!;\n return new Listr(\n which.map((name) => ({\n title: name,\n task: async (_ctx, task) => {\n const start = Date.now();\n const result = await runOneAudit(site, name);\n results.push(result);\n const elapsed = formatDuration(Date.now() - start);\n task.title = `${name}: ${result.summary} (${elapsed})`;\n if (result.status === \"fail\") throw new Error(result.summary);\n },\n })),\n { concurrent: true, exitOnError: false, renderer },\n );\n }\n\n return new Listr(\n sites.map((site) => {\n const label = site.name || site.path; // `||`: empty slug must fall back to path, not blank\n return {\n title: label,\n task: async (_ctx, task) => {\n const start = Date.now();\n let done = 0;\n task.output = `0/${which.length} audits`;\n const settled = await Promise.all(\n which.map(async (name) => {\n const r = await runOneAudit(site, name);\n results.push(r);\n done += 1;\n task.output = `${done}/${which.length} audits`;\n return r;\n }),\n );\n const elapsed = formatDuration(Date.now() - start);\n const failed = settled.filter((r) => r.status === \"fail\").length;\n const warned = settled.filter((r) => r.status === \"warn\").length;\n const note =\n failed > 0\n ? `${failed} failed`\n : warned > 0\n ? `${warned} warning${warned === 1 ? \"\" : \"s\"}`\n : \"all green\";\n task.title = `${label}: ${note} (${elapsed})`;\n if (failed > 0) throw new Error(`${label}: ${failed} audit(s) failed`);\n },\n };\n }),\n { concurrent: concurrency, exitOnError: false, renderer },\n );\n}\n\ntype WriteSummary = Awaited<\n ReturnType<typeof import(\"../../audits/write-audits-to-airtable.js\").writeAuditsToAirtable>\n>;\n\nfunction formatWriteSummary(summary: WriteSummary): string {\n const lines = summary.writes.map((w) => {\n if (w.audit === \"lighthouse\") {\n const s = w.counts as {\n performance: number;\n accessibility: number;\n bestPractices: number;\n seo: number;\n };\n return ` lighthouse: P=${s.performance} A=${s.accessibility} BP=${s.bestPractices} SEO=${s.seo}`;\n }\n if (w.audit === \"a11y\") {\n return ` a11y: ${(w.counts as { violations: number }).violations} violations`;\n }\n if (w.audit === \"deps\") {\n const c = w.counts as { drifted: number; majorBehind: number };\n return ` deps: ${c.drifted} drifted (${c.majorBehind} major)`;\n }\n const c = w.counts as { critical: number; high: number; moderate: number; low: number };\n return ` security: ${c.critical}C/${c.high}H/${c.moderate}M/${c.low}L`;\n });\n return `→ wrote to Websites[${summary.siteName}]:\\n${lines.join(\"\\n\")}`;\n}\n\n/** Listr renderer choice. `--json` → silent so stdout stays clean for piping.\n * Otherwise listr's `default` renderer auto-falls back to `simple` in\n * non-TTY contexts (CI, log capture, our own integration tests). */\nfunction rendererFor(json: boolean | undefined): Renderer {\n return json ? \"silent\" : \"default\";\n}\n\n/** When `--url` is set but audits other than lighthouse also ran (those use the\n * local checkout, not the deployed URL), return a one-line operator notice;\n * null when there's nothing to warn about. Keeps the mixed-provenance result\n * table from silently confusing the operator. */\nexport function deployedUrlNotice(\n which: AuditName[],\n url: string | undefined,\n cwd: string,\n): string | null {\n if (url === undefined) return null;\n const others = which.filter((n) => n !== \"lighthouse\");\n if (others.length === 0) return null;\n return `note: --url only affects lighthouse; ${others.join(\", \")} ran against the local checkout at ${cwd}`;\n}\n\n/** Audits that need no repo checkout — they probe an external resource. lighthouse hits the\n * live URL; domain probes DNS/TLS of the URL's host; browser drives the deployed URL;\n * netlify-deploy queries the Netlify API by site id. */\nconst CHECKOUT_FREE_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\n \"lighthouse\",\n \"domain\",\n \"browser\",\n \"netlify-deploy\",\n]);\n\n/** Audits whose checkout-free run is keyed off `site.netlifyId` (the Netlify API) rather than\n * the deployed URL. Everything else in CHECKOUT_FREE_AUDITS reads `site.deployedUrl`. */\nconst NETLIFY_ID_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\"netlify-deploy\"]);\n\n/** A fleet site needs a local checkout unless every requested audit is checkout-free AND the site\n * has the external resource each one runs against (`deployedUrl` for lighthouse/domain/browser,\n * `netlifyId` for netlify-deploy). */\nexport function auditNeedsCheckout(site: Site, which: AuditName[]): boolean {\n const deployedCapable = which.every((n) => {\n if (!CHECKOUT_FREE_AUDITS.has(n)) return false;\n return NETLIFY_ID_AUDITS.has(n) ? site.netlifyId !== undefined : site.deployedUrl !== undefined;\n });\n return !deployedCapable;\n}\n\n/** Apply a single-site `--url` to the resolved sites. Returns the input\n * untouched when no url is given; otherwise requires exactly one site and\n * stamps `deployedUrl` on it so the lighthouse audit takes its deployed path.\n * The `--url`+`--fleet` combination is rejected earlier in `runAuditCommand`;\n * this length guard also covers any future multi-site single-run resolver. */\nexport function applyDeployedUrl(sites: Site[], url: string | undefined): Site[] {\n if (url === undefined) return sites;\n if (sites.length !== 1) {\n throw Object.assign(\n new Error(`--url expects exactly one site, but ${sites.length} resolved.`),\n { exitCode: 2 },\n );\n }\n // Scheme-allowlist: the URL is handed straight to Chrome/lhci, so only\n // http(s) is safe (a file:///gopher:// value would be a local-file read /\n // SSRF). Same predicate the inventory paths use.\n if (!isHttpUrl(url)) {\n throw Object.assign(new Error(`--url must be an http(s) URL (got: ${JSON.stringify(url)})`), {\n exitCode: 2,\n });\n }\n return [{ ...sites[0]!, deployedUrl: url }];\n}\n\nexport async function runAuditCommand(\n site: string | undefined,\n opts: AuditCommandOptions,\n): Promise<{ output: string; code: number }> {\n const which = parseOnly(opts.only) ?? ALL_AUDIT_NAMES;\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n // A literal --write-airtable=<slug> is single-site (the slug names one row).\n // Boolean --write-airtable + --fleet is fine: each site's slug comes from the\n // inventory, so there's no cwd-derived-slug ambiguity.\n if (typeof opts.writeAirtable === \"string\" && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--write-airtable=<slug> is single-site; with --fleet each site's slug comes from the inventory. Use --write-airtable (no slug) + --fleet.\",\n ),\n { exitCode: 2 },\n );\n }\n\n if (opts.url !== undefined && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--url is single-site only and cannot be combined with --fleet. Audit a single site instead.\",\n ),\n { exitCode: 2 },\n );\n }\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n sites = applyDeployedUrl(sites, opts.url);\n\n let skippedPrep: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, {\n workdir,\n needsCheckout: (s) => auditNeedsCheckout(s, which),\n });\n sites = prep.prepared;\n skippedPrep = prep.skipped;\n }\n\n const results: AuditResult[] = [];\n const renderer = rendererFor(opts.json);\n await buildAuditTasks(sites, which, results, renderer, parseConcurrency(opts.concurrency)).run();\n\n let output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);\n\n // Surface any site that couldn't be prepared (no auditable target, clone\n // failure) — visibly, but without reding the run. One misconfigured inventory\n // row is an operator fix, not an outage; the other sites still audited and\n // wrote back. \"No silent caps\": a dropped site is never invisible.\n const skipNotice = formatSkippedNotice(skippedPrep);\n if (skipNotice && !opts.json) output += `\\n\\n${skipNotice}`;\n\n // Did any site fail to write back to Airtable? The fleet writer collects\n // per-site failures instead of throwing, so without this the command would\n // exit 0 while rows silently failed to persist — automation keying on `$?`\n // would see a clean run. (The single-site writer throws on failure, so it's\n // already non-zero via the propagated error.)\n let writeBackFailed = false;\n if (opts.writeAirtable !== undefined) {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n\n if (opts.fleet !== undefined) {\n const { writeFleetAuditsToAirtable, formatFleetWriteSummary } =\n await import(\"../../audits/write-audits-to-airtable.js\");\n const base = openBase(readAirtableConfig());\n const websites = await listWebsites(base);\n const fleetWrite = await writeFleetAuditsToAirtable({ base, websites, results });\n if (fleetWrite.failed.length > 0) writeBackFailed = true;\n // Record fleet-activity events (vuln_cleared / cert_renewed rode along on each\n // WriteSummary) plus a per-sweep rollup. Best-effort: a missing Turso cred no-ops.\n const sweep = which.includes(\"security\") ? \"security\" : \"lighthouse\";\n const now = new Date();\n const auditEvents = fleetWrite.written.flatMap((w) => w.events ?? []);\n await recordFleetEventsBestEffort(\n [...auditEvents, fleetSweptEvent(sweep, fleetWrite.written.length, now.toISOString())],\n now,\n );\n // Gate on !json: the write-summary is human text; appending it after the\n // results array would corrupt `--json` output (the other notices already\n // guard this way). The write itself still happens regardless of --json.\n if (!opts.json) output += `\\n\\n${formatFleetWriteSummary(fleetWrite)}`;\n } else {\n const { resolveSlugFromCwd } = await import(\"../../audits/lighthouse-airtable.js\");\n const { writeAuditsToAirtable } = await import(\"../../audits/write-audits-to-airtable.js\");\n const slug =\n typeof opts.writeAirtable === \"string\" && opts.writeAirtable.length > 0\n ? opts.writeAirtable\n : await resolveSlugFromCwd(cwd);\n let writeSummary: WriteSummary | null = null;\n await new Listr(\n [\n {\n title: `Write to Airtable[${slug}]`,\n task: async (_ctx, task) => {\n const base = openBase(readAirtableConfig());\n task.output = \"loading Websites…\";\n const websites = await listWebsites(base);\n task.output = \"writing scores…\";\n writeSummary = await writeAuditsToAirtable({ base, websites, slug, results });\n task.title = `Wrote to Websites[${writeSummary.siteName}] (${writeSummary.writes.length} audit type${writeSummary.writes.length === 1 ? \"\" : \"s\"})`;\n },\n },\n ],\n { renderer },\n ).run();\n if (writeSummary && !opts.json) output += `\\n\\n${formatWriteSummary(writeSummary)}`;\n }\n }\n\n const notice = deployedUrlNotice(which, opts.url, cwd);\n if (notice && !opts.json) output += `\\n\\n${notice}`;\n\n const code = Math.max(\n auditExitCode(results, opts.failOnViolations === true),\n writeBackFailed ? 1 : 0,\n );\n return { output, code };\n}\n"],"mappings":"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","names":["c"]}
1
+ {"version":3,"sources":["../../../src/cli/commands/audit.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { Listr } from \"listr2\";\nimport { runOneAudit, ALL_AUDIT_NAMES } from \"../../audits/index.js\";\nimport type { AuditName, AuditResult, Site } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport {\n prepareFleetSites,\n formatSkippedNotice,\n type SkippedSite,\n} from \"../fleet/prepare-sites.js\";\nimport { isHttpUrl } from \"../../util/url.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport { fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\n\nexport type AuditCommandOptions = {\n only?: string;\n json?: boolean;\n fleet?: string;\n workdir?: string;\n cwd?: string;\n /**\n * After running, push the lighthouse scores to the matching Websites row\n * in Airtable. `true` (no value) = derive slug from cwd/package.json#name;\n * string = explicit slug (e.g. \"med-solutions-of-texas\").\n */\n writeAirtable?: string | boolean;\n /** Exit non-zero if any a11y violations are found (overrides warn). For CI gates. */\n failOnViolations?: boolean;\n /** Audit this deployed URL directly (lighthouse only; single-site). */\n url?: string;\n /** Max sites to audit in parallel (fleet mode). Unset = all at once;\n * `1` = sequential (used by the nightly CI workflow). */\n concurrency?: string;\n};\n\nfunction parseOnly(value: string | undefined): AuditName[] | undefined {\n if (!value) return undefined;\n const names = value.split(\",\").map((s) => s.trim());\n for (const n of names) {\n if (!ALL_AUDIT_NAMES.includes(n as AuditName)) {\n throw Object.assign(new Error(`unknown audit in --only: ${n}`), { exitCode: 2 });\n }\n }\n return names as AuditName[];\n}\n\nfunction formatTable(results: AuditResult[]): string {\n return results\n .map((r) => `${r.audit.padEnd(12)} ${r.status.padEnd(5)} ${r.site}\\n ${r.summary}`)\n .join(\"\\n\");\n}\n\nexport function auditExitCode(results: AuditResult[], failOnViolations: boolean): number {\n if (results.some((r) => r.status === \"fail\")) return 1;\n if (failOnViolations) {\n const a11yViolations = results\n .filter((r) => r.audit === \"a11y\")\n .reduce(\n (n, r) =>\n n + ((r.details as { totalViolations?: number } | undefined)?.totalViolations ?? 0),\n 0,\n );\n if (a11yViolations > 0) return 1;\n }\n return 0;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1_000) return `${ms}ms`;\n const totalSeconds = Math.round(ms / 1_000);\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}m${s.toString().padStart(2, \"0\")}s`;\n}\n\ntype Renderer = \"default\" | \"silent\";\n\n/** Parse the `--concurrency <n>` flag into a Listr `concurrent` value. Unset →\n * `true` (all sites in parallel, the interactive default). A positive integer\n * bounds how many sites audit at once — `--concurrency 1` runs sequentially,\n * which is what the nightly CI workflow uses so ~10 deployed-Lighthouse runs\n * don't saturate a 2-core runner (one flaked locally at full parallelism). */\nexport function parseConcurrency(value: string | undefined): boolean | number {\n if (value === undefined) return true;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 1) {\n throw Object.assign(new Error(`--concurrency must be a positive integer, got \"${value}\"`), {\n exitCode: 2,\n });\n }\n return n;\n}\n\n/** Build the audit-progress task list. Single-site → each audit is a sibling\n * task. Fleet → each site is a task whose `output` shows X/N audits done as\n * they complete (audits-per-site still run in parallel). Results are pushed\n * into the shared `results` array; tasks throw on `fail` status so listr2\n * paints them red, but `exitOnError: false` keeps other tasks running. */\nfunction buildAuditTasks(\n sites: Site[],\n which: AuditName[],\n results: AuditResult[],\n renderer: Renderer,\n concurrency: boolean | number,\n) {\n const singleSite = sites.length === 1;\n\n if (singleSite) {\n const site = sites[0]!;\n return new Listr(\n which.map((name) => ({\n title: name,\n task: async (_ctx, task) => {\n const start = Date.now();\n const result = await runOneAudit(site, name);\n results.push(result);\n const elapsed = formatDuration(Date.now() - start);\n task.title = `${name}: ${result.summary} (${elapsed})`;\n if (result.status === \"fail\") throw new Error(result.summary);\n },\n })),\n { concurrent: true, exitOnError: false, renderer },\n );\n }\n\n return new Listr(\n sites.map((site) => {\n const label = site.name || site.path; // `||`: empty slug must fall back to path, not blank\n return {\n title: label,\n task: async (_ctx, task) => {\n const start = Date.now();\n let done = 0;\n task.output = `0/${which.length} audits`;\n const settled = await Promise.all(\n which.map(async (name) => {\n const r = await runOneAudit(site, name);\n results.push(r);\n done += 1;\n task.output = `${done}/${which.length} audits`;\n return r;\n }),\n );\n const elapsed = formatDuration(Date.now() - start);\n const failed = settled.filter((r) => r.status === \"fail\").length;\n const warned = settled.filter((r) => r.status === \"warn\").length;\n const note =\n failed > 0\n ? `${failed} failed`\n : warned > 0\n ? `${warned} warning${warned === 1 ? \"\" : \"s\"}`\n : \"all green\";\n task.title = `${label}: ${note} (${elapsed})`;\n if (failed > 0) throw new Error(`${label}: ${failed} audit(s) failed`);\n },\n };\n }),\n { concurrent: concurrency, exitOnError: false, renderer },\n );\n}\n\ntype WriteSummary = Awaited<\n ReturnType<typeof import(\"../../audits/write-audits-to-airtable.js\").writeAuditsToAirtable>\n>;\n\nfunction formatWriteSummary(summary: WriteSummary): string {\n const lines = summary.writes.map((w) => {\n if (w.audit === \"lighthouse\") {\n const s = w.counts as {\n performance: number;\n accessibility: number;\n bestPractices: number;\n seo: number;\n };\n return ` lighthouse: P=${s.performance} A=${s.accessibility} BP=${s.bestPractices} SEO=${s.seo}`;\n }\n if (w.audit === \"a11y\") {\n return ` a11y: ${(w.counts as { violations: number }).violations} violations`;\n }\n if (w.audit === \"deps\") {\n const c = w.counts as { drifted: number; majorBehind: number };\n return ` deps: ${c.drifted} drifted (${c.majorBehind} major)`;\n }\n const c = w.counts as { critical: number; high: number; moderate: number; low: number };\n return ` security: ${c.critical}C/${c.high}H/${c.moderate}M/${c.low}L`;\n });\n return `→ wrote to Websites[${summary.siteName}]:\\n${lines.join(\"\\n\")}`;\n}\n\n/** Listr renderer choice. `--json` → silent so stdout stays clean for piping.\n * Otherwise listr's `default` renderer auto-falls back to `simple` in\n * non-TTY contexts (CI, log capture, our own integration tests). */\nfunction rendererFor(json: boolean | undefined): Renderer {\n return json ? \"silent\" : \"default\";\n}\n\n/** When `--url` is set but audits other than lighthouse also ran (those use the\n * local checkout, not the deployed URL), return a one-line operator notice;\n * null when there's nothing to warn about. Keeps the mixed-provenance result\n * table from silently confusing the operator. */\nexport function deployedUrlNotice(\n which: AuditName[],\n url: string | undefined,\n cwd: string,\n): string | null {\n if (url === undefined) return null;\n const others = which.filter((n) => n !== \"lighthouse\");\n if (others.length === 0) return null;\n return `note: --url only affects lighthouse; ${others.join(\", \")} ran against the local checkout at ${cwd}`;\n}\n\n/** Audits that need no repo checkout — they probe an external resource. lighthouse hits the\n * live URL; domain probes DNS/TLS of the URL's host; browser drives the deployed URL;\n * netlify-deploy queries the Netlify API by site id. */\nconst CHECKOUT_FREE_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\n \"lighthouse\",\n \"domain\",\n \"browser\",\n \"netlify-deploy\",\n]);\n\n/** Audits whose checkout-free run is keyed off `site.netlifyId` (the Netlify API) rather than\n * the deployed URL. Everything else in CHECKOUT_FREE_AUDITS reads `site.deployedUrl`. */\nconst NETLIFY_ID_AUDITS: ReadonlySet<AuditName> = new Set<AuditName>([\"netlify-deploy\"]);\n\n/** A fleet site needs a local checkout unless every requested audit is checkout-free AND the site\n * has the external resource each one runs against (`deployedUrl` for lighthouse/domain/browser,\n * `netlifyId` for netlify-deploy). */\nexport function auditNeedsCheckout(site: Site, which: AuditName[]): boolean {\n const deployedCapable = which.every((n) => {\n if (!CHECKOUT_FREE_AUDITS.has(n)) return false;\n return NETLIFY_ID_AUDITS.has(n) ? site.netlifyId !== undefined : site.deployedUrl !== undefined;\n });\n return !deployedCapable;\n}\n\n/** Apply a single-site `--url` to the resolved sites. Returns the input\n * untouched when no url is given; otherwise requires exactly one site and\n * stamps `deployedUrl` on it so the lighthouse audit takes its deployed path.\n * The `--url`+`--fleet` combination is rejected earlier in `runAuditCommand`;\n * this length guard also covers any future multi-site single-run resolver. */\nexport function applyDeployedUrl(sites: Site[], url: string | undefined): Site[] {\n if (url === undefined) return sites;\n if (sites.length !== 1) {\n throw Object.assign(\n new Error(`--url expects exactly one site, but ${sites.length} resolved.`),\n { exitCode: 2 },\n );\n }\n // Scheme-allowlist: the URL is handed straight to Chrome/lhci, so only\n // http(s) is safe (a file:///gopher:// value would be a local-file read /\n // SSRF). Same predicate the inventory paths use.\n if (!isHttpUrl(url)) {\n throw Object.assign(new Error(`--url must be an http(s) URL (got: ${JSON.stringify(url)})`), {\n exitCode: 2,\n });\n }\n return [{ ...sites[0]!, deployedUrl: url }];\n}\n\nexport async function runAuditCommand(\n site: string | undefined,\n opts: AuditCommandOptions,\n): Promise<{ output: string; code: number }> {\n const which = parseOnly(opts.only) ?? ALL_AUDIT_NAMES;\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n // A literal --write-airtable=<slug> is single-site (the slug names one row).\n // Boolean --write-airtable + --fleet is fine: each site's slug comes from the\n // inventory, so there's no cwd-derived-slug ambiguity.\n if (typeof opts.writeAirtable === \"string\" && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--write-airtable=<slug> is single-site; with --fleet each site's slug comes from the inventory. Use --write-airtable (no slug) + --fleet.\",\n ),\n { exitCode: 2 },\n );\n }\n\n if (opts.url !== undefined && opts.fleet !== undefined) {\n throw Object.assign(\n new Error(\n \"--url is single-site only and cannot be combined with --fleet. Audit a single site instead.\",\n ),\n { exitCode: 2 },\n );\n }\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n ...(opts.workdir !== undefined ? { workdir: opts.workdir } : {}),\n cwd,\n });\n\n sites = applyDeployedUrl(sites, opts.url);\n\n let skippedPrep: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, {\n workdir,\n needsCheckout: (s) => auditNeedsCheckout(s, which),\n });\n sites = prep.prepared;\n skippedPrep = prep.skipped;\n }\n\n const results: AuditResult[] = [];\n const renderer = rendererFor(opts.json);\n await buildAuditTasks(sites, which, results, renderer, parseConcurrency(opts.concurrency)).run();\n\n let output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);\n\n // Surface any site that couldn't be prepared (no auditable target, clone\n // failure) — visibly, but without reding the run. One misconfigured inventory\n // row is an operator fix, not an outage; the other sites still audited and\n // wrote back. \"No silent caps\": a dropped site is never invisible.\n const skipNotice = formatSkippedNotice(skippedPrep);\n if (skipNotice && !opts.json) output += `\\n\\n${skipNotice}`;\n\n // Did any site fail to write back to Airtable? The fleet writer collects\n // per-site failures instead of throwing, so without this the command would\n // exit 0 while rows silently failed to persist — automation keying on `$?`\n // would see a clean run. (The single-site writer throws on failure, so it's\n // already non-zero via the propagated error.)\n let writeBackFailed = false;\n if (opts.writeAirtable !== undefined) {\n const { openBase, readAirtableConfig } = await import(\"../../reports/airtable/client.js\");\n const { listWebsites } = await import(\"../../reports/airtable/websites.js\");\n\n if (opts.fleet !== undefined) {\n const { writeFleetAuditsToAirtable, formatFleetWriteSummary } =\n await import(\"../../audits/write-audits-to-airtable.js\");\n const base = openBase(readAirtableConfig());\n const websites = await listWebsites(base);\n const fleetWrite = await writeFleetAuditsToAirtable({ base, websites, results });\n if (fleetWrite.failed.length > 0) writeBackFailed = true;\n // Record fleet-activity events (vuln_cleared / cert_renewed rode along on each\n // WriteSummary) plus a per-sweep rollup. Best-effort: a missing Turso cred no-ops.\n const sweep = which.includes(\"security\") ? \"security\" : \"lighthouse\";\n const now = new Date();\n const auditEvents = fleetWrite.written.flatMap((w) => w.events ?? []);\n await recordFleetEventsBestEffort(\n [...auditEvents, fleetSweptEvent(sweep, fleetWrite.written.length, now.toISOString())],\n now,\n );\n // Gate on !json: the write-summary is human text; appending it after the\n // results array would corrupt `--json` output (the other notices already\n // guard this way). The write itself still happens regardless of --json.\n if (!opts.json) output += `\\n\\n${formatFleetWriteSummary(fleetWrite)}`;\n } else {\n const { resolveSlugFromCwd } = await import(\"../../audits/lighthouse-airtable.js\");\n const { writeAuditsToAirtable } = await import(\"../../audits/write-audits-to-airtable.js\");\n const slug =\n typeof opts.writeAirtable === \"string\" && opts.writeAirtable.length > 0\n ? opts.writeAirtable\n : await resolveSlugFromCwd(cwd);\n let writeSummary: WriteSummary | null = null;\n await new Listr(\n [\n {\n title: `Write to Airtable[${slug}]`,\n task: async (_ctx, task) => {\n const base = openBase(readAirtableConfig());\n task.output = \"loading Websites…\";\n const websites = await listWebsites(base);\n task.output = \"writing scores…\";\n writeSummary = await writeAuditsToAirtable({ base, websites, slug, results });\n task.title = `Wrote to Websites[${writeSummary.siteName}] (${writeSummary.writes.length} audit type${writeSummary.writes.length === 1 ? \"\" : \"s\"})`;\n },\n },\n ],\n { renderer },\n ).run();\n if (writeSummary && !opts.json) output += `\\n\\n${formatWriteSummary(writeSummary)}`;\n }\n }\n\n const notice = deployedUrlNotice(which, opts.url, cwd);\n if (notice && !opts.json) output += `\\n\\n${notice}`;\n\n const code = Math.max(\n auditExitCode(results, opts.failOnViolations === true),\n writeBackFailed ? 1 : 0,\n );\n return { output, code };\n}\n"],"mappings":"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","names":["c"]}
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  openDb,
3
3
  readDbConfig
4
- } from "./chunk-6ZKM7OIJ.js";
4
+ } from "./chunk-PTPTVVY7.js";
5
5
  import "./chunk-XKKTK6WA.js";
6
6
  import "./chunk-6VAL7XJT.js";
7
7
  export {
8
8
  openDb,
9
9
  readDbConfig
10
10
  };
11
- //# sourceMappingURL=client-GTMJTXBK.js.map
11
+ //# sourceMappingURL=client-C2GOZD2D.js.map
@@ -1,3 +1,6 @@
1
+ import {
2
+ runRecipeOverSites
3
+ } from "./chunk-VPJ7N6P7.js";
1
4
  import {
2
5
  appendSkipNotice,
3
6
  fleetWorkdir,
@@ -5,7 +8,7 @@ import {
5
8
  } from "./chunk-4NAOR44V.js";
6
9
  import {
7
10
  resolveSites
8
- } from "./chunk-SAOCVNX7.js";
11
+ } from "./chunk-FH7WQHD7.js";
9
12
  import {
10
13
  convertToPnpm
11
14
  } from "./chunk-2X322ZES.js";
@@ -39,8 +42,7 @@ async function runConvertToPnpmCommand(site, opts) {
39
42
  sites = prep.prepared;
40
43
  skipped = prep.skipped;
41
44
  }
42
- const results = [];
43
- for (const s of sites) results.push(await convertToPnpm(s));
45
+ const results = await runRecipeOverSites("convert-to-pnpm", sites, (s) => convertToPnpm(s));
44
46
  const output = results.map(formatResult).join("\n");
45
47
  const code = results.some((r) => r.status === "failed") ? 1 : 0;
46
48
  return { output: appendSkipNotice(output, skipped), code };
@@ -48,4 +50,4 @@ async function runConvertToPnpmCommand(site, opts) {
48
50
  export {
49
51
  runConvertToPnpmCommand
50
52
  };
51
- //# sourceMappingURL=convert-to-pnpm-JG3767ZQ.js.map
53
+ //# sourceMappingURL=convert-to-pnpm-S4TNJD53.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/convert-to-pnpm.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { convertToPnpm } from \"../../recipes/convert-to-pnpm.js\";\nimport type { RecipeResult } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport { prepareFleetSites, appendSkipNotice, type SkippedSite } from \"../fleet/prepare-sites.js\";\nimport { runRecipeOverSites } from \"../fleet/run-recipe-over-sites.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\n\nexport type ConvertToPnpmCommandOptions = {\n fleet?: string;\n workdir?: string;\n cwd?: string;\n};\n\nfunction formatResult(r: RecipeResult): string {\n if (r.status === \"noop\") return `[${r.site}] noop: ${r.notes ?? \"\"}`;\n if (r.status === \"failed\") return `[${r.site}] failed: ${r.notes ?? \"\"}`;\n return `[${r.site}] applied: ${r.commits.length} commit(s)\\n${r.notes ?? \"\"}`;\n}\n\nexport async function runConvertToPnpmCommand(\n site: string | undefined,\n opts: ConvertToPnpmCommandOptions,\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n cwd,\n });\n\n let skipped: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, { workdir });\n sites = prep.prepared;\n skipped = prep.skipped;\n }\n\n const results = await runRecipeOverSites(\"convert-to-pnpm\", sites, (s) => convertToPnpm(s));\n\n const output = results.map(formatResult).join(\"\\n\");\n const code = results.some((r) => r.status === \"failed\") ? 1 : 0;\n return { output: appendSkipNotice(output, skipped), code };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,eAAe;AAcxB,SAAS,aAAa,GAAyB;AAC7C,MAAI,EAAE,WAAW,OAAQ,QAAO,IAAI,EAAE,IAAI,WAAW,EAAE,SAAS,EAAE;AAClE,MAAI,EAAE,WAAW,SAAU,QAAO,IAAI,EAAE,IAAI,aAAa,EAAE,SAAS,EAAE;AACtE,SAAO,IAAI,EAAE,IAAI,cAAc,EAAE,QAAQ,MAAM;AAAA,EAAe,EAAE,SAAS,EAAE;AAC7E;AAEA,eAAsB,wBACpB,MACA,MAC2C;AAC3C,QAAM,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG,IAAI,QAAQ,IAAI;AAEvD,MAAI,QAAQ,MAAM,aAAa;AAAA,IAC7B,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IACrC,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,IACxD;AAAA,EACF,CAAC;AAED,MAAI,UAAyB,CAAC;AAC9B,MAAI,KAAK,OAAO;AACd,UAAM,UAAU,KAAK,WAAW,aAAa;AAC7C,UAAM,OAAO,MAAM,kBAAkB,OAAO,EAAE,QAAQ,CAAC;AACvD,YAAQ,KAAK;AACb,cAAU,KAAK;AAAA,EACjB;AAEA,QAAM,UAAU,MAAM,mBAAmB,mBAAmB,OAAO,CAAC,MAAM,cAAc,CAAC,CAAC;AAE1F,QAAM,SAAS,QAAQ,IAAI,YAAY,EAAE,KAAK,IAAI;AAClD,QAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ,IAAI,IAAI;AAC9D,SAAO,EAAE,QAAQ,iBAAiB,QAAQ,OAAO,GAAG,KAAK;AAC3D;","names":[]}
@@ -1,7 +1,7 @@
1
1
  // src/cli/commands/db.ts
2
2
  async function runDbCommand(action, opts) {
3
3
  if (action === "migrate") {
4
- const { readDbConfig } = await import("./client-GTMJTXBK.js");
4
+ const { readDbConfig } = await import("./client-C2GOZD2D.js");
5
5
  const cfg = opts.url ? { url: opts.url } : readDbConfig();
6
6
  const { runMigrations } = await import("./migrate-BFOG2OCP.js");
7
7
  const { createClient } = await import("@libsql/client");
@@ -20,4 +20,4 @@ async function runDbCommand(action, opts) {
20
20
  export {
21
21
  runDbCommand
22
22
  };
23
- //# sourceMappingURL=db-Y6BVTNH7.js.map
23
+ //# sourceMappingURL=db-YBLQR2YY.js.map
@@ -8,7 +8,7 @@ import {
8
8
  diffAttention,
9
9
  readDigestState,
10
10
  writeDigestState
11
- } from "./chunk-BMDBOPN3.js";
11
+ } from "./chunk-4A5PYCGB.js";
12
12
  import {
13
13
  isIdempotencyConflict
14
14
  } from "./chunk-OGGRFBL4.js";
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  listWebsites,
22
22
  siteSlug
23
- } from "./chunk-BLD6AYJO.js";
23
+ } from "./chunk-7RGQ4KLE.js";
24
24
  import {
25
25
  openBase,
26
26
  readAirtableConfig
@@ -226,4 +226,4 @@ export {
226
226
  renderDigestHtml,
227
227
  runDigest
228
228
  };
229
- //# sourceMappingURL=digest-ABJROO4H.js.map
229
+ //# sourceMappingURL=digest-R3ROSGLP.js.map
@@ -3,26 +3,26 @@ import {
3
3
  } from "./chunk-PZQ3WED2.js";
4
4
  import {
5
5
  formatFleetWriteSummary
6
- } from "./chunk-4REUHZAW.js";
7
- import "./chunk-YYUY2OVP.js";
8
- import {
9
- makeGitHub
10
- } from "./chunk-OSRFDGQM.js";
6
+ } from "./chunk-LM42CATB.js";
7
+ import "./chunk-CSTLFSGM.js";
11
8
  import {
12
9
  detectSignalEvents,
13
10
  fleetSweptEvent
14
11
  } from "./chunk-5L3FPQIJ.js";
15
12
  import {
16
13
  recordFleetEventsBestEffort
17
- } from "./chunk-L5F664ZA.js";
18
- import "./chunk-6ZKM7OIJ.js";
14
+ } from "./chunk-G2YRXV26.js";
15
+ import "./chunk-PTPTVVY7.js";
19
16
  import "./chunk-XKKTK6WA.js";
17
+ import {
18
+ makeGitHub
19
+ } from "./chunk-YEGHUODU.js";
20
20
  import "./chunk-HVOOCK6L.js";
21
21
  import {
22
22
  listWebsites,
23
23
  siteSlug,
24
24
  updateGitHubSignals
25
- } from "./chunk-BLD6AYJO.js";
25
+ } from "./chunk-7RGQ4KLE.js";
26
26
  import {
27
27
  openBase,
28
28
  readAirtableConfig
@@ -134,4 +134,4 @@ export {
134
134
  githubSignalsExitCode,
135
135
  runGitHubSignalsCommand
136
136
  };
137
- //# sourceMappingURL=github-signals-XTJLKOXJ.js.map
137
+ //# sourceMappingURL=github-signals-R4FNU34K.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as Site, a as AuditResult, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-Dg4mHvyf.js';
1
+ import { a as AuditResult, S as Site, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-Dg4mHvyf.js';
2
2
  export { C as ConfigName } from './types-Dg4mHvyf.js';
3
3
  import * as airtable_lib_airtable_base_js from 'airtable/lib/airtable_base.js';
4
4
  import { F as FormType } from './types-RXY-vY-5.js';
@@ -69,10 +69,12 @@ type BrowserRunner = {
69
69
  };
70
70
 
71
71
  /** Result of probing a site's latest PRODUCTION Netlify deploy. Every field is
72
- * nullable: a network/parse failure (or a site with no deploys yet) degrades to
73
- * all-nulls rather than throwing, so the audit always returns a verdict.
72
+ * nullable: a site with no production deploy yet degrades to all-nulls (a real
73
+ * "none" verdict). A read FAILURE is NOT represented here it surfaces as
74
+ * `{ ok: false }` on the enclosing `NetlifyDeployFetch` so it can't be mistaken
75
+ * for a verdict. See `NetlifyDeployFetch`.
74
76
  * - `state` is Netlify's raw deploy state (`ready`/`error`/`building`/…), lower-cased,
75
- * or null when it couldn't be read.
77
+ * or null when the site has no production deploy.
76
78
  * - `deployedAt` is the deploy's publish/create time (ISO), or null.
77
79
  * - `logUrl` links to the deploy (its live URL or the Netlify admin/build-log page).
78
80
  * - `errorMessage` is Netlify's `error_message` for a failed build, or null. */
@@ -82,15 +84,58 @@ type NetlifyDeployCheck = {
82
84
  logUrl: string | null;
83
85
  errorMessage: string | null;
84
86
  };
87
+ /** Outcome of probing Netlify, distinguishing "couldn't read" from "read OK".
88
+ * This distinction is load-bearing: `Deploy status` drives the cockpit's Broken
89
+ * band (an ALARM), so a transient read failure must NOT be allowed to clear a
90
+ * real `error` — unlike a degraded "no deploy yet", which is a real verdict.
91
+ * - `{ ok: false }` — the API was unreachable/unreadable (network error, non-2xx,
92
+ * malformed body). The caller persists nothing, leaving the prior status intact.
93
+ * - `{ ok: true, deploy }` — the API answered. `deploy` is the latest production
94
+ * deploy, or all-nulls when the site genuinely has no production deploy yet
95
+ * (a real verdict, safe to persist). */
96
+ type NetlifyDeployFetch = {
97
+ ok: false;
98
+ } | {
99
+ ok: true;
100
+ deploy: NetlifyDeployCheck;
101
+ };
85
102
  /** Injected IO so the check is unit-testable without hitting the Netlify API.
86
- * `fetchLatestProductionDeploy` returns the parsed first (latest) production
87
- * deploy object, or null when the site has none / the call failed. `now` is the
88
- * clock for the checked-at stamp. */
103
+ * `fetchLatestProductionDeploy` returns a `NetlifyDeployFetch` `{ ok: false }`
104
+ * when the call couldn't be read, else `{ ok: true, deploy }` with the latest
105
+ * production deploy (or all-nulls for a site with none). `now` is the clock for
106
+ * the checked-at stamp. */
89
107
  type NetlifyDeployDeps = {
90
- fetchLatestProductionDeploy: (siteId: string) => Promise<NetlifyDeployCheck | null>;
108
+ fetchLatestProductionDeploy: (siteId: string) => Promise<NetlifyDeployFetch>;
91
109
  now: Date;
92
110
  };
93
111
 
112
+ /** One GitHub Dependabot alert, mapped to the fields the security audit needs. */
113
+ type DependabotAlert = {
114
+ /** The vulnerable package name (`dependency.package.name`). */
115
+ package: string;
116
+ /** GitHub's raw severity vocabulary: "low" | "medium" | "high" | "critical". The audit layer
117
+ * maps "medium" → the app's "moderate"; the rest pass through. */
118
+ severity: string;
119
+ /** Human advisory summary (`security_advisory.summary`). */
120
+ summary: string;
121
+ /** CVE id from the advisory's `cve_id`, as a 0- or 1-element array. Empty for GHSA-only
122
+ * advisories (no CVE assigned) — those have no inline identifier here, but `url` still links
123
+ * to the GHSA advisory page. (The GitHub `identifiers[]` array is intentionally not surfaced.) */
124
+ cves: string[];
125
+ /** Link to the alert (`html_url`), or null. */
126
+ url: string | null;
127
+ /** Dependency graph scope, or null when GitHub doesn't classify it. "development" marks a
128
+ * build-time-only dependency (lower live-exploit surface for a static site). */
129
+ scope: "runtime" | "development" | null;
130
+ };
131
+
132
+ /** The Dependabot fetch the security audit needs, injectable for tests. The real default is
133
+ * built from GITHUB_TOKEN; absent token (or a site with no gitRepo) → pnpm/npm audit fallback. */
134
+ type DependabotDeps = {
135
+ listAlerts: (repo: string) => Promise<DependabotAlert[]>;
136
+ };
137
+ declare function securityAudit(ctx: AuditContext): Promise<AuditResult>;
138
+
94
139
  type AuditContext = {
95
140
  site: Site;
96
141
  spawn?: SpawnFn;
@@ -104,14 +149,15 @@ type AuditContext = {
104
149
  browserRunner?: BrowserRunner;
105
150
  /** Netlify API injection for the netlify-deploy audit (tests). Defaults to a real API call. */
106
151
  netlifyDeployDeps?: NetlifyDeployDeps;
152
+ /** GitHub Dependabot fetch injection for the security audit (tests). Defaults to a real client
153
+ * from GITHUB_TOKEN; absent token or no site.gitRepo → the pnpm/npm audit fallback. */
154
+ dependabotDeps?: DependabotDeps;
107
155
  };
108
156
 
109
157
  declare function depsAudit(ctx: AuditContext): Promise<AuditResult>;
110
158
 
111
159
  declare function lintAudit(ctx: AuditContext): Promise<AuditResult>;
112
160
 
113
- declare function securityAudit(ctx: AuditContext): Promise<AuditResult>;
114
-
115
161
  declare function lighthouseAudit(ctx: AuditContext): Promise<AuditResult>;
116
162
 
117
163
  declare function a11yAudit(ctx: AuditContext): Promise<AuditResult>;
@@ -307,6 +353,7 @@ type WebsiteRow = {
307
353
  gitRepo: string | null;
308
354
  reportRecipientsTo: string | null;
309
355
  reportRecipientsCc: string | null;
356
+ acceptedWatchConditions: string[];
310
357
  /** First attachment in the Header image field (Airtable's signed URL — fetch before expiry). */
311
358
  headerImage: {
312
359
  url: string;
@@ -396,6 +443,10 @@ type SecurityAdvisory = {
396
443
  title: string;
397
444
  cves: string[];
398
445
  url: string | null;
446
+ /** GitHub Dependabot dependency scope ("runtime" | "development"), when known. Lets the
447
+ * dashboard flag build-time-only ("development") vulns. Omitted for advisories from the
448
+ * lockfile `pnpm audit` fallback, which carries no per-package graph scope. */
449
+ scope?: "runtime" | "development";
399
450
  };
400
451
 
401
452
  type ResolvedCopy = {
@@ -776,6 +827,9 @@ type SiteCard = {
776
827
  watchReasons: string[];
777
828
  /** Structured watch tags ("lighthouse" / "stale") for the client filter. */
778
829
  watchSignals: string[];
830
+ /** Watch reasons the operator has accepted: suppressed from the band, shown as a
831
+ * muted chip. Populated whenever the underlying condition is currently active. */
832
+ acceptedReasons: string[];
779
833
  /** Count of NEW submissions for this site (optional; populated by buildCockpitModel). */
780
834
  newSubmissions?: number;
781
835
  };