@reddoorla/maintenance 0.90.0 → 0.91.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 (108) hide show
  1. package/dist/{announce-NDTLRQCN.js → announce-6OS4C57D.js} +3 -3
  2. package/dist/{blux-YGBGS24U.js → blux-NXQLMS4W.js} +4 -4
  3. package/dist/blux-NXQLMS4W.js.map +1 -0
  4. package/dist/{chunk-WS6NU475.js → chunk-22GKJO5F.js} +2 -2
  5. package/dist/{chunk-NYUYNYQL.js → chunk-7MKT4I5T.js} +15 -1
  6. package/dist/chunk-7MKT4I5T.js.map +1 -0
  7. package/dist/{chunk-DXWLWR4Q.js → chunk-A6T2R63Z.js} +11 -2
  8. package/dist/chunk-A6T2R63Z.js.map +1 -0
  9. package/dist/{chunk-W45FZEGE.js → chunk-DMPHP7UP.js} +2 -2
  10. package/dist/{chunk-KGUKL3CV.js → chunk-E22WAIF2.js} +2 -2
  11. package/dist/{chunk-VBEKL445.js → chunk-FCS36FDT.js} +2 -2
  12. package/dist/{chunk-5PWB3JHJ.js → chunk-H6BLTEFN.js} +27 -8
  13. package/dist/chunk-H6BLTEFN.js.map +1 -0
  14. package/dist/{chunk-ZOCJBJQV.js → chunk-MVDMKKBN.js} +2 -2
  15. package/dist/{chunk-MFHS7IQ2.js → chunk-OCNSIBMP.js} +27 -1
  16. package/dist/{chunk-MFHS7IQ2.js.map → chunk-OCNSIBMP.js.map} +1 -1
  17. package/dist/{chunk-DFLN2KO3.js → chunk-QD427NIO.js} +2 -2
  18. package/dist/chunk-RQLXEMEE.js +496 -0
  19. package/dist/chunk-RQLXEMEE.js.map +1 -0
  20. package/dist/{chunk-DCDF5A7N.js → chunk-UXJZU23T.js} +3 -3
  21. package/dist/chunk-WVVT6SJK.js +3382 -0
  22. package/dist/chunk-WVVT6SJK.js.map +1 -0
  23. package/dist/{chunk-O3GT46R2.js → chunk-YLYXW5PH.js} +14 -2
  24. package/dist/{chunk-O3GT46R2.js.map → chunk-YLYXW5PH.js.map} +1 -1
  25. package/dist/cli/bin.js +20 -17
  26. package/dist/cli/bin.js.map +1 -1
  27. package/dist/cli/commands/audit.js +7 -7
  28. package/dist/client-M2V6FHH5.js +11 -0
  29. package/dist/configs/eslint.js +5 -1
  30. package/dist/configs/eslint.js.map +1 -1
  31. package/dist/configs/playwright-a11y.js +1 -1
  32. package/dist/{db-LOSJ74ZJ.js → db-IMMQSRAB.js} +9 -9
  33. package/dist/{digest-CS6KLCZB.js → digest-EOA4JLTV.js} +13 -13
  34. package/dist/{digest-collectors-NI2M3UTC.js → digest-collectors-ZA3BTS3R.js} +7 -7
  35. package/dist/{email-LJQT5J7F.js → email-PLPDJUDH.js} +4 -3
  36. package/dist/{email-LJQT5J7F.js.map → email-PLPDJUDH.js.map} +1 -1
  37. package/dist/{ensure-site-FFDUMY6E.js → ensure-site-SF6KICTN.js} +2 -2
  38. package/dist/{forms-notify-target-IHJX7NWA.js → forms-notify-target-YCZUAHTY.js} +2 -2
  39. package/dist/{github-signals-FCLNHKMF.js → github-signals-3WLIJ5RR.js} +5 -5
  40. package/dist/{header-image-7A7XLVEU.js → header-image-DOCDFFRO.js} +2 -2
  41. package/dist/{health-mirror-GMSF3Z5R.js → health-mirror-55RRU6BI.js} +3 -3
  42. package/dist/index.js +11 -11
  43. package/dist/{init-S4MJ3Y6W.js → init-NXZ4NXTL.js} +5 -5
  44. package/dist/{launch-7FT4EYEO.js → launch-WPEQ3TY2.js} +5 -5
  45. package/dist/migrate-K4JETR36.js +7 -0
  46. package/dist/{orchestrate-6HROYTM6.js → orchestrate-KMQIUP3H.js} +5 -5
  47. package/dist/pipeline-HQJYZ443.js +29 -0
  48. package/dist/{preflight-LIN5NCYU.js → preflight-VPHQYWTY.js} +6 -6
  49. package/dist/{prismic-models-VNGIKS2B.js → prismic-models-FPDSUXDO.js} +2 -2
  50. package/dist/prospect/types.d.ts +792 -2
  51. package/dist/prospect/types.js +8 -0
  52. package/dist/{prospect-audit-JCWTKJTS.js → prospect-audit-TFIXSTE5.js} +27 -6
  53. package/dist/prospect-audit-TFIXSTE5.js.map +1 -0
  54. package/dist/{prospect-audits-3PMIO73D.js → prospect-audits-RVYEA4KH.js} +6 -4
  55. package/dist/recipes/sync-configs.js +1 -1
  56. package/dist/render-3GYW2J34.js +11 -0
  57. package/dist/{report-AAY2HZZR.js → report-Y4LXDHRV.js} +11 -11
  58. package/dist/{report-mirror-GMNSH5HF.js → report-mirror-6ZDCAQZB.js} +3 -3
  59. package/dist/{schema-75PJECAE.js → schema-2V7BGYVT.js} +1 -1
  60. package/dist/{schema-75PJECAE.js.map → schema-2V7BGYVT.js.map} +1 -1
  61. package/dist/{selftest-CUX2FIQD.js → selftest-IFGSJP4X.js} +5 -5
  62. package/dist/{site-mirror-GCFLBBWK.js → site-mirror-3E7JYTJG.js} +3 -3
  63. package/dist/{submissions-7LGJJSDL.js → submissions-UMJS3YST.js} +2 -2
  64. package/dist/{sync-configs-ZYLTMUNX.js → sync-configs-PVRLZRKL.js} +2 -2
  65. package/package.json +2 -1
  66. package/dist/blux-YGBGS24U.js.map +0 -1
  67. package/dist/chunk-5PWB3JHJ.js.map +0 -1
  68. package/dist/chunk-DXKF552B.js +0 -1630
  69. package/dist/chunk-DXKF552B.js.map +0 -1
  70. package/dist/chunk-DXWLWR4Q.js.map +0 -1
  71. package/dist/chunk-NYUYNYQL.js.map +0 -1
  72. package/dist/client-JYKPVCJX.js +0 -11
  73. package/dist/migrate-YITCXBLS.js +0 -7
  74. package/dist/pipeline-MQJD4CKN.js +0 -16
  75. package/dist/prospect-audit-JCWTKJTS.js.map +0 -1
  76. package/dist/render-P3SBUFVB.js +0 -10
  77. /package/dist/{announce-NDTLRQCN.js.map → announce-6OS4C57D.js.map} +0 -0
  78. /package/dist/{chunk-WS6NU475.js.map → chunk-22GKJO5F.js.map} +0 -0
  79. /package/dist/{chunk-W45FZEGE.js.map → chunk-DMPHP7UP.js.map} +0 -0
  80. /package/dist/{chunk-KGUKL3CV.js.map → chunk-E22WAIF2.js.map} +0 -0
  81. /package/dist/{chunk-VBEKL445.js.map → chunk-FCS36FDT.js.map} +0 -0
  82. /package/dist/{chunk-ZOCJBJQV.js.map → chunk-MVDMKKBN.js.map} +0 -0
  83. /package/dist/{chunk-DFLN2KO3.js.map → chunk-QD427NIO.js.map} +0 -0
  84. /package/dist/{chunk-DCDF5A7N.js.map → chunk-UXJZU23T.js.map} +0 -0
  85. /package/dist/{client-JYKPVCJX.js.map → client-M2V6FHH5.js.map} +0 -0
  86. /package/dist/{db-LOSJ74ZJ.js.map → db-IMMQSRAB.js.map} +0 -0
  87. /package/dist/{digest-CS6KLCZB.js.map → digest-EOA4JLTV.js.map} +0 -0
  88. /package/dist/{digest-collectors-NI2M3UTC.js.map → digest-collectors-ZA3BTS3R.js.map} +0 -0
  89. /package/dist/{ensure-site-FFDUMY6E.js.map → ensure-site-SF6KICTN.js.map} +0 -0
  90. /package/dist/{forms-notify-target-IHJX7NWA.js.map → forms-notify-target-YCZUAHTY.js.map} +0 -0
  91. /package/dist/{github-signals-FCLNHKMF.js.map → github-signals-3WLIJ5RR.js.map} +0 -0
  92. /package/dist/{header-image-7A7XLVEU.js.map → header-image-DOCDFFRO.js.map} +0 -0
  93. /package/dist/{health-mirror-GMSF3Z5R.js.map → health-mirror-55RRU6BI.js.map} +0 -0
  94. /package/dist/{init-S4MJ3Y6W.js.map → init-NXZ4NXTL.js.map} +0 -0
  95. /package/dist/{launch-7FT4EYEO.js.map → launch-WPEQ3TY2.js.map} +0 -0
  96. /package/dist/{migrate-YITCXBLS.js.map → migrate-K4JETR36.js.map} +0 -0
  97. /package/dist/{orchestrate-6HROYTM6.js.map → orchestrate-KMQIUP3H.js.map} +0 -0
  98. /package/dist/{pipeline-MQJD4CKN.js.map → pipeline-HQJYZ443.js.map} +0 -0
  99. /package/dist/{preflight-LIN5NCYU.js.map → preflight-VPHQYWTY.js.map} +0 -0
  100. /package/dist/{prismic-models-VNGIKS2B.js.map → prismic-models-FPDSUXDO.js.map} +0 -0
  101. /package/dist/{prospect-audits-3PMIO73D.js.map → prospect-audits-RVYEA4KH.js.map} +0 -0
  102. /package/dist/{render-P3SBUFVB.js.map → render-3GYW2J34.js.map} +0 -0
  103. /package/dist/{report-AAY2HZZR.js.map → report-Y4LXDHRV.js.map} +0 -0
  104. /package/dist/{report-mirror-GMNSH5HF.js.map → report-mirror-6ZDCAQZB.js.map} +0 -0
  105. /package/dist/{selftest-CUX2FIQD.js.map → selftest-IFGSJP4X.js.map} +0 -0
  106. /package/dist/{site-mirror-GCFLBBWK.js.map → site-mirror-3E7JYTJG.js.map} +0 -0
  107. /package/dist/{submissions-7LGJJSDL.js.map → submissions-UMJS3YST.js.map} +0 -0
  108. /package/dist/{sync-configs-ZYLTMUNX.js.map → sync-configs-PVRLZRKL.js.map} +0 -0
@@ -1 +1,9 @@
1
+ import {
2
+ GOAL_LABELS,
3
+ orderRequirements
4
+ } from "../chunk-RQLXEMEE.js";
5
+ export {
6
+ GOAL_LABELS,
7
+ orderRequirements
8
+ };
1
9
  //# sourceMappingURL=types.js.map
@@ -21,6 +21,18 @@ function reportPrintUrl(token, raw = process.env.REPORT_BASE_URL) {
21
21
  }
22
22
 
23
23
  // src/cli/commands/prospect-audit.ts
24
+ var GOALS = [
25
+ "book",
26
+ "enquire",
27
+ "call",
28
+ "visit",
29
+ "buy",
30
+ "demo",
31
+ "partner"
32
+ ];
33
+ function isSiteGoal(value) {
34
+ return GOALS.includes(value);
35
+ }
24
36
  function fail(message) {
25
37
  return { output: message, code: 2 };
26
38
  }
@@ -97,17 +109,25 @@ async function runProspectAuditCommand(url, opts) {
97
109
  "No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials."
98
110
  );
99
111
  }
100
- const { runProspectAudit } = await import("./pipeline-MQJD4CKN.js");
112
+ const { runProspectAudit } = await import("./pipeline-HQJYZ443.js");
101
113
  const onStage = (name, status, detail) => {
102
114
  if (status === "start") console.error(`\u2026 ${name}`);
103
115
  else if (status === "ok") console.error(`\u2713 ${name}`);
104
116
  else console.error(`! ${name} \u2014 ${detail ?? "failed"}`);
105
117
  };
106
118
  const business = opts.business?.trim();
119
+ const goal = opts.goal?.trim();
120
+ if (goal !== void 0 && goal !== "" && !isSiteGoal(goal)) {
121
+ return fail(`--goal must be one of: ${GOALS.join(", ")}`);
122
+ }
107
123
  const result = await runProspectAudit(
108
124
  url,
109
125
  {
110
126
  ...business ? { business } : {},
127
+ // An empty string is what a blank dispatch field sends; it must read as
128
+ // "not supplied" so the model's own inference still runs, rather than
129
+ // overriding it with nothing.
130
+ ...goal ? { goal } : {},
111
131
  ...opts.competitors ? {
112
132
  competitors: opts.competitors.split(",").map((c) => c.trim()).filter(Boolean)
113
133
  } : {},
@@ -115,7 +135,7 @@ async function runProspectAuditCommand(url, opts) {
115
135
  },
116
136
  { ...opts.deps ?? {}, onStage }
117
137
  );
118
- const { renderProspectReport } = await import("./render-P3SBUFVB.js");
138
+ const { renderProspectReport } = await import("./render-3GYW2J34.js");
119
139
  const html = renderProspectReport(result);
120
140
  const warnings = [];
121
141
  let file = null;
@@ -132,8 +152,8 @@ async function runProspectAuditCommand(url, opts) {
132
152
  let auditId = null;
133
153
  if (canPersist) {
134
154
  try {
135
- const { openDb, readDbConfig } = await import("./client-JYKPVCJX.js");
136
- const { createProspectAudit } = await import("./prospect-audits-3PMIO73D.js");
155
+ const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
156
+ const { createProspectAudit } = await import("./prospect-audits-RVYEA4KH.js");
137
157
  const db = await openDb(readDbConfig());
138
158
  const created = await createProspectAudit(db, {
139
159
  url: result.url,
@@ -167,7 +187,7 @@ async function runProspectAuditCommand(url, opts) {
167
187
  let email = null;
168
188
  if (opts.email) {
169
189
  try {
170
- const { sendAuditEmail, parseProspectAuditRecipients } = await import("./email-LJQT5J7F.js");
190
+ const { sendAuditEmail, parseProspectAuditRecipients } = await import("./email-PLPDJUDH.js");
171
191
  const recipients = parseProspectAuditRecipients(process.env.PROSPECT_AUDIT_RECIPIENTS);
172
192
  email = await sendAuditEmail(result, { link, recipients, auditId, pdf });
173
193
  if (!email.sent) {
@@ -190,6 +210,7 @@ async function runProspectAuditCommand(url, opts) {
190
210
  return { output: summarize(result, link, file, warnings, recovery, email), code };
191
211
  }
192
212
  export {
213
+ GOALS,
193
214
  runProspectAuditCommand
194
215
  };
195
- //# sourceMappingURL=prospect-audit-JCWTKJTS.js.map
216
+ //# sourceMappingURL=prospect-audit-TFIXSTE5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/prospect-audit.ts","../src/prospect/report-url.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { hostnameOf, isHttpUrl, isPrivateOrLoopbackHost } from \"../../util/url.js\";\nimport { reportUrl, reportPrintUrl } from \"../../prospect/report-url.js\";\nimport type { ProspectAuditStatus } from \"../../db/prospect-audits.js\";\nimport type { PipelineDeps, StageName } from \"../../prospect/pipeline.js\";\nimport type { ProspectAuditResult } from \"../../prospect/types.js\";\nimport type { SendAuditEmailResult } from \"../../prospect/email.js\";\nimport type { SiteGoal } from \"../../prospect/goals.js\";\n\n/** The operator-selectable goals, in the order the dispatch dropdown lists\n * them. `unknown` is deliberately absent: it is a finding the audit can reach\n * on its own, never something an operator would choose. */\nexport const GOALS = [\n \"book\",\n \"enquire\",\n \"call\",\n \"visit\",\n \"buy\",\n \"demo\",\n \"partner\",\n] as const satisfies readonly SiteGoal[];\n\nfunction isSiteGoal(value: string): value is SiteGoal {\n return (GOALS as readonly string[]).includes(value);\n}\n\nexport type ProspectAuditCliOptions = {\n business?: string;\n /**\n * The one action the site should produce from a visitor, chosen by the\n * operator instead of inferred from the site.\n *\n * Worth overriding because the operator knows things the site does not say.\n * A practice that wants online bookings but has never built one reads as\n * \"call\" from its own pages, and grading it as \"call\" would score it well for\n * doing the thing they are trying to stop doing.\n */\n goal?: string;\n /** Comma-separated competitor domains. */\n competitors?: string;\n /** cac sets this false for `--no-probes`. */\n probes?: boolean;\n out?: string;\n json?: boolean;\n /**\n * Email the internal sheet (scores, what wasn't measured and why, top\n * fixes, and the shareable link) to `PROSPECT_AUDIT_RECIPIENTS`, once the\n * report is rendered and the `--out`/Turso delivery attempts finish. A\n * missing/empty recipients var, or a Resend failure, is a warning — never a\n * crash — because by the time this runs the audit is already delivered (or\n * recovered) by the code above it.\n */\n email?: boolean;\n /** Test seam: injected pipeline deps. Never set from the CLI. */\n deps?: PipelineDeps;\n};\n\ntype RecoveryWrite = { htmlPath: string; jsonPath: string };\n\nfunction fail(message: string): { output: string; code: number } {\n return { output: message, code: 2 };\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction scoreLine(label: string, value: number | null): string {\n return `${label.padEnd(14)} ${value === null ? \"not measured\" : String(value).padStart(3)}`;\n}\n\n/** Item 3: the `status` column exists to answer, without deserializing the\n * whole result_json, whether this was a clean run or a degraded one. crawl\n * is excluded — it's the one fatal stage (see pipeline.ts), so by the time a\n * ProspectAuditResult exists it was always ok. Every other stage's\n * StageResult already collapses \"failed\" and \"deliberately skipped\"\n * (--no-probes, the checks→analyze cascade) into the same `ok: false` — both\n * mean the report has a \"not measured\" section, so both count as partial\n * here too. */\nfunction auditStatus(result: ProspectAuditResult): ProspectAuditStatus {\n const allStagesOk =\n result.checks.ok && result.lighthouse.ok && result.analyze.ok && result.probes.ok;\n return allStagesOk ? \"complete\" : \"partial\";\n}\n\n/** Filesystem-safe, never empty. Collapses anything outside [a-z0-9.-] to a\n * single dash so a hostname becomes a sane filename fragment. */\nfunction slugify(s: string): string {\n const slug = s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"prospect\";\n}\n\n/**\n * Last-resort write when NEITHER `--out` nor the database persist made it —\n * the one situation a paid, already-run audit must never end in silently. Lands\n * in `reports/`, mirroring header-image.ts's own convention for CLI-written\n * artifacts (mkdir -p, resolved against cwd), rather than a bare file dropped\n * into whatever directory the operator happened to be standing in when they\n * ran the command — often unrelated to this repo at all in fleet-tool usage.\n */\nasync function writeRecoveryFiles(\n result: ProspectAuditResult,\n html: string,\n): Promise<RecoveryWrite> {\n const dir = resolve(\"reports\");\n await mkdir(dir, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const base = `prospect-audit-${slugify(hostnameOf(result.url))}-${stamp}`;\n const htmlPath = resolve(dir, `${base}.html`);\n const jsonPath = resolve(dir, `${base}.json`);\n await writeFile(htmlPath, html, \"utf-8\");\n await writeFile(jsonPath, JSON.stringify(result, null, 2), \"utf-8\");\n return { htmlPath, jsonPath };\n}\n\nfunction summarize(\n result: ProspectAuditResult,\n link: string | null,\n file: string | null,\n warnings: string[],\n recovery: RecoveryWrite | null,\n email: SendAuditEmailResult | null,\n): string {\n const lines = [\n `Prospect audit — ${result.businessName ?? result.url}`,\n \"\",\n scoreLine(\"Findability\", result.scores.findability),\n scoreLine(\"Readability\", result.scores.readability),\n scoreLine(\"Answers\", result.scores.answers),\n // No AI Visibility line: the score was removed from every presentation\n // surface (web, print, email, here) — the measurement lives on as the\n // report's receipts section, not as a number that reads as ours to move.\n ];\n for (const [name, stage] of [\n [\"checks\", result.checks],\n [\"lighthouse\", result.lighthouse],\n [\"analyze\", result.analyze],\n [\"probes\", result.probes],\n ] as const) {\n if (!stage.ok) lines.push(` ! ${name} not measured — ${stage.error}`);\n }\n if (file) lines.push(\"\", `Report written to ${file}`);\n if (link) lines.push(\"\", `Shareable link: ${link}`);\n if (email?.sent) {\n lines.push(\"\", `Emailed to ${email.recipients.join(\", \")} (${email.messageId})`);\n }\n for (const w of warnings) lines.push(\"\", `! ${w}`);\n if (recovery) {\n lines.push(\n \"\",\n \"Neither --out nor the database took this report — wrote a recovery copy instead:\",\n ` ${recovery.htmlPath}`,\n ` ${recovery.jsonPath}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Run one prospect audit end to end. Progress goes to stderr so `--json` stdout\n * stays pipeable. Persistence needs Turso; without it `--out` is mandatory,\n * because an audit nobody can read afterwards is just a bill.\n *\n * By the time `runProspectAudit` resolves, the money is already spent — only\n * the crawl is fatal, every other stage degrades rather than throwing. So the\n * `--out` write and the database persist below are each wrapped in their own\n * try/catch and attempted independently: a bad `--out` path must not cost a\n * working Turso persist (or vice versa), and if BOTH fail — or persist fails\n * with no `--out` given at all — a recovery copy goes to `reports/` rather\n * than the run ending with nothing anywhere. `--json` only changes the SHAPE\n * of stdout; it still runs the same `--out`/persist/recovery logic beneath it.\n */\nexport async function runProspectAuditCommand(\n url: string,\n opts: ProspectAuditCliOptions,\n): Promise<{ output: string; code: number }> {\n if (!isHttpUrl(url)) {\n return fail(`\"${url}\" is not a URL. Pass the full address, e.g. https://example.com`);\n }\n // #612 review: this validated only the SHAPE. The sole private-address guard\n // was in `triggerProspectAudit` — one layer at the far end of the chain — so\n // anyone running the CLI directly, or any future second caller, had none.\n // `crawlSite` now refuses too; this keeps the refusal a clean message instead\n // of a thrown crawl error.\n if (isPrivateOrLoopbackHost(new URL(url).hostname)) {\n return fail(`\"${url}\" is a private or loopback address — refusing to audit it.`);\n }\n const canPersist = Boolean(process.env.TURSO_DATABASE_URL);\n if (!canPersist && !opts.out) {\n return fail(\n \"No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials.\",\n );\n }\n\n const { runProspectAudit } = await import(\"../../prospect/pipeline.js\");\n const onStage = (name: StageName, status: \"start\" | \"ok\" | \"fail\", detail?: string): void => {\n if (status === \"start\") console.error(`… ${name}`);\n else if (status === \"ok\") console.error(`✓ ${name}`);\n else console.error(`! ${name} — ${detail ?? \"failed\"}`);\n };\n\n const business = opts.business?.trim();\n const goal = opts.goal?.trim();\n if (goal !== undefined && goal !== \"\" && !isSiteGoal(goal)) {\n return fail(`--goal must be one of: ${GOALS.join(\", \")}`);\n }\n\n const result = await runProspectAudit(\n url,\n {\n ...(business ? { business } : {}),\n // An empty string is what a blank dispatch field sends; it must read as\n // \"not supplied\" so the model's own inference still runs, rather than\n // overriding it with nothing.\n ...(goal ? { goal } : {}),\n ...(opts.competitors\n ? {\n competitors: opts.competitors\n .split(\",\")\n .map((c) => c.trim())\n .filter(Boolean),\n }\n : {}),\n ...(opts.probes === false ? { probes: false } : {}),\n },\n { ...(opts.deps ?? {}), onStage },\n );\n\n const { renderProspectReport } = await import(\"../../prospect/render.js\");\n const html = renderProspectReport(result);\n\n const warnings: string[] = [];\n\n let file: string | null = null;\n if (opts.out) {\n try {\n await writeFile(opts.out, html, \"utf-8\");\n file = opts.out;\n } catch (err) {\n warnings.push(`Could not write --out (${opts.out}): ${errorMessage(err)}`);\n }\n }\n\n let link: string | null = null;\n let token: string | null = null;\n let auditId: string | null = null;\n if (canPersist) {\n try {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n const { createProspectAudit } = await import(\"../../db/prospect-audits.js\");\n const db = await openDb(readDbConfig());\n const created = await createProspectAudit(db, {\n url: result.url,\n // Map at the boundary: ProspectAuditResult.businessName is the field\n // name (Item 2 — it's a resolved NAME, not a description); the\n // `prospect_audits.business` column keeps its existing name, since\n // renaming a column needs its own migration for no benefit here.\n business: result.businessName,\n status: auditStatus(result),\n resultJson: JSON.stringify(result),\n });\n auditId = created.id;\n token = created.token;\n link = reportUrl(token);\n } catch (err) {\n warnings.push(`Could not save to the database: ${errorMessage(err)}`);\n }\n }\n\n const delivered = file !== null || link !== null;\n const recovery = delivered ? null : await writeRecoveryFiles(result, html);\n const code = delivered ? 0 : 1;\n\n // The audit is already delivered (or recovered) above — an email failure\n // must never take that away. Same independent try/catch shape as the\n // --out write and the Turso persist: record a warning, keep going, never\n // touch `code`. Dynamically imported for the same reason db/client.js and\n // prospect/render.js are above: it reaches `resend` (a devDependency) and\n // must stay out of bin.js's static import graph (see bin.ts's\n // central-dep-blocker comment).\n // The PDF leave-behind, printed from the website's print route — a document\n // designed for paper, not the interactive report with its evidence folded\n // away behind disclosures.\n //\n // Best-effort by design. Every other stage in this pipeline degrades rather\n // than throwing, and an attachment is not worth losing a delivered report\n // over: if this fails the email still goes, with the link, and a warning says\n // the PDF is missing. It also needs a token — with no persisted report there\n // is no page to print.\n let pdf: Buffer | null = null;\n if (opts.email && token) {\n try {\n const { renderReportPdf } = await import(\"../../prospect/pdf.js\");\n pdf = await renderReportPdf(reportPrintUrl(token));\n } catch (err) {\n warnings.push(`Could not attach the PDF: ${errorMessage(err)}`);\n }\n }\n\n let email: SendAuditEmailResult | null = null;\n if (opts.email) {\n try {\n const { sendAuditEmail, parseProspectAuditRecipients } =\n await import(\"../../prospect/email.js\");\n const recipients = parseProspectAuditRecipients(process.env.PROSPECT_AUDIT_RECIPIENTS);\n email = await sendAuditEmail(result, { link, recipients, auditId, pdf });\n if (!email.sent) {\n warnings.push(`Could not email the audit sheet: ${email.reason}`);\n }\n } catch (err) {\n warnings.push(`Could not email the audit sheet: ${errorMessage(err)}`);\n }\n }\n\n if (opts.json) {\n return {\n output: JSON.stringify(\n { result, out: file, link, token, recovery, warnings, email },\n null,\n 2,\n ),\n code,\n };\n }\n\n return { output: summarize(result, link, file, warnings, recovery, email), code };\n}\n","/** Where the prospect-facing report lives now: a real, branded route on the\n * marketing site rather than generated HTML on the ops app's domain. */\nexport const DEFAULT_REPORT_BASE_URL = \"https://reddoorla.com\";\n\n/**\n * Resolve the public report origin.\n *\n * Deliberately its own variable rather than reusing `DASHBOARD_BASE_URL`. That\n * one addresses operators and points at the ops app; this one addresses a\n * prospect and points at reddoorla.com. They are different audiences on\n * different domains, and sharing a variable would mean one of them silently\n * moving the day the other is repointed.\n */\nexport function resolveReportBaseUrl(raw: string | undefined): string {\n return (raw?.trim() || DEFAULT_REPORT_BASE_URL).replace(/\\/$/, \"\");\n}\n\n/** The link a prospect opens. */\nexport function reportUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${resolveReportBaseUrl(raw)}/audit/${token}`;\n}\n\n/** The print-designed variant, which the runner renders to a PDF. Same token,\n * same guards — a separate document, not a stylesheet over the page. */\nexport function reportPrintUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${reportUrl(token, raw)}/print`;\n}\n"],"mappings":"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","names":[]}
@@ -5,8 +5,9 @@ import {
5
5
  getProspectAuditByToken,
6
6
  isValidToken,
7
7
  listRecentProspectAudits,
8
- newProspectAuditId
9
- } from "./chunk-NYUYNYQL.js";
8
+ newProspectAuditId,
9
+ siteKey
10
+ } from "./chunk-7MKT4I5T.js";
10
11
  export {
11
12
  MAX_RECENT_PROSPECT_AUDITS,
12
13
  createProspectAudit,
@@ -14,6 +15,7 @@ export {
14
15
  getProspectAuditByToken,
15
16
  isValidToken,
16
17
  listRecentProspectAudits,
17
- newProspectAuditId
18
+ newProspectAuditId,
19
+ siteKey
18
20
  };
19
- //# sourceMappingURL=prospect-audits-3PMIO73D.js.map
21
+ //# sourceMappingURL=prospect-audits-RVYEA4KH.js.map
@@ -3,7 +3,7 @@ import {
3
3
  isConfigName,
4
4
  planTemplateDiffs,
5
5
  syncConfigs
6
- } from "../chunk-DXWLWR4Q.js";
6
+ } from "../chunk-A6T2R63Z.js";
7
7
  import "../chunk-3G25KIWW.js";
8
8
  import "../chunk-MLT4QY5X.js";
9
9
  import "../chunk-XTK5VIQB.js";
@@ -0,0 +1,11 @@
1
+ import {
2
+ renderProspectReport
3
+ } from "./chunk-H6BLTEFN.js";
4
+ import "./chunk-WVVT6SJK.js";
5
+ import "./chunk-RQLXEMEE.js";
6
+ import "./chunk-BGTYPVLT.js";
7
+ import "./chunk-RARREDQE.js";
8
+ export {
9
+ renderProspectReport
10
+ };
11
+ //# sourceMappingURL=render-3GYW2J34.js.map
@@ -78,14 +78,14 @@ function dashboardBaseUrl() {
78
78
  }
79
79
  async function runReportCommand(slug, opts) {
80
80
  if (opts.digest) {
81
- const { runDigest } = await import("./digest-CS6KLCZB.js");
81
+ const { runDigest } = await import("./digest-EOA4JLTV.js");
82
82
  return runDigest({ baseUrl: dashboardBaseUrl() });
83
83
  }
84
84
  if (opts.sendReady) {
85
- const { sendApprovedReports } = await import("./orchestrate-6HROYTM6.js");
86
- const { makeSiteMirror } = await import("./site-mirror-GCFLBBWK.js");
85
+ const { sendApprovedReports } = await import("./orchestrate-KMQIUP3H.js");
86
+ const { makeSiteMirror } = await import("./site-mirror-3E7JYTJG.js");
87
87
  const { mirrorWrite } = await import("./freeze-A6MUCZU4.js");
88
- const { openDb, readDbConfig } = await import("./client-JYKPVCJX.js");
88
+ const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
89
89
  const { mirrorReportPatch } = await import("./fleet-state-3LQUQHYD.js");
90
90
  return sendApprovedReports({
91
91
  siteMirror: await makeSiteMirror(),
@@ -105,7 +105,7 @@ async function runReportCommand(slug, opts) {
105
105
  if (opts.rerender) {
106
106
  const { rerenderReport, formatRerenderResult } = await import("./rerender-3FM6DNRH.js");
107
107
  const { renderReportFromRow } = await import("./render-from-row-P5CWM3MQ.js");
108
- const { openDb, readDbConfig } = await import("./client-JYKPVCJX.js");
108
+ const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
109
109
  const { getReportById, getSiteById, storeRenderedHtml } = await import("./fleet-state-3LQUQHYD.js");
110
110
  const { loadHeaderImage } = await import("./header-images-SQBCE5OD.js");
111
111
  const { fetchAttachmentBytes } = await import("./attachments-NLZIJ5NU.js");
@@ -145,9 +145,9 @@ async function runReportCommand(slug, opts) {
145
145
  }
146
146
  async function runDueDraft() {
147
147
  const base = openBase(readAirtableConfig());
148
- const { makeScheduleMirrorBestEffort } = await import("./health-mirror-GMSF3Z5R.js");
149
- const { makeReportMirror } = await import("./report-mirror-GMNSH5HF.js");
150
- const { makeSiteMirror } = await import("./site-mirror-GCFLBBWK.js");
148
+ const { makeScheduleMirrorBestEffort } = await import("./health-mirror-55RRU6BI.js");
149
+ const { makeReportMirror } = await import("./report-mirror-6ZDCAQZB.js");
150
+ const { makeSiteMirror } = await import("./site-mirror-3E7JYTJG.js");
151
151
  const result = await draftDueReports(
152
152
  base,
153
153
  /* @__PURE__ */ new Date(),
@@ -337,8 +337,8 @@ async function runSingleSiteDraft(slug, opts) {
337
337
  if (!site) {
338
338
  throw Object.assign(new Error(`No Websites row matched slug "${slug}"`), { exitCode: 2 });
339
339
  }
340
- const reportMirror = opts.previewOnly ? null : await (await import("./report-mirror-GMNSH5HF.js")).makeReportMirror();
341
- const siteMirror = opts.previewOnly ? null : await (await import("./site-mirror-GCFLBBWK.js")).makeSiteMirror();
340
+ const reportMirror = opts.previewOnly ? null : await (await import("./report-mirror-6ZDCAQZB.js")).makeReportMirror();
341
+ const siteMirror = opts.previewOnly ? null : await (await import("./site-mirror-3E7JYTJG.js")).makeSiteMirror();
342
342
  const result = await draftReportForSite(opts.previewOnly ? null : base, site, opts.reportType, {
343
343
  previewOnly: opts.previewOnly,
344
344
  // Only forced on. Left undefined, draftReportForSite keeps its default
@@ -366,4 +366,4 @@ export {
366
366
  runReportCommand,
367
367
  writeNextDueDates
368
368
  };
369
- //# sourceMappingURL=report-AAY2HZZR.js.map
369
+ //# sourceMappingURL=report-Y4LXDHRV.js.map
@@ -13,8 +13,8 @@ import "./chunk-7NYN2P6I.js";
13
13
  import {
14
14
  openDb,
15
15
  readDbConfig
16
- } from "./chunk-WS6NU475.js";
17
- import "./chunk-MFHS7IQ2.js";
16
+ } from "./chunk-22GKJO5F.js";
17
+ import "./chunk-OCNSIBMP.js";
18
18
  import "./chunk-6VAL7XJT.js";
19
19
 
20
20
  // src/reports/report-mirror.ts
@@ -52,4 +52,4 @@ async function makeReportMirror(open = () => openDb(readDbConfig()), strict = TU
52
52
  export {
53
53
  makeReportMirror
54
54
  };
55
- //# sourceMappingURL=report-mirror-GMNSH5HF.js.map
55
+ //# sourceMappingURL=report-mirror-6ZDCAQZB.js.map
@@ -15,4 +15,4 @@ var DATABASE_TABLES = [
15
15
  export {
16
16
  DATABASE_TABLES
17
17
  };
18
- //# sourceMappingURL=schema-75PJECAE.js.map
18
+ //# sourceMappingURL=schema-2V7BGYVT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db/schema.ts"],"sourcesContent":["// The hand-written shape Kysely types every query from. SQLite stores TEXT/INTEGER\n// only, so these are the column *shapes* — the value domains (form_type, status,\n// notify_status) are still narrowed at read time by the validators in\n// src/reports/submission-row.ts. Keep this in lockstep with src/db/migrations.ts;\n// drift is caught by the in-memory tests in tests/db/, not by codegen.\n\nexport interface SubmissionsTable {\n id: string;\n submission_id: number | null;\n site_id: string;\n form_type: string;\n name: string;\n email: string;\n phone: string | null;\n message: string | null;\n extra_fields: string | null;\n source_url: string | null;\n utm: string | null;\n submitted_at: string | null;\n status: string;\n notify_status: string;\n resend_message_id: string | null;\n spam_score: number | null;\n spam_reason: string | null;\n fanout_status: string | null;\n}\n\nexport interface SpamScreenoutsTable {\n site_id: string;\n date: string;\n honeypot: number;\n too_fast: number;\n marked_spam: number;\n}\n\nexport interface FleetEventsTable {\n id: string;\n ts: string;\n type: string;\n site_id: string | null;\n site_name: string | null;\n summary: string;\n data: string | null;\n created_at: string;\n}\n\nexport interface MigrationsTable {\n id: string;\n applied_at: string;\n}\n\n/** A lead whose site lookup threw mid-ingest (migration 0006). `payload` and\n * `turnstile` are JSON — the raw wire body and the verification computed at\n * receipt — exactly what `db replay-deadletters` feeds back through\n * `ingestSubmission` once the lookup recovers. */\nexport interface SubmissionDeadletterTable {\n id: string;\n site_slug: string;\n payload: string;\n turnstile: string;\n error: string;\n received_at: string;\n replayed_at: string | null;\n replay_outcome: string | null;\n replay_submission_id: string | null;\n}\n\n/** Operator-owned fleet config (migration 0007). PK = Airtable rec id (D1).\n * `legacy` holds the 33 populated-but-code-unreferenced Airtable columns as a\n * JSON object keyed by original column name; the plaintext DNS/cms credential\n * cells never migrate (operator ruling 2026-08-23). */\nexport interface SitesTable {\n id: string;\n slug: string;\n name: string;\n url: string | null;\n status: string | null;\n point_of_contact: string | null;\n maintenance_freq: string | null;\n testing_freq: string | null;\n maintenance_day: string | null;\n testing_day: string | null;\n ga4_property_id: string | null;\n search_query: string | null;\n search_console_property: string | null;\n git_repo: string | null;\n netlify_id: string | null;\n report_recipients_to: string | null;\n report_recipients_cc: string | null;\n copy_intro: string | null;\n copy_contact: string | null;\n copy_footer: string | null;\n newsletter_webhook: string | null;\n mailchimp_api_key: string | null;\n mailchimp_audience_id: string | null;\n notify_routing: string | null;\n require_turnstile: number;\n accepted_watch_conditions: string | null;\n prismic_ack_until: string | null;\n launched_at: string | null;\n header_image: Uint8Array | null;\n header_image_filename: string | null;\n header_image_type: string | null;\n header_image_generated_at: string | null;\n legacy: string | null;\n}\n\n/** Nightly-cron-owned health/telemetry (migration 0007) — one row per site,\n * written as one batched upsert per sweep family. `analytics_soft_fail_at` is\n * the \"Analytics soft-fail at\" column code always wrote best-effort but no\n * operator ever created in Airtable. */\nexport interface SiteHealthTable {\n site_id: string;\n p_score: number | null;\n r_score: number | null;\n bp_score: number | null;\n seo_score: number | null;\n lighthouse_at: string | null;\n a11y_violations: number | null;\n deps_drifted: number | null;\n deps_major_behind: number | null;\n deps_outdated: number | null;\n deps_major_outdated: number | null;\n vulns_critical: number | null;\n vulns_high: number | null;\n vulns_moderate: number | null;\n vulns_low: number | null;\n security_audit_at: string | null;\n security_advisories: string | null;\n auto_fix_attempts: number | null;\n analytics_soft_fail_at: string | null;\n cert_days_remaining: number | null;\n domain_checked_at: string | null;\n deploy_status: string | null;\n last_deploy_at: string | null;\n deploy_log_url: string | null;\n deploy_checked_at: string | null;\n function_health: string | null;\n cms_reachable: string | null;\n turnstile_widget: string | null;\n function_health_checked_at: string | null;\n crossbrowser_ok: number | null;\n mobile_ok: number | null;\n links_ok: number | null;\n broken_links: number | null;\n browser_checked_at: string | null;\n uptime_reachable: string | null;\n titles_meta_ok: string | null;\n smoke_ok: string | null;\n last_smoke_at: string | null;\n form_e2e_ok: string | null;\n form_e2e_checked_at: string | null;\n renovate_failing_cis: number | null;\n default_branch_ci: string | null;\n last_commit_at: string | null;\n github_signals_at: string | null;\n prismic_models: string | null;\n prismic_models_checked_at: string | null;\n prismic_models_drift: string | null;\n}\n\n/** Code-derived schedule (migration 0007) — written by updateNextDueDates. */\nexport interface SiteScheduleTable {\n site_id: string;\n next_maintenance_at: string | null;\n next_testing_at: string | null;\n computed_at: string | null;\n}\n\n/** Report rows (migration 0007). PK = Airtable rec id. `checklist` is JSON\n * keyed by the STABLE checklist key (src/reports/checklist.ts), not the\n * Airtable column name; `rendered_html` is the downloaded attachment body. */\nexport interface ReportsTable {\n id: string;\n site_id: string | null;\n report_id: string | null;\n report_type: string | null;\n period: string | null;\n period_start: string | null;\n period_end: string | null;\n completed_on: string | null;\n lighthouse_performance: number | null;\n lighthouse_accessibility: number | null;\n lighthouse_best_practices: number | null;\n lighthouse_seo: number | null;\n ga_users_current: number | null;\n ga_users_previous: number | null;\n search_found_page1: number | null;\n search_position: number | null;\n last_tested_date: string | null;\n commentary: string | null;\n subject_override: string | null;\n draft_ready: number;\n approved_to_send: number;\n approved_at: string | null;\n approved_by: string | null;\n send_override: number;\n override_reason: string | null;\n override_by: string | null;\n override_at: string | null;\n sent_at: string | null;\n delivery_status: string | null;\n resend_message_id: string | null;\n checklist: string | null;\n checklist_auto_evidence: string | null;\n rendered_html: string | null;\n}\n\n/** One prospect-audit run (migration 0009). `token` is the 128-bit unguessable\n * public handle for GET /r/{token}; `result_json` holds the full\n * ProspectAuditResult (src/prospect/types.ts). */\nexport interface ProspectAuditsTable {\n id: string;\n token: string;\n url: string;\n business: string | null;\n created_at: string;\n status: string;\n result_json: string;\n}\n\n/** The digest's prior-run snapshot (migration 0011). One row, `id` = the\n * constant singleton key; `snapshot` is the whole DigestSnapshot as JSON. */\nexport interface DigestStateTable {\n id: string;\n snapshot: string;\n updated_at: string | null;\n}\n\n/** Every table the app owns, as a RUNTIME value (#612 review).\n *\n * The `Database` interface below is types-only, so nothing could assert that a\n * backup actually covered it — `tables=N` was printed by the verifier and never\n * checked, meaning a table a migration failed to create would ride green\n * forever. The compile-time check under this list makes forgetting an entry a\n * build error naming the missing table. */\nexport const DATABASE_TABLES = [\n \"submissions\",\n \"spam_screenouts\",\n \"fleet_events\",\n \"_migrations\",\n \"submission_deadletter\",\n \"sites\",\n \"site_health\",\n \"site_schedule\",\n \"reports\",\n \"prospect_audits\",\n \"digest_state\",\n] as const;\n\nexport type DatabaseTable = (typeof DATABASE_TABLES)[number];\n\nexport interface Database {\n submissions: SubmissionsTable;\n spam_screenouts: SpamScreenoutsTable;\n fleet_events: FleetEventsTable;\n _migrations: MigrationsTable;\n submission_deadletter: SubmissionDeadletterTable;\n sites: SitesTable;\n site_health: SiteHealthTable;\n site_schedule: SiteScheduleTable;\n reports: ReportsTable;\n prospect_audits: ProspectAuditsTable;\n digest_state: DigestStateTable;\n}\n\n/** Compile-time exhaustiveness: if a table is added to `Database` and not to\n * `DATABASE_TABLES`, this errors and NAMES it. */\ntype MissingFromDatabaseTables = Exclude<keyof Database, DatabaseTable>;\nconst _everyTableIsListed: MissingFromDatabaseTables extends never\n ? true\n : MissingFromDatabaseTables = true;\nvoid _everyTableIsListed;\n"],"mappings":";AA4OO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/db/schema.ts"],"sourcesContent":["// The hand-written shape Kysely types every query from. SQLite stores TEXT/INTEGER\n// only, so these are the column *shapes* — the value domains (form_type, status,\n// notify_status) are still narrowed at read time by the validators in\n// src/reports/submission-row.ts. Keep this in lockstep with src/db/migrations.ts;\n// drift is caught by the in-memory tests in tests/db/, not by codegen.\n\nexport interface SubmissionsTable {\n id: string;\n submission_id: number | null;\n site_id: string;\n form_type: string;\n name: string;\n email: string;\n phone: string | null;\n message: string | null;\n extra_fields: string | null;\n source_url: string | null;\n utm: string | null;\n submitted_at: string | null;\n status: string;\n notify_status: string;\n resend_message_id: string | null;\n spam_score: number | null;\n spam_reason: string | null;\n fanout_status: string | null;\n}\n\nexport interface SpamScreenoutsTable {\n site_id: string;\n date: string;\n honeypot: number;\n too_fast: number;\n marked_spam: number;\n}\n\nexport interface FleetEventsTable {\n id: string;\n ts: string;\n type: string;\n site_id: string | null;\n site_name: string | null;\n summary: string;\n data: string | null;\n created_at: string;\n}\n\nexport interface MigrationsTable {\n id: string;\n applied_at: string;\n}\n\n/** A lead whose site lookup threw mid-ingest (migration 0006). `payload` and\n * `turnstile` are JSON — the raw wire body and the verification computed at\n * receipt — exactly what `db replay-deadletters` feeds back through\n * `ingestSubmission` once the lookup recovers. */\nexport interface SubmissionDeadletterTable {\n id: string;\n site_slug: string;\n payload: string;\n turnstile: string;\n error: string;\n received_at: string;\n replayed_at: string | null;\n replay_outcome: string | null;\n replay_submission_id: string | null;\n}\n\n/** Operator-owned fleet config (migration 0007). PK = Airtable rec id (D1).\n * `legacy` holds the 33 populated-but-code-unreferenced Airtable columns as a\n * JSON object keyed by original column name; the plaintext DNS/cms credential\n * cells never migrate (operator ruling 2026-08-23). */\nexport interface SitesTable {\n id: string;\n slug: string;\n name: string;\n url: string | null;\n status: string | null;\n point_of_contact: string | null;\n maintenance_freq: string | null;\n testing_freq: string | null;\n maintenance_day: string | null;\n testing_day: string | null;\n ga4_property_id: string | null;\n search_query: string | null;\n search_console_property: string | null;\n git_repo: string | null;\n netlify_id: string | null;\n report_recipients_to: string | null;\n report_recipients_cc: string | null;\n copy_intro: string | null;\n copy_contact: string | null;\n copy_footer: string | null;\n newsletter_webhook: string | null;\n mailchimp_api_key: string | null;\n mailchimp_audience_id: string | null;\n notify_routing: string | null;\n require_turnstile: number;\n accepted_watch_conditions: string | null;\n prismic_ack_until: string | null;\n launched_at: string | null;\n header_image: Uint8Array | null;\n header_image_filename: string | null;\n header_image_type: string | null;\n header_image_generated_at: string | null;\n legacy: string | null;\n}\n\n/** Nightly-cron-owned health/telemetry (migration 0007) — one row per site,\n * written as one batched upsert per sweep family. `analytics_soft_fail_at` is\n * the \"Analytics soft-fail at\" column code always wrote best-effort but no\n * operator ever created in Airtable. */\nexport interface SiteHealthTable {\n site_id: string;\n p_score: number | null;\n r_score: number | null;\n bp_score: number | null;\n seo_score: number | null;\n lighthouse_at: string | null;\n a11y_violations: number | null;\n deps_drifted: number | null;\n deps_major_behind: number | null;\n deps_outdated: number | null;\n deps_major_outdated: number | null;\n vulns_critical: number | null;\n vulns_high: number | null;\n vulns_moderate: number | null;\n vulns_low: number | null;\n security_audit_at: string | null;\n security_advisories: string | null;\n auto_fix_attempts: number | null;\n analytics_soft_fail_at: string | null;\n cert_days_remaining: number | null;\n domain_checked_at: string | null;\n deploy_status: string | null;\n last_deploy_at: string | null;\n deploy_log_url: string | null;\n deploy_checked_at: string | null;\n function_health: string | null;\n cms_reachable: string | null;\n turnstile_widget: string | null;\n function_health_checked_at: string | null;\n crossbrowser_ok: number | null;\n mobile_ok: number | null;\n links_ok: number | null;\n broken_links: number | null;\n browser_checked_at: string | null;\n uptime_reachable: string | null;\n titles_meta_ok: string | null;\n smoke_ok: string | null;\n last_smoke_at: string | null;\n form_e2e_ok: string | null;\n form_e2e_checked_at: string | null;\n renovate_failing_cis: number | null;\n default_branch_ci: string | null;\n last_commit_at: string | null;\n github_signals_at: string | null;\n prismic_models: string | null;\n prismic_models_checked_at: string | null;\n prismic_models_drift: string | null;\n}\n\n/** Code-derived schedule (migration 0007) — written by updateNextDueDates. */\nexport interface SiteScheduleTable {\n site_id: string;\n next_maintenance_at: string | null;\n next_testing_at: string | null;\n computed_at: string | null;\n}\n\n/** Report rows (migration 0007). PK = Airtable rec id. `checklist` is JSON\n * keyed by the STABLE checklist key (src/reports/checklist.ts), not the\n * Airtable column name; `rendered_html` is the downloaded attachment body. */\nexport interface ReportsTable {\n id: string;\n site_id: string | null;\n report_id: string | null;\n report_type: string | null;\n period: string | null;\n period_start: string | null;\n period_end: string | null;\n completed_on: string | null;\n lighthouse_performance: number | null;\n lighthouse_accessibility: number | null;\n lighthouse_best_practices: number | null;\n lighthouse_seo: number | null;\n ga_users_current: number | null;\n ga_users_previous: number | null;\n search_found_page1: number | null;\n search_position: number | null;\n last_tested_date: string | null;\n commentary: string | null;\n subject_override: string | null;\n draft_ready: number;\n approved_to_send: number;\n approved_at: string | null;\n approved_by: string | null;\n send_override: number;\n override_reason: string | null;\n override_by: string | null;\n override_at: string | null;\n sent_at: string | null;\n delivery_status: string | null;\n resend_message_id: string | null;\n checklist: string | null;\n checklist_auto_evidence: string | null;\n rendered_html: string | null;\n}\n\n/** One prospect-audit run (migration 0009). `token` is the 128-bit unguessable\n * public handle for GET /r/{token}; `result_json` holds the full\n * ProspectAuditResult (src/prospect/types.ts). */\nexport interface ProspectAuditsTable {\n id: string;\n token: string;\n url: string;\n /** One key per site, however its address was written (migration 0013). `url`\n * records what we audited; this is what two audits of one site are grouped\n * by. Null on rows written before the column existed. See siteKey(). */\n site_key: string | null;\n business: string | null;\n created_at: string;\n status: string;\n result_json: string;\n}\n\n/** The digest's prior-run snapshot (migration 0011). One row, `id` = the\n * constant singleton key; `snapshot` is the whole DigestSnapshot as JSON. */\nexport interface DigestStateTable {\n id: string;\n snapshot: string;\n updated_at: string | null;\n}\n\n/** Every table the app owns, as a RUNTIME value (#612 review).\n *\n * The `Database` interface below is types-only, so nothing could assert that a\n * backup actually covered it — `tables=N` was printed by the verifier and never\n * checked, meaning a table a migration failed to create would ride green\n * forever. The compile-time check under this list makes forgetting an entry a\n * build error naming the missing table. */\nexport const DATABASE_TABLES = [\n \"submissions\",\n \"spam_screenouts\",\n \"fleet_events\",\n \"_migrations\",\n \"submission_deadletter\",\n \"sites\",\n \"site_health\",\n \"site_schedule\",\n \"reports\",\n \"prospect_audits\",\n \"digest_state\",\n] as const;\n\nexport type DatabaseTable = (typeof DATABASE_TABLES)[number];\n\nexport interface Database {\n submissions: SubmissionsTable;\n spam_screenouts: SpamScreenoutsTable;\n fleet_events: FleetEventsTable;\n _migrations: MigrationsTable;\n submission_deadletter: SubmissionDeadletterTable;\n sites: SitesTable;\n site_health: SiteHealthTable;\n site_schedule: SiteScheduleTable;\n reports: ReportsTable;\n prospect_audits: ProspectAuditsTable;\n digest_state: DigestStateTable;\n}\n\n/** Compile-time exhaustiveness: if a table is added to `Database` and not to\n * `DATABASE_TABLES`, this errors and NAMES it. */\ntype MissingFromDatabaseTables = Exclude<keyof Database, DatabaseTable>;\nconst _everyTableIsListed: MissingFromDatabaseTables extends never\n ? true\n : MissingFromDatabaseTables = true;\nvoid _everyTableIsListed;\n"],"mappings":";AAgPO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  isProbablyEmail,
15
15
  parseAddresses
16
- } from "./chunk-VBEKL445.js";
16
+ } from "./chunk-FCS36FDT.js";
17
17
  import "./chunk-F3JWOPTG.js";
18
18
  import "./chunk-VAD43MOR.js";
19
19
  import {
@@ -40,10 +40,10 @@ import {
40
40
  import {
41
41
  defaultResendClient
42
42
  } from "./chunk-WEKL2HYJ.js";
43
- import "./chunk-W45FZEGE.js";
44
- import "./chunk-WS6NU475.js";
43
+ import "./chunk-DMPHP7UP.js";
44
+ import "./chunk-22GKJO5F.js";
45
45
  import "./chunk-RARREDQE.js";
46
- import "./chunk-MFHS7IQ2.js";
46
+ import "./chunk-OCNSIBMP.js";
47
47
  import "./chunk-6VAL7XJT.js";
48
48
 
49
49
  // src/recipes/selftest-email.ts
@@ -172,4 +172,4 @@ async function runSelftestCommand(kind, site, opts) {
172
172
  export {
173
173
  runSelftestCommand
174
174
  };
175
- //# sourceMappingURL=selftest-CUX2FIQD.js.map
175
+ //# sourceMappingURL=selftest-IFGSJP4X.js.map
@@ -13,8 +13,8 @@ import "./chunk-7NYN2P6I.js";
13
13
  import {
14
14
  openDb,
15
15
  readDbConfig
16
- } from "./chunk-WS6NU475.js";
17
- import "./chunk-MFHS7IQ2.js";
16
+ } from "./chunk-22GKJO5F.js";
17
+ import "./chunk-OCNSIBMP.js";
18
18
  import "./chunk-6VAL7XJT.js";
19
19
 
20
20
  // src/db/site-mirror.ts
@@ -59,4 +59,4 @@ async function makeSiteMirror(open = () => openDb(readDbConfig()), strict = TURS
59
59
  export {
60
60
  makeSiteMirror
61
61
  };
62
- //# sourceMappingURL=site-mirror-GCFLBBWK.js.map
62
+ //# sourceMappingURL=site-mirror-3E7JYTJG.js.map
@@ -78,7 +78,7 @@ async function runSubmissionsCommand(action, opts) {
78
78
  return { output: "--apply and --dry-run conflict. Pick one.", code: 2 };
79
79
  }
80
80
  const apply = opts.apply === true;
81
- const { openDb, readDbConfig } = await import("./client-JYKPVCJX.js");
81
+ const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
82
82
  const { SPAM_THRESHOLD } = await import("./spam-classifier-T5H4VTP6.js");
83
83
  const cfg = opts.url ? { url: opts.url } : readDbConfig();
84
84
  const db = await openDb(cfg);
@@ -119,4 +119,4 @@ export {
119
119
  rescoreNewSubmissions,
120
120
  runSubmissionsCommand
121
121
  };
122
- //# sourceMappingURL=submissions-7LGJJSDL.js.map
122
+ //# sourceMappingURL=submissions-UMJS3YST.js.map
@@ -16,7 +16,7 @@ import {
16
16
  mergeGitignore,
17
17
  planTemplateDiffs,
18
18
  syncConfigs
19
- } from "./chunk-DXWLWR4Q.js";
19
+ } from "./chunk-A6T2R63Z.js";
20
20
  import "./chunk-3G25KIWW.js";
21
21
  import {
22
22
  ALL_TEMPLATES,
@@ -107,4 +107,4 @@ async function runSyncConfigsCommand(site, opts) {
107
107
  export {
108
108
  runSyncConfigsCommand
109
109
  };
110
- //# sourceMappingURL=sync-configs-ZYLTMUNX.js.map
110
+ //# sourceMappingURL=sync-configs-PVRLZRKL.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reddoorla/maintenance",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "description": "Canonical maintenance configs, audits, and recipes for the reddoor stack.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -113,6 +113,7 @@
113
113
  "zod": "^4.4.3"
114
114
  },
115
115
  "scripts": {
116
+ "verify": "pnpm typecheck && pnpm lint && pnpm build && pnpm test:coverage && pnpm test:dist",
116
117
  "build": "tsup",
117
118
  "dev": "tsup --watch",
118
119
  "pretest": "pnpm run build",