@reddoorla/maintenance 0.90.1 → 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.
- package/dist/{announce-NDTLRQCN.js → announce-6OS4C57D.js} +3 -3
- package/dist/{blux-YGBGS24U.js → blux-NXQLMS4W.js} +4 -4
- package/dist/blux-NXQLMS4W.js.map +1 -0
- package/dist/{chunk-WS6NU475.js → chunk-22GKJO5F.js} +2 -2
- package/dist/{chunk-NYUYNYQL.js → chunk-7MKT4I5T.js} +15 -1
- package/dist/chunk-7MKT4I5T.js.map +1 -0
- package/dist/{chunk-W45FZEGE.js → chunk-DMPHP7UP.js} +2 -2
- package/dist/{chunk-KGUKL3CV.js → chunk-E22WAIF2.js} +2 -2
- package/dist/{chunk-VBEKL445.js → chunk-FCS36FDT.js} +2 -2
- package/dist/{chunk-5PWB3JHJ.js → chunk-H6BLTEFN.js} +27 -8
- package/dist/chunk-H6BLTEFN.js.map +1 -0
- package/dist/{chunk-ZOCJBJQV.js → chunk-MVDMKKBN.js} +2 -2
- package/dist/{chunk-MFHS7IQ2.js → chunk-OCNSIBMP.js} +27 -1
- package/dist/{chunk-MFHS7IQ2.js.map → chunk-OCNSIBMP.js.map} +1 -1
- package/dist/chunk-RQLXEMEE.js +496 -0
- package/dist/chunk-RQLXEMEE.js.map +1 -0
- package/dist/chunk-WVVT6SJK.js +3382 -0
- package/dist/chunk-WVVT6SJK.js.map +1 -0
- package/dist/cli/bin.js +18 -15
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +5 -5
- package/dist/client-M2V6FHH5.js +11 -0
- package/dist/{db-LOSJ74ZJ.js → db-IMMQSRAB.js} +9 -9
- package/dist/{digest-CS6KLCZB.js → digest-EOA4JLTV.js} +13 -13
- package/dist/{digest-collectors-NI2M3UTC.js → digest-collectors-ZA3BTS3R.js} +7 -7
- package/dist/{email-LJQT5J7F.js → email-PLPDJUDH.js} +4 -3
- package/dist/{email-LJQT5J7F.js.map → email-PLPDJUDH.js.map} +1 -1
- package/dist/{ensure-site-FFDUMY6E.js → ensure-site-SF6KICTN.js} +2 -2
- package/dist/{forms-notify-target-IHJX7NWA.js → forms-notify-target-YCZUAHTY.js} +2 -2
- package/dist/{github-signals-FCLNHKMF.js → github-signals-3WLIJ5RR.js} +5 -5
- package/dist/{header-image-7A7XLVEU.js → header-image-DOCDFFRO.js} +2 -2
- package/dist/{health-mirror-GMSF3Z5R.js → health-mirror-55RRU6BI.js} +3 -3
- package/dist/index.js +7 -7
- package/dist/{launch-5V7RLUV7.js → launch-WPEQ3TY2.js} +3 -3
- package/dist/migrate-K4JETR36.js +7 -0
- package/dist/{orchestrate-6HROYTM6.js → orchestrate-KMQIUP3H.js} +5 -5
- package/dist/pipeline-HQJYZ443.js +29 -0
- package/dist/{preflight-LIN5NCYU.js → preflight-VPHQYWTY.js} +6 -6
- package/dist/{prismic-models-VNGIKS2B.js → prismic-models-FPDSUXDO.js} +2 -2
- package/dist/prospect/types.d.ts +792 -2
- package/dist/prospect/types.js +8 -0
- package/dist/{prospect-audit-JCWTKJTS.js → prospect-audit-TFIXSTE5.js} +27 -6
- package/dist/prospect-audit-TFIXSTE5.js.map +1 -0
- package/dist/{prospect-audits-3PMIO73D.js → prospect-audits-RVYEA4KH.js} +6 -4
- package/dist/render-3GYW2J34.js +11 -0
- package/dist/{report-AAY2HZZR.js → report-Y4LXDHRV.js} +11 -11
- package/dist/{report-mirror-GMNSH5HF.js → report-mirror-6ZDCAQZB.js} +3 -3
- package/dist/{schema-75PJECAE.js → schema-2V7BGYVT.js} +1 -1
- package/dist/{schema-75PJECAE.js.map → schema-2V7BGYVT.js.map} +1 -1
- package/dist/{selftest-CUX2FIQD.js → selftest-IFGSJP4X.js} +5 -5
- package/dist/{site-mirror-GCFLBBWK.js → site-mirror-3E7JYTJG.js} +3 -3
- package/dist/{submissions-7LGJJSDL.js → submissions-UMJS3YST.js} +2 -2
- package/package.json +2 -1
- package/dist/blux-YGBGS24U.js.map +0 -1
- package/dist/chunk-5PWB3JHJ.js.map +0 -1
- package/dist/chunk-DXKF552B.js +0 -1630
- package/dist/chunk-DXKF552B.js.map +0 -1
- package/dist/chunk-NYUYNYQL.js.map +0 -1
- package/dist/client-JYKPVCJX.js +0 -11
- package/dist/migrate-YITCXBLS.js +0 -7
- package/dist/pipeline-MQJD4CKN.js +0 -16
- package/dist/prospect-audit-JCWTKJTS.js.map +0 -1
- package/dist/render-P3SBUFVB.js +0 -10
- /package/dist/{announce-NDTLRQCN.js.map → announce-6OS4C57D.js.map} +0 -0
- /package/dist/{chunk-WS6NU475.js.map → chunk-22GKJO5F.js.map} +0 -0
- /package/dist/{chunk-W45FZEGE.js.map → chunk-DMPHP7UP.js.map} +0 -0
- /package/dist/{chunk-KGUKL3CV.js.map → chunk-E22WAIF2.js.map} +0 -0
- /package/dist/{chunk-VBEKL445.js.map → chunk-FCS36FDT.js.map} +0 -0
- /package/dist/{chunk-ZOCJBJQV.js.map → chunk-MVDMKKBN.js.map} +0 -0
- /package/dist/{client-JYKPVCJX.js.map → client-M2V6FHH5.js.map} +0 -0
- /package/dist/{db-LOSJ74ZJ.js.map → db-IMMQSRAB.js.map} +0 -0
- /package/dist/{digest-CS6KLCZB.js.map → digest-EOA4JLTV.js.map} +0 -0
- /package/dist/{digest-collectors-NI2M3UTC.js.map → digest-collectors-ZA3BTS3R.js.map} +0 -0
- /package/dist/{ensure-site-FFDUMY6E.js.map → ensure-site-SF6KICTN.js.map} +0 -0
- /package/dist/{forms-notify-target-IHJX7NWA.js.map → forms-notify-target-YCZUAHTY.js.map} +0 -0
- /package/dist/{github-signals-FCLNHKMF.js.map → github-signals-3WLIJ5RR.js.map} +0 -0
- /package/dist/{header-image-7A7XLVEU.js.map → header-image-DOCDFFRO.js.map} +0 -0
- /package/dist/{health-mirror-GMSF3Z5R.js.map → health-mirror-55RRU6BI.js.map} +0 -0
- /package/dist/{launch-5V7RLUV7.js.map → launch-WPEQ3TY2.js.map} +0 -0
- /package/dist/{migrate-YITCXBLS.js.map → migrate-K4JETR36.js.map} +0 -0
- /package/dist/{orchestrate-6HROYTM6.js.map → orchestrate-KMQIUP3H.js.map} +0 -0
- /package/dist/{pipeline-MQJD4CKN.js.map → pipeline-HQJYZ443.js.map} +0 -0
- /package/dist/{preflight-LIN5NCYU.js.map → preflight-VPHQYWTY.js.map} +0 -0
- /package/dist/{prismic-models-VNGIKS2B.js.map → prismic-models-FPDSUXDO.js.map} +0 -0
- /package/dist/{prospect-audits-3PMIO73D.js.map → prospect-audits-RVYEA4KH.js.map} +0 -0
- /package/dist/{render-P3SBUFVB.js.map → render-3GYW2J34.js.map} +0 -0
- /package/dist/{report-AAY2HZZR.js.map → report-Y4LXDHRV.js.map} +0 -0
- /package/dist/{report-mirror-GMNSH5HF.js.map → report-mirror-6ZDCAQZB.js.map} +0 -0
- /package/dist/{selftest-CUX2FIQD.js.map → selftest-IFGSJP4X.js.map} +0 -0
- /package/dist/{site-mirror-GCFLBBWK.js.map → site-mirror-3E7JYTJG.js.map} +0 -0
- /package/dist/{submissions-7LGJJSDL.js.map → submissions-UMJS3YST.js.map} +0 -0
package/dist/prospect/types.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
136
|
-
const { createProspectAudit } = await import("./prospect-audits-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
21
|
+
//# sourceMappingURL=prospect-audits-RVYEA4KH.js.map
|
|
@@ -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-
|
|
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-
|
|
86
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
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-
|
|
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-
|
|
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-
|
|
149
|
-
const { makeReportMirror } = await import("./report-mirror-
|
|
150
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
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-
|
|
341
|
-
const siteMirror = opts.previewOnly ? null : await (await import("./site-mirror-
|
|
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-
|
|
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-
|
|
17
|
-
import "./chunk-
|
|
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-
|
|
55
|
+
//# sourceMappingURL=report-mirror-6ZDCAQZB.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":";
|
|
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-
|
|
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-
|
|
44
|
-
import "./chunk-
|
|
43
|
+
import "./chunk-DMPHP7UP.js";
|
|
44
|
+
import "./chunk-22GKJO5F.js";
|
|
45
45
|
import "./chunk-RARREDQE.js";
|
|
46
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
17
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
122
|
+
//# sourceMappingURL=submissions-UMJS3YST.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reddoorla/maintenance",
|
|
3
|
-
"version": "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",
|