@reddoorla/maintenance 0.27.0 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audits/util/spawn.ts","../src/audits/deps.ts","../src/util/site.ts","../src/configs/baseline-versions.ts","../src/audits/lint.ts","../src/audits/security.ts","../src/audits/lighthouse.ts","../src/configs/lighthouse.ts","../src/audits/util/site-config.ts","../src/util/free-port.ts","../src/audits/a11y.ts","../src/configs/playwright-a11y.ts","../src/audits/index.ts","../src/recipes/sync-configs.ts","../src/recipes/sync-configs/templates.ts","../src/recipes/sync-configs/gitignore.ts","../src/util/git.ts","../src/recipes/_with-recipe.ts","../src/recipes/bump-deps.ts","../src/recipes/svelte-5/index.ts","../src/util/pkg.ts","../src/recipes/svelte-5/step-bump-versions.ts","../src/recipes/svelte-5/step-svelte-config.ts","../src/recipes/svelte-5/step-svelte-migrate.ts","../src/recipes/svelte-5/step-tailwind-upgrade.ts","../src/recipes/svelte-5/step-gotchas.ts","../src/recipes/svelte-5/codemods/on-event-to-handler.ts","../src/recipes/svelte-5/codemods/dollar-props.ts","../src/util/svelte-source.ts","../src/recipes/svelte-5/codemods/dollar-restprops.ts","../src/recipes/svelte-5/codemods/state-effect-sync.ts","../src/recipes/svelte-5/codemods/dollar-props-class.ts","../src/recipes/svelte-5/codemods/legacy-reactive.ts","../src/recipes/svelte-5/step-verify.ts","../src/recipes/svelte-5/step-summary.ts","../src/recipes/svelte-codemods.ts","../src/recipes/convert-to-pnpm.ts","../src/recipes/convert-to-pnpm/script-rewrites.ts","../src/recipes/onboard.ts","../src/util/self-version.ts","../src/recipes/a11y-fixtures-page/index.ts","../src/recipes/a11y-fixtures-page/template.ts","../src/recipes/init.ts","../src/recipes/index.ts","../src/inventory/local.ts","../src/inventory/json.ts","../src/reports/airtable/websites.ts","../src/inventory/airtable.ts","../src/reports/draft.ts","../src/reports/render.ts","../src/reports/maintenance-email/assets/index.ts","../src/reports/maintenance-email/template.ts","../src/reports/airtable/reports.ts","../src/reports/airtable/attachments.ts","../src/reports/ga/config.ts","../src/util/credentials.ts","../src/reports/ga/client.ts","../src/reports/search/client.ts","../src/reports/airtable/client.ts","../src/reports/maintenance-email/header-image.ts","../src/reports/send/resend.ts","../src/reports/send/orchestrate.ts","../src/reports/due.ts","../src/dashboard/relative-time.ts","../src/dashboard/render.ts","../src/dashboard/auth.ts","../src/dashboard/onboarding.ts","../src/dashboard/fleet-render.ts","../src/dashboard/basic-auth.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport type SpawnResult = { code: number; stdout: string; stderr: string };\n\nexport type SpawnOptions = {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n timeoutMs?: number;\n /** When true, the child inherits stdout/stderr so the user sees live\n * progress (useful for long-running `pnpm up` / `npm install`). The\n * returned `stdout` and `stderr` will be empty strings in that case. */\n streaming?: boolean;\n};\n\nexport type SpawnFn = (\n cmd: string,\n args: readonly string[],\n opts?: SpawnOptions,\n) => Promise<SpawnResult>;\n\nexport const defaultSpawn: SpawnFn = (cmd, args, opts = {}) =>\n new Promise((resolve, reject) => {\n const streaming = opts.streaming === true;\n const child = spawn(cmd, [...args], {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n stdio: streaming ? [\"ignore\", \"inherit\", \"inherit\"] : [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n if (!streaming) {\n child.stdout?.on(\"data\", (chunk) => (stdout += String(chunk)));\n child.stderr?.on(\"data\", (chunk) => (stderr += String(chunk)));\n }\n\n const timer = opts.timeoutMs\n ? setTimeout(() => {\n child.kill(\"SIGTERM\");\n reject(new Error(`spawn timeout after ${opts.timeoutMs}ms: ${cmd}`));\n }, opts.timeoutMs)\n : undefined;\n\n child.on(\"error\", (err) => {\n if (timer) clearTimeout(timer);\n reject(err);\n });\n child.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ code: code ?? -1, stdout, stderr });\n });\n });\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { baselineVersions } from \"../configs/baseline-versions.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\nexport type Drift = \"same\" | \"patch\" | \"minor\" | \"major\" | \"newer\";\n\nexport type DepsDriftEntry = {\n pkg: string;\n baseline: string;\n actual: string;\n drift: Drift;\n};\n\nfunction stripCaret(range: string): string {\n return range.replace(/^[\\^~]/, \"\");\n}\n\nfunction parseSemver(v: string): [number, number, number] {\n const cleaned = stripCaret(v).split(\"-\")[0] ?? \"0.0.0\";\n const parts = cleaned.split(\".\").map((n) => Number.parseInt(n, 10));\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nfunction compareSemver(actual: string, baseline: string): Drift {\n const [aMajor, aMinor, aPatch] = parseSemver(actual);\n const [bMajor, bMinor, bPatch] = parseSemver(baseline);\n if (aMajor > bMajor) return \"newer\";\n if (aMajor < bMajor) return \"major\";\n if (aMinor > bMinor) return \"newer\";\n if (aMinor < bMinor) return \"minor\";\n if (aPatch > bPatch) return \"newer\";\n if (aPatch < bPatch) return \"patch\";\n return \"same\";\n}\n\nexport async function depsAudit(ctx: AuditContext): Promise<AuditResult> {\n const pkgPath = join(ctx.site.path, \"package.json\");\n let pkgRaw: string;\n try {\n pkgRaw = await readFile(pkgPath, \"utf-8\");\n } catch (err) {\n return {\n audit: \"deps\",\n site: siteLabel(ctx.site),\n status: \"skip\",\n summary: `no package.json at ${pkgPath}`,\n details: { error: String(err) },\n };\n }\n\n const pkg = JSON.parse(pkgRaw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const installed: Record<string, string> = {\n ...(pkg.dependencies ?? {}),\n ...(pkg.devDependencies ?? {}),\n };\n\n const details: DepsDriftEntry[] = [];\n for (const [name, baseline] of Object.entries(baselineVersions)) {\n const actual = installed[name];\n if (!actual) continue;\n details.push({\n pkg: name,\n baseline,\n actual,\n drift: compareSemver(actual, baseline),\n });\n }\n\n const anyMajor = details.some((d) => d.drift === \"major\");\n const anyMinor = details.some((d) => d.drift === \"minor\");\n const anyNewer = details.some((d) => d.drift === \"newer\");\n\n const status: AuditResult[\"status\"] = anyMajor ? \"fail\" : anyMinor || anyNewer ? \"warn\" : \"pass\";\n\n const summary =\n status === \"pass\"\n ? `all ${details.length} tracked deps in line with baseline`\n : status === \"warn\"\n ? `${details.filter((d) => d.drift !== \"same\").length} of ${details.length} tracked deps drifted`\n : `${details.filter((d) => d.drift === \"major\").length} deps lagging by a major version`;\n\n return {\n audit: \"deps\",\n site: siteLabel(ctx.site),\n status,\n summary,\n details,\n };\n}\n","import type { Site } from \"../types.js\";\n\n/** Human-friendly label for log/output formatting. Prefer the inventory's\n * `name` when present (e.g. \"caltex-landing\") and fall back to the\n * filesystem `path` when unnamed. Every audit + recipe uses this. */\nexport function siteLabel(site: Site): string {\n return site.name ?? site.path;\n}\n","// Curated map of the framework deps reddoor sites should stay close to.\n// Refreshed at each package release from reddoor-starter's package.json.\n// Versions are caret ranges to mirror what `pnpm add` would produce.\n\nexport const baselineVersions: Record<string, string> = {\n // SvelteKit core\n svelte: \"^5.55.10\",\n \"@sveltejs/kit\": \"^2.61.1\",\n \"@sveltejs/adapter-netlify\": \"^6.0.4\",\n \"@sveltejs/adapter-auto\": \"^7.0.1\",\n \"@sveltejs/vite-plugin-svelte\": \"^7.1.2\",\n \"svelte-check\": \"^4.4.8\",\n\n // Build tooling\n vite: \"^8.0.14\",\n vitest: \"^4.1.7\",\n typescript: \"^6.0.3\",\n\n // Tailwind 4\n tailwindcss: \"^4.3.0\",\n \"@tailwindcss/vite\": \"^4.3.0\",\n\n // Prismic\n \"@prismicio/client\": \"^7.21.8\",\n \"@prismicio/svelte\": \"^2.2.1\",\n \"@slicemachine/adapter-sveltekit\": \"^0.3.96\",\n \"slice-machine-ui\": \"^2.21.3\",\n\n // Test tooling\n \"@playwright/test\": \"^1.60.0\",\n \"@axe-core/playwright\": \"^4.11.3\",\n \"@lhci/cli\": \"^0.15.1\",\n\n // Lint\n eslint: \"^10.4.0\",\n \"eslint-plugin-svelte\": \"^3.18.0\",\n \"eslint-config-prettier\": \"^10.1.8\",\n prettier: \"^3.8.3\",\n \"prettier-plugin-svelte\": \"^4.0.1\",\n \"typescript-eslint\": \"^8.60.0\",\n \"@eslint/js\": \"^10.0.1\",\n globals: \"^17.6.0\",\n\n // Misc\n \"@lucide/svelte\": \"^1.17.0\",\n \"@zerodevx/svelte-img\": \"^2.1.2\",\n};\n\nexport default baselineVersions;\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { ESLint } from \"eslint\";\nimport { check as prettierCheck, resolveConfig as prettierResolveConfig } from \"prettier\";\nimport { glob } from \"tinyglobby\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\nconst TARGET_GLOBS = [\"**/*.{ts,js,svelte}\"];\nconst IGNORE = [\"node_modules/**\", \"dist/**\", \".svelte-kit/**\", \"build/**\", \".netlify/**\"];\n\nasync function listFiles(cwd: string): Promise<string[]> {\n return glob(TARGET_GLOBS, { cwd, ignore: IGNORE, absolute: false });\n}\n\nexport async function lintAudit(ctx: AuditContext): Promise<AuditResult> {\n const { site } = ctx;\n const configPath = join(site.path, \"eslint.config.js\");\n\n if (!existsSync(configPath)) {\n return {\n audit: \"lint\",\n site: siteLabel(site),\n status: \"skip\",\n summary: \"no eslint config at site root\",\n };\n }\n\n const eslint = new ESLint({\n cwd: site.path,\n overrideConfigFile: configPath,\n errorOnUnmatchedPattern: false,\n });\n\n const relFiles = await listFiles(site.path);\n\n // Pass relative paths to ESLint; its cwd is already site.path. Avoids\n // dereferencing symlinks on pnpm workspaces.\n const eslintResults = await eslint.lintFiles(relFiles);\n const eslintErrors = eslintResults.reduce((n, r) => n + r.errorCount, 0);\n const eslintWarnings = eslintResults.reduce((n, r) => n + r.warningCount, 0);\n\n const prettierUnformatted: string[] = [];\n for (const rel of relFiles) {\n const absForResolve = join(site.path, rel);\n const source = await readFile(absForResolve, \"utf-8\");\n const options = (await prettierResolveConfig(absForResolve)) ?? {};\n const ok = await prettierCheck(source, { ...options, filepath: absForResolve });\n if (!ok) prettierUnformatted.push(rel);\n }\n\n const status: AuditResult[\"status\"] =\n eslintErrors > 0 || prettierUnformatted.length > 0\n ? \"fail\"\n : eslintWarnings > 0\n ? \"warn\"\n : \"pass\";\n\n const summary =\n status === \"pass\"\n ? `lint clean across ${relFiles.length} files`\n : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;\n\n return {\n audit: \"lint\",\n site: siteLabel(site),\n status,\n summary,\n details: {\n eslintErrors,\n eslintWarnings,\n prettierUnformatted,\n files: relFiles.length,\n },\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { defaultSpawn, type SpawnResult } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\ntype Severity = \"low\" | \"moderate\" | \"high\" | \"critical\";\n\ntype Counts = { low: number; moderate: number; high: number; critical: number };\n\ntype AdvisoryEntry = {\n module: string;\n severity: Severity;\n title: string;\n cves?: string[];\n url?: string;\n};\n\n// pnpm audit output (npm-compat with extra advisories map keyed by ID).\ntype PnpmAuditJson = {\n metadata?: { vulnerabilities?: Partial<Counts> };\n advisories?: Record<\n string,\n {\n id?: number;\n title?: string;\n module_name?: string;\n severity?: string;\n cves?: string[];\n url?: string;\n }\n >;\n};\n\n// npm v7+ shape (vulnerabilities keyed by package name).\ntype NpmAuditJson = {\n metadata?: { vulnerabilities?: Partial<Counts> };\n vulnerabilities?: Record<\n string,\n {\n name?: string;\n severity?: string;\n via?: unknown;\n url?: string;\n }\n >;\n};\n\nfunction classify(v: Counts) {\n if (v.critical > 0 || v.high > 0) return \"fail\" as const;\n if (v.moderate > 0 || v.low > 0) return \"warn\" as const;\n return \"pass\" as const;\n}\n\nfunction normalizeSeverity(s: unknown): Severity {\n if (s === \"low\" || s === \"moderate\" || s === \"high\" || s === \"critical\") return s;\n // npm/pnpm sometimes emit \"info\" for informational advisories. Map down\n // rather than defaulting to \"moderate\" (which would inflate severity).\n return \"low\";\n}\n\nfunction extractAdvisoriesFromPnpm(parsed: PnpmAuditJson): AdvisoryEntry[] {\n const out: AdvisoryEntry[] = [];\n for (const a of Object.values(parsed.advisories ?? {})) {\n if (!a) continue;\n out.push({\n module: a.module_name ?? \"unknown\",\n severity: normalizeSeverity(a.severity),\n title: a.title ?? \"(no title)\",\n ...(a.cves ? { cves: a.cves } : {}),\n ...(a.url ? { url: a.url } : {}),\n });\n }\n return out;\n}\n\n/** Walk an npm v7+ `via` chain to find the root entry whose `via` array\n * contains a real advisory object (rather than another package name string).\n * Returns the package name at the root and the advisory detail. */\nfunction resolveNpmAdvisoryRoot(\n startName: string,\n vulnerabilities: NonNullable<NpmAuditJson[\"vulnerabilities\"]>,\n): { rootName: string; detail?: { title?: string; url?: string } } {\n const seen = new Set<string>();\n let current = startName;\n while (!seen.has(current)) {\n seen.add(current);\n const entry = vulnerabilities[current];\n if (!entry || !Array.isArray(entry.via)) return { rootName: current };\n\n const detailed = entry.via.find(\n (e): e is { title?: string; url?: string } => typeof e === \"object\" && e !== null,\n );\n if (detailed) return { rootName: current, detail: detailed };\n\n const next = entry.via.find((e): e is string => typeof e === \"string\");\n if (!next || next === current) return { rootName: current };\n current = next;\n }\n return { rootName: current };\n}\n\nfunction extractAdvisoriesFromNpm(parsed: NpmAuditJson): AdvisoryEntry[] {\n const vulnerabilities = parsed.vulnerabilities ?? {};\n const roots = new Map<string, AdvisoryEntry>();\n\n for (const [name, v] of Object.entries(vulnerabilities)) {\n if (!v) continue;\n const { rootName, detail } = resolveNpmAdvisoryRoot(name, vulnerabilities);\n if (roots.has(rootName)) continue; // already surfaced via another transitive entry\n\n const rootEntry = vulnerabilities[rootName];\n const severity = normalizeSeverity(rootEntry?.severity ?? v.severity);\n const title = detail?.title ?? rootName;\n const url = detail?.url;\n\n roots.set(rootName, {\n module: rootEntry?.name ?? rootName,\n severity,\n title,\n ...(url ? { url } : {}),\n });\n }\n\n return [...roots.values()];\n}\n\ntype ToolResult =\n | { kind: \"missing\" }\n | { kind: \"error\"; reason: string }\n | { kind: \"ok\"; parsed: PnpmAuditJson & NpmAuditJson };\n\nasync function runAuditTool(\n spawn: (cmd: string, args: readonly string[], opts?: { cwd?: string }) => Promise<SpawnResult>,\n cmd: string,\n args: readonly string[],\n cwd: string,\n): Promise<ToolResult> {\n let raw: SpawnResult;\n try {\n raw = await spawn(cmd, args, { cwd });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) return { kind: \"missing\" };\n return { kind: \"error\", reason: `spawn failed: ${String(err).slice(0, 200)}` };\n }\n\n // 0 = clean, 1 = vulns found. Anything else is a real error.\n if (raw.code !== 0 && raw.code !== 1) {\n return {\n kind: \"error\",\n reason: `exit ${raw.code}${raw.stderr ? `: ${raw.stderr.slice(0, 150)}` : \"\"}`,\n };\n }\n\n let parsed: PnpmAuditJson & NpmAuditJson;\n try {\n parsed = JSON.parse(raw.stdout || \"{}\") as PnpmAuditJson & NpmAuditJson;\n } catch (err) {\n return { kind: \"error\", reason: `unparseable JSON: ${String(err).slice(0, 100)}` };\n }\n\n // pnpm error envelope: { error: { code, message } }. npm sometimes emits\n // a top-level error too. Either means the audit didn't actually run.\n const errEnvelope = (parsed as unknown as { error?: { code?: string } }).error;\n if (errEnvelope && typeof errEnvelope === \"object\") {\n return { kind: \"error\", reason: errEnvelope.code ?? \"error envelope returned\" };\n }\n\n // Without metadata.vulnerabilities there are no counts to report and we\n // can't trust the result. An empty `{}` is just as suspect as a missing\n // key — counts default to 0 and we'd silently report \"pass\". Treat both\n // as a tool failure so the caller can fall through to the other audit.\n const vulnsMeta = parsed.metadata?.vulnerabilities;\n if (!vulnsMeta || Object.keys(vulnsMeta).length === 0) {\n return { kind: \"error\", reason: \"no metadata.vulnerabilities in output\" };\n }\n\n return { kind: \"ok\", parsed };\n}\n\nexport async function securityAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n let used: \"pnpm audit\" | \"npm audit\" = \"pnpm audit\";\n let result = await runAuditTool(spawn, \"pnpm\", [\"audit\", \"--json\", \"--prod\"], site.path);\n\n // Fall through to npm if pnpm is missing OR pnpm couldn't actually\n // audit the project (e.g., no pnpm-lock.yaml). Previously we only fell\n // through on ENOENT, which meant npm-using sites silently reported \"pass\"\n // because pnpm returned an error envelope with no metadata.\n if (result.kind !== \"ok\") {\n const pnpmReason = result.kind === \"missing\" ? \"not installed\" : result.reason;\n const npmResult = await runAuditTool(\n spawn,\n \"npm\",\n [\"audit\", \"--json\", \"--omit=dev\"],\n site.path,\n );\n if (npmResult.kind === \"ok\") {\n result = npmResult;\n used = \"npm audit\";\n } else {\n const npmReason = npmResult.kind === \"missing\" ? \"not installed\" : npmResult.reason;\n return {\n audit: \"security\",\n site: label,\n status: \"skip\",\n summary: `cannot run audit — pnpm: ${pnpmReason}; npm: ${npmReason}`,\n };\n }\n }\n\n const parsed = result.parsed;\n\n const counts: Counts = {\n low: parsed.metadata?.vulnerabilities?.low ?? 0,\n moderate: parsed.metadata?.vulnerabilities?.moderate ?? 0,\n high: parsed.metadata?.vulnerabilities?.high ?? 0,\n critical: parsed.metadata?.vulnerabilities?.critical ?? 0,\n };\n\n const advisories =\n used === \"pnpm audit\" ? extractAdvisoriesFromPnpm(parsed) : extractAdvisoriesFromNpm(parsed);\n\n const status = classify(counts);\n const total = counts.low + counts.moderate + counts.high + counts.critical;\n const summary =\n status === \"pass\"\n ? `${used}: 0 vulnerabilities`\n : `${used}: ${total} vulnerabilities (${counts.critical}C/${counts.high}H/${counts.moderate}M/${counts.low}L)`;\n\n return {\n audit: \"security\",\n site: label,\n status,\n summary,\n details: { counts, advisories },\n };\n}\n","import { readFile, writeFile, mkdtemp, rm, readdir } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { lighthouseConfig } from \"../configs/lighthouse.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { readSiteConfig } from \"./util/site-config.js\";\nimport { findFreePort, withFreePort } from \"../util/free-port.js\";\n\ntype ManifestEntry = {\n url: string;\n summary: Record<string, number>;\n htmlPath?: string;\n jsonPath?: string;\n};\n\ntype AssertionResult = {\n name: string;\n actual: number;\n expected: number;\n operator: string;\n passed: boolean;\n level: \"warn\" | \"error\";\n auditProperty?: string;\n auditId?: string;\n};\n\ntype NormalizedLhciResult = {\n summary: Record<string, number>;\n assertionsFailed: number;\n assertions: Array<{ category: string; level: \"warn\" | \"error\"; message: string }>;\n};\n\nasync function readJsonMaybe<T>(path: string): Promise<T | null> {\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\ntype LhrFile = {\n requestedUrl: string;\n finalUrl?: string;\n categories: Record<string, { score: number | null }>;\n};\n\n/**\n * Build manifest-equivalent entries by scanning the `.lighthouseci/` dir\n * for `lhr-*.json` files written by `lhci collect`. We used to read\n * `manifest.json` directly, but lhci 0.15+ no longer writes it — the\n * audit would silently return \"no manifest written\" against a perfectly\n * healthy run. Reproduced on caltex 2026-05-28 (0.10.5 dogfood).\n */\nasync function readLhrEntries(resultsDir: string): Promise<ManifestEntry[]> {\n const files = await readdir(resultsDir).catch(() => [] as string[]);\n const entries: ManifestEntry[] = [];\n for (const f of files) {\n if (!f.startsWith(\"lhr-\") || !f.endsWith(\".json\")) continue;\n const lhr = await readJsonMaybe<LhrFile>(join(resultsDir, f));\n if (!lhr || !lhr.categories) continue;\n const summary: Record<string, number> = {};\n for (const [k, v] of Object.entries(lhr.categories)) {\n if (typeof v?.score === \"number\") summary[k] = v.score;\n }\n entries.push({ url: lhr.requestedUrl, summary });\n }\n return entries;\n}\n\nfunction averageSummaries(entries: ManifestEntry[]): Record<string, number> {\n if (entries.length === 0) return {};\n const sums: Record<string, number> = {};\n const counts: Record<string, number> = {};\n for (const e of entries) {\n for (const [k, v] of Object.entries(e.summary ?? {})) {\n if (typeof v !== \"number\") continue;\n sums[k] = (sums[k] ?? 0) + v;\n counts[k] = (counts[k] ?? 0) + 1;\n }\n }\n const out: Record<string, number> = {};\n for (const k of Object.keys(sums)) {\n const total = sums[k] ?? 0;\n const count = counts[k] ?? 1;\n out[k] = total / count;\n }\n return out;\n}\n\nfunction categoryFromAssertion(a: AssertionResult): string {\n // `name` looks like \"categories:accessibility\" or \"audits:uses-http2\".\n const colonIdx = a.name.indexOf(\":\");\n return colonIdx >= 0 ? a.name.slice(colonIdx + 1) : a.name;\n}\n\nfunction messageForAssertion(a: AssertionResult): string {\n return `${a.name} ${a.operator} ${a.expected} (actual: ${a.actual.toFixed(2)})`;\n}\n\nexport async function lighthouseAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n const siteCfg = await readSiteConfig(site.path);\n // Allocate a free port + force vite to `--strictPort` so the spawned dev\n // server either binds the port we picked or fails loudly. Without this,\n // a zombie on 5173 makes vite bump to 5174 while lhci still probes 5173\n // and audits the wrong server (silently returns \"no manifest written\").\n const port = await findFreePort();\n const baseUrl = siteCfg.lighthouseUrl ?? lighthouseConfig.ci.collect.url[0];\n const resolvedConfig = {\n ...lighthouseConfig,\n ci: {\n ...lighthouseConfig.ci,\n collect: {\n ...lighthouseConfig.ci.collect,\n url: [withFreePort(baseUrl, port)],\n startServerCommand: `npm run vite:dev -- --port ${port} --strictPort`,\n },\n },\n };\n\n const configDir = await mkdtemp(join(tmpdir(), \"reddoor-lhci-\"));\n const configPath = join(configDir, \"lighthouserc.json\");\n await writeFile(configPath, JSON.stringify(resolvedConfig), \"utf-8\");\n\n const resultsDir = join(site.path, \".lighthouseci\");\n // Clear any stale artifacts before the run so we never confuse a failed\n // spawn with old results.\n await rm(resultsDir, { recursive: true, force: true });\n\n let raw;\n try {\n raw = await spawn(\"npx\", [\"--yes\", \"@lhci/cli\", \"autorun\", `--config=${configPath}`], {\n cwd: site.path,\n // lhci autorun boots the site's dev server, downloads Chrome on first\n // use, and runs the audit — easily 2–3 min on a cold tree. The shared\n // 30 s default in runAudits is fine for deps/lint/security but starves\n // lhci.\n timeoutMs: 5 * 60_000,\n });\n } catch (err) {\n await rm(configDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"skip\",\n summary: \"npx/@lhci/cli not available\",\n };\n }\n throw err;\n }\n await rm(configDir, { recursive: true, force: true });\n\n const manifest = await readLhrEntries(resultsDir);\n\n if (manifest.length === 0) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"fail\",\n summary: `lighthouse: no lhr-*.json written (exit ${raw.code})${\n raw.stderr ? ` — ${raw.stderr.slice(0, 200)}` : \"\"\n }`,\n };\n }\n\n const assertionResults =\n (await readJsonMaybe<AssertionResult[]>(join(resultsDir, \"assertion-results.json\"))) ?? [];\n\n const failed = assertionResults.filter((a) => !a.passed);\n const assertions = failed.map((a) => ({\n category: categoryFromAssertion(a),\n level: a.level,\n message: messageForAssertion(a),\n }));\n\n const anyError = assertions.some((a) => a.level === \"error\");\n const anyWarn = assertions.some((a) => a.level === \"warn\");\n const status: AuditResult[\"status\"] = anyError ? \"fail\" : anyWarn ? \"warn\" : \"pass\";\n\n const normalized: NormalizedLhciResult = {\n summary: averageSummaries(manifest),\n assertionsFailed: failed.length,\n assertions,\n };\n\n const summary =\n status === \"pass\"\n ? \"lighthouse: all categories passing\"\n : `lighthouse: ${failed.length} assertion(s) failed`;\n\n return {\n audit: \"lighthouse\",\n site: label,\n status,\n summary,\n details: normalized,\n };\n}\n","export const lighthouseConfig = {\n ci: {\n collect: {\n url: [\"http://localhost:5173/dev/a11y-fixtures\"],\n // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects\n // the `run` form too. Keeps this config portable across the fleet\n // while sites transition to pnpm.\n startServerCommand: \"npm run vite:dev\",\n startServerReadyPattern: \"ready in\",\n startServerReadyTimeout: 120_000,\n numberOfRuns: 1,\n settings: {\n preset: \"desktop\",\n skipAudits: [\"uses-http2\"],\n },\n },\n assert: {\n assertions: {\n \"categories:accessibility\": [\"error\", { minScore: 0.95 }],\n \"categories:best-practices\": [\"error\", { minScore: 0.9 }],\n \"categories:seo\": [\"error\", { minScore: 0.9 }],\n \"categories:performance\": [\"warn\", { minScore: 0.7 }],\n },\n },\n upload: {\n target: \"temporary-public-storage\",\n },\n },\n} as const;\n\nexport default lighthouseConfig;\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type SiteConfig = {\n /** Override URL the lighthouse audit hits. Sites without the default\n * `/dev/a11y-fixtures` dev route set this to their homepage. */\n lighthouseUrl?: string;\n};\n\n/**\n * Read per-site overrides from `package.json#reddoor`. Returns `{}` on any\n * failure (missing file, malformed JSON, missing key, wrong type) so every\n * caller can safely fall back to its built-in default. Never throws.\n */\nexport async function readSiteConfig(sitePath: string): Promise<SiteConfig> {\n let raw: string;\n try {\n raw = await readFile(join(sitePath, \"package.json\"), \"utf-8\");\n } catch {\n return {};\n }\n let pkg: unknown;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return {};\n }\n if (!pkg || typeof pkg !== \"object\") return {};\n const cfg = (pkg as { reddoor?: unknown }).reddoor;\n if (!cfg || typeof cfg !== \"object\") return {};\n\n const out: SiteConfig = {};\n const url = (cfg as { lighthouseUrl?: unknown }).lighthouseUrl;\n if (typeof url === \"string\" && url.length > 0) {\n out.lighthouseUrl = url;\n }\n return out;\n}\n","import { createServer } from \"node:net\";\n\n/**\n * Bind an ephemeral TCP port, capture it, release it, and return it. Used\n * by the lighthouse and a11y audits to pick a port the audit's own dev\n * server will then bind via `--strictPort`.\n *\n * Why: vite's default behavior on a busy port is to bump to the next free\n * one (5173 → 5174 → …). When zombie vite processes (or any squatter) are\n * already on 5173, the audit's spawned vite lands on a higher port, but\n * the audit tooling (lhci, playwright) still probes 5173 — hits the\n * zombie — gets stale 404s — fails with \"no manifest written\" / \"no\n * results written (exit 1)\". Reproduced on caltex 2026-05-28 with 10\n * orphaned vite processes accumulated across this repo, the reports repo,\n * and caltex itself. Allocating a free port up front + `--strictPort`\n * makes the audit immune to port collisions.\n *\n * TOCTOU note: the small window between close() and the spawned vite\n * binding is theoretically racy, but in practice we run one audit at a\n * time and the OS keeps the port free for re-use. If vite still fails to\n * bind under `--strictPort`, the audit fails loudly — that's the correct\n * outcome (vs. silently auditing the wrong server).\n */\nexport async function findFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.unref();\n server.on(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const addr = server.address();\n if (typeof addr === \"object\" && addr) {\n const port = addr.port;\n server.close(() => resolve(port));\n } else {\n server.close();\n reject(new Error(\"findFreePort: could not determine assigned port from socket\"));\n }\n });\n });\n}\n\n/**\n * Swap the port (and force `localhost` host) on a URL so it points at the\n * audit's freshly-allocated dev server. Preserves the path + any query.\n * Used to rewrite the lighthouse `url` so lhci probes the correct port.\n */\nexport function withFreePort(url: string, port: number): string {\n const u = new URL(url);\n u.hostname = \"localhost\";\n u.port = String(port);\n return u.toString();\n}\n","import { readFile, writeFile, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { a11yRoutes } from \"../configs/playwright-a11y.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { findFreePort } from \"../util/free-port.js\";\n\ntype Impact = \"minor\" | \"moderate\" | \"serious\" | \"critical\";\n\ntype AxeViolation = {\n id: string;\n impact: Impact;\n route: string;\n help?: string;\n helpUrl?: string;\n nodes?: Array<{ html?: string; target?: string[] }>;\n};\n\ntype NormalizedA11y = {\n totalViolations: number;\n byImpact: Partial<Record<Impact, number>>;\n violations: AxeViolation[];\n};\n\nconst RESULTS_REL = \".reddoor-a11y/results.json\";\n\nasync function readJsonMaybe<T>(path: string): Promise<T | null> {\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\n// The audit-controlled playwright config. We synthesize it (rather than\n// rely on the site's playwright.config.ts) so we can pin the dev server\n// port + force `--strictPort` — same fix as the lighthouse audit, same\n// reason (zombie vite processes squatting on 5173 would otherwise eat\n// the audit's request and return stale 404s).\nfunction buildPlaywrightConfig(port: number, sitePath: string): string {\n return `import { defineConfig } from \"@playwright/test\";\n\nexport default defineConfig({\n testDir: \".\",\n testMatch: /.*\\\\.spec\\\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: \"http://localhost:${port}\",\n trace: \"on-first-retry\",\n },\n webServer: {\n // --strictPort: refuse to bump to a different port if ours is taken,\n // so the audit fails loudly instead of probing a zombie.\n // reuseExistingServer:false: never reuse — we control the lifecycle.\n // cwd: playwright's default webServer.cwd is the config file's\n // directory. Our config lives in /tmp so without this override,\n // \"npm run vite:dev\" tries to read /tmp/.../package.json and\n // ENOENTs before vite ever starts. Caltex 2026-05-28 (0.10.5).\n command: \"npm run vite:dev -- --port ${port} --strictPort\",\n url: \"http://localhost:${port}/dev/a11y-fixtures\",\n cwd: ${JSON.stringify(sitePath)},\n reuseExistingServer: false,\n timeout: 120_000,\n },\n});\n`;\n}\n\n// The spec the audit writes runs all configured routes through axe in a single\n// test (so worker isolation doesn't fragment the collected violations) and\n// writes the structured result to <cwd>/.reddoor-a11y/results.json before\n// asserting. That way, the audit can read real axe details even when the\n// expect(...).toEqual([]) assertion fails.\nfunction buildSpec(): string {\n return `import { test, expect } from \"@playwright/test\";\nimport AxeBuilder from \"@axe-core/playwright\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst pages = ${JSON.stringify(a11yRoutes)};\nconst OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;\n\n// Playwright's default per-test timeout is 30s. We loop through every\n// configured route in a single test, so the budget needs to scale.\ntest.setTimeout(5 * 60_000);\n\ntest(\"a11y across configured routes\", async ({ page }) => {\n const violations = [];\n for (const { path, name } of pages) {\n await page.goto(path);\n const results = await new AxeBuilder({ page })\n .withTags([\"wcag2a\",\"wcag2aa\",\"wcag21a\",\"wcag21aa\",\"wcag22aa\"])\n .analyze();\n for (const v of results.violations) {\n violations.push({\n id: v.id,\n impact: v.impact ?? \"moderate\",\n route: name,\n help: v.help,\n helpUrl: v.helpUrl,\n nodes: v.nodes.map((n) => ({ html: n.html, target: n.target })),\n });\n }\n }\n const byImpact = {};\n for (const v of violations) {\n byImpact[v.impact] = (byImpact[v.impact] ?? 0) + 1;\n }\n if (OUTPUT) {\n await mkdir(dirname(OUTPUT), { recursive: true });\n await writeFile(\n OUTPUT,\n JSON.stringify({ totalViolations: violations.length, byImpact, violations }, null, 2),\n );\n }\n expect(violations).toEqual([]);\n});\n`;\n}\n\nexport async function a11yAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n // specDir lives INSIDE site.path (not /tmp) so the spec's\n // `import AxeBuilder from \"@axe-core/playwright\"` resolves via Node's\n // walk-up — the site's node_modules is the nearest one. A spec written\n // to /tmp ENOENTs at module resolution before any test runs. Caltex\n // 2026-05-28 (0.10.6 dogfood), third layer of the same class as the\n // webServer.cwd bug.\n const specDir = await mkdtemp(join(site.path, \".reddoor-a11y-spec-\"));\n const specPath = join(specDir, \"a11y.spec.ts\");\n await writeFile(specPath, buildSpec(), \"utf-8\");\n\n const port = await findFreePort();\n const configPath = join(specDir, \"playwright.config.ts\");\n await writeFile(configPath, buildPlaywrightConfig(port, site.path), \"utf-8\");\n\n const resultsPath = join(site.path, RESULTS_REL);\n // Clear stale artifacts so a failed spawn never reports old data.\n await rm(join(site.path, \".reddoor-a11y\"), { recursive: true, force: true });\n\n let raw;\n try {\n raw = await spawn(\n \"npx\",\n [\"--yes\", \"playwright\", \"test\", `--config=${configPath}`, \"--reporter=line\", specPath],\n {\n cwd: site.path,\n env: { ...process.env, REDDOOR_A11Y_OUTPUT: resultsPath },\n // playwright on a cold tree downloads Chrome, boots the site's dev\n // server, and runs axe over every configured route. The shared 30 s\n // default in runAudits is fine for deps/lint/security but starves\n // playwright (mirrors the lighthouse fix shipped earlier).\n timeoutMs: 5 * 60_000,\n },\n );\n } catch (err) {\n await rm(specDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"a11y\",\n site: label,\n status: \"skip\",\n summary: \"npx/playwright not available\",\n };\n }\n throw err;\n }\n await rm(specDir, { recursive: true, force: true });\n\n const artifact = await readJsonMaybe<NormalizedA11y>(resultsPath);\n\n if (!artifact) {\n return {\n audit: \"a11y\",\n site: label,\n status: \"fail\",\n summary: `a11y: no results written (exit ${raw.code})${\n raw.stderr ? ` — ${raw.stderr.slice(0, 200)}` : \"\"\n }`,\n };\n }\n\n const hasSerious = (artifact.byImpact.serious ?? 0) > 0 || (artifact.byImpact.critical ?? 0) > 0;\n const hasAny = artifact.totalViolations > 0;\n\n const status: AuditResult[\"status\"] = hasSerious ? \"fail\" : hasAny ? \"warn\" : \"pass\";\n const summary =\n status === \"pass\"\n ? `a11y: 0 violations across ${a11yRoutes.length} routes`\n : `a11y: ${artifact.totalViolations} violations`;\n\n return {\n audit: \"a11y\",\n site: label,\n status,\n summary,\n details: artifact,\n };\n}\n","import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\nexport const playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: \"http://localhost:5173\",\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n command: \"npm run vite:dev\",\n url: \"http://localhost:5173/dev/a11y-fixtures\",\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n","import type { AuditName, AuditResult, Site } from \"../types.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { SpawnFn } from \"./util/spawn.js\";\nimport { depsAudit } from \"./deps.js\";\nimport { lintAudit } from \"./lint.js\";\nimport { securityAudit } from \"./security.js\";\nimport { lighthouseAudit } from \"./lighthouse.js\";\nimport { a11yAudit } from \"./a11y.js\";\n\nconst REGISTRY: Record<AuditName, (ctx: AuditContext) => Promise<AuditResult>> = {\n deps: depsAudit,\n lint: lintAudit,\n security: securityAudit,\n lighthouse: lighthouseAudit,\n a11y: a11yAudit,\n};\n\nexport const ALL_AUDIT_NAMES = Object.keys(REGISTRY) as AuditName[];\n\n/** Default per-audit spawn timeout when running via runAudits (30 s). */\nconst DEFAULT_AUDIT_TIMEOUT_MS = 30_000;\n\nfunction timedSpawn(timeoutMs: number): SpawnFn {\n return (cmd, args, opts = {}) =>\n defaultSpawn(cmd, args, { ...opts, timeoutMs: opts.timeoutMs ?? timeoutMs });\n}\n\n/** Single-audit runner with the same error-to-result conversion that\n * `runAudits` applies. Exposed so the CLI can wrap each audit in its\n * own progress task (listr2) and surface per-audit completion timing,\n * while keeping audit implementations UI-free. */\nexport async function runOneAudit(site: Site, name: AuditName): Promise<AuditResult> {\n if (!(name in REGISTRY)) throw new Error(`unknown audit: ${name}`);\n const spawn = timedSpawn(DEFAULT_AUDIT_TIMEOUT_MS);\n const label = site.name ?? site.path;\n try {\n return await REGISTRY[name]({ site, spawn });\n } catch (err) {\n return {\n audit: name,\n site: label,\n status: \"fail\",\n summary: `${name}: unexpected error — ${String(err)}`,\n };\n }\n}\n\nexport async function runAudits(site: Site, which?: AuditName[]): Promise<AuditResult[]> {\n const names = which ?? ALL_AUDIT_NAMES;\n for (const n of names) {\n if (!(n in REGISTRY)) throw new Error(`unknown audit: ${n}`);\n }\n return Promise.all(names.map((n) => runOneAudit(site, n)));\n}\n\nexport async function runAuditsAcross(sites: Site[], which?: AuditName[]): Promise<AuditResult[]> {\n const all = await Promise.all(sites.map((s) => runAudits(s, which)));\n return all.flat();\n}\n\nexport { depsAudit, lintAudit, securityAudit, lighthouseAudit, a11yAudit };\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport { listTrackedFiles, removeFromIndex } from \"../util/git.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"prettier-ignore\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n \"ci\",\n \"renovate-action\",\n \"renovate-config\",\n \"netlify\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing !== t.contents) diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n | { kind: \"noop\" }\n | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n return withRecipe({\n name: \"sync-configs\",\n site,\n plan: async () => {\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return { kind: \"noop\", notes: \"all targeted configs already match\" };\n }\n return { kind: \"apply\", plan: { templateDiffs, gitignorePlan } };\n },\n apply: async ({ templateDiffs, gitignorePlan }, { commit }) => {\n for (const t of templateDiffs) {\n const dest = join(site.path, t.path);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, t.contents, \"utf-8\");\n await commit(`chore: sync ${t.config} config from @reddoorla/maintenance`);\n }\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n await commit(`chore: sync gitignore from @reddoorla/maintenance`);\n }\n return { kind: \"ok\" };\n },\n });\n}\n","import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst prettierIgnore: ConfigTemplate = {\n config: \"prettier-ignore\",\n path: \".prettierignore\",\n contents: `pnpm-lock.yaml\n.svelte-kit/\nbuild/\n.netlify/\ndist/\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-netlify\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter({ edge: false, split: false }) },\n});\n`,\n};\n\nconst ci: ConfigTemplate = {\n config: \"ci\",\n path: \".github/workflows/ci.yml\",\n contents: `name: ci\non:\n pull_request:\n push:\n branches: [main]\npermissions:\n contents: read\njobs:\n ci:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n cache: pnpm\n - run: pnpm install --frozen-lockfile\n - run: pnpm exec prettier --check .\n - run: pnpm exec eslint .\n - run: pnpm exec svelte-kit sync && pnpm exec svelte-check --tsconfig ./tsconfig.json\n - run: pnpm build\n - run: pnpm exec playwright install --with-deps chromium\n - run: pnpm exec reddoor-maint audit --only a11y --fail-on-violations\n - name: Test (if present)\n run: |\n if node -e \"process.exit(require('./package.json').scripts?.test ? 0 : 1)\"; then\n pnpm test\n else\n echo \"no test script — skipping\"\n fi\n`,\n};\n\nconst renovateAction: ConfigTemplate = {\n config: \"renovate-action\",\n path: \".github/workflows/renovate.yml\",\n contents: `name: renovate\non:\n schedule:\n - cron: \"0 7 * * 1\"\n workflow_dispatch:\njobs:\n renovate:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: renovatebot/github-action@v40\n with:\n token: \\${{ secrets.RENOVATE_TOKEN }}\n env:\n RENOVATE_REPOSITORIES: \\${{ github.repository }}\n`,\n};\n\nconst renovateConfig: ConfigTemplate = {\n config: \"renovate-config\",\n path: \"renovate.json\",\n contents: `{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"config:recommended\"],\n \"schedule\": [\"before 7am on monday\"],\n \"packageRules\": [\n { \"matchUpdateTypes\": [\"patch\", \"minor\"], \"automerge\": true, \"platformAutomerge\": true },\n { \"matchUpdateTypes\": [\"major\"], \"automerge\": false }\n ]\n}\n`,\n};\n\nconst netlify: ConfigTemplate = {\n config: \"netlify\",\n path: \"netlify.toml\",\n contents: `[build]\n command = \"pnpm build\"\n publish = \"build/\"\n functions = \"functions/\"\n\n[build.environment]\n NODE_VERSION = \"22\"\n COREPACK_INTEGRITY_KEYS = \"0\"\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n prettierIgnore,\n lighthouse,\n playwrightA11y,\n svelte,\n ci,\n renovateAction,\n renovateConfig,\n netlify,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n \".reddoor-a11y/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n\n/** Derive `owner/repo` from a git remote URL (https or scp-style). Null if unparseable. */\nexport function parseOwnerRepo(remoteUrl: string): string | null {\n const trimmed = remoteUrl\n .trim()\n .replace(/\\.git$/, \"\")\n .replace(/\\/$/, \"\");\n // scp-style: git@github.com:owner/repo\n const scp = trimmed.match(/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:(.+)$/);\n const path = scp ? scp[1]! : trimmed.replace(/^https?:\\/\\/[^/]+\\//, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n if (segments.length < 2) return null;\n return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;\n}\n\n/** `origin` remote URL for a checkout, trimmed. Throws (via git) if there's no origin. */\nexport async function getRemoteUrl(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"remote\", \"get-url\", \"origin\"]);\n return stdout.trim();\n}\n\n/** Push a branch to origin, setting upstream. Throws on non-zero (execFile rejects). */\nexport async function push(cwd: string, branch: string): Promise<void> {\n await git(cwd, [\"push\", \"-u\", \"origin\", branch]);\n}\n","import type { RecipeName, RecipeResult, Site } from \"../types.js\";\nimport { branchName, commit as gitCommit, createBranch, isWorkingTreeClean } from \"../util/git.js\";\nimport { siteLabel } from \"../util/site.js\";\n\n/** Outcome of the read-only planning phase. `noop` and `failed` short-circuit\n * without creating a branch; `apply` carries the recipe-specific plan data\n * forward to the apply phase. */\nexport type RecipePlan<P> =\n | { kind: \"noop\"; notes?: string }\n | { kind: \"failed\"; notes: string }\n | { kind: \"apply\"; plan: P };\n\nexport type RecipeApplyCtx = {\n /** Stage all current changes and commit. Returns the SHA, or null if\n * nothing was staged. The wrapper accumulates SHAs into the final\n * RecipeResult. */\n commit: (message: string) => Promise<string | null>;\n /** Branch name that was created for this run. */\n branch: string;\n /** Site path — same as `site.path`. */\n cwd: string;\n};\n\nexport type RecipeApplyResult = { kind: \"ok\"; notes?: string } | { kind: \"failed\"; notes: string };\n\nexport type RecipeBody<P> = {\n name: RecipeName;\n site: Site;\n /** Inspect the site and decide: noop, failed, or proceed (with plan data\n * passed to apply). Runs before the working-tree clean check unless\n * `checkTreeFirst: true` is set, so most recipes can noop on a dirty\n * tree without throwing. */\n plan: () => Promise<RecipePlan<P>>;\n /** Make the actual changes. Use `ctx.commit(msg)` for each logical step;\n * the wrapper collects SHAs into `RecipeResult.commits`. Return\n * `{ kind: \"failed\", notes }` to abort partway and surface the failure. */\n apply: (plan: P, ctx: RecipeApplyCtx) => Promise<RecipeApplyResult>;\n /** Check working tree clean BEFORE `plan()` runs. Use only when plan\n * itself mutates the tree (e.g. `bump-deps` runs `pnpm install` in plan\n * for an accurate outdated probe). Default false — clean check happens\n * after plan only if plan returns proceed, allowing noop-on-dirty for\n * read-only plans (a tree with stray edits + no recipe work to do\n * should not throw). */\n checkTreeFirst?: boolean;\n};\n\n/** Wrap a recipe's plan/apply phases. Centralises the siteLabel /\n * clean-tree check / branch creation / commit accumulation / RecipeResult\n * construction boilerplate that every recipe used to re-implement. */\nexport async function withRecipe<P>(body: RecipeBody<P>): Promise<RecipeResult> {\n const label = siteLabel(body.site);\n\n if (body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n const planned = await body.plan();\n\n if (planned.kind === \"noop\") {\n return {\n recipe: body.name,\n site: label,\n status: \"noop\",\n commits: [],\n ...(planned.notes ? { notes: planned.notes } : {}),\n };\n }\n if (planned.kind === \"failed\") {\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: [],\n notes: planned.notes,\n };\n }\n\n if (!body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n const branch = branchName(body.name);\n await createBranch(body.site.path, branch);\n\n const shas: string[] = [];\n const result = await body.apply(planned.plan, {\n cwd: body.site.path,\n branch,\n commit: async (msg) => {\n const sha = await gitCommit(body.site.path, msg);\n if (sha) shas.push(sha);\n return sha;\n },\n });\n\n if (result.kind === \"failed\") {\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: shas,\n notes: result.notes,\n };\n }\n\n const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;\n return {\n recipe: body.name,\n site: label,\n status: shas.length > 0 ? \"applied\" : \"noop\",\n commits: shas,\n notes,\n };\n}\n","import { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type BumpDepsGroup = \"patch\" | \"minor\" | \"major\";\n\nexport type BumpDepsOptions = {\n group?: BumpDepsGroup;\n spawn?: SpawnFn;\n};\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction outdatedFlagsForGroup(group: BumpDepsGroup): string[] {\n if (group === \"major\") return [\"--latest\"];\n if (group === \"minor\") return [];\n return [\"--depth\", \"0\"];\n}\n\nfunction upFlagsForGroup(group: BumpDepsGroup): string[] {\n if (group === \"major\") return [\"--latest\"];\n return [];\n}\n\ntype Plan = { group: BumpDepsGroup };\n\nexport async function bumpDeps(site: Site, opts: BumpDepsOptions = {}): Promise<RecipeResult> {\n const group: BumpDepsGroup = opts.group ?? \"minor\";\n const spawn = opts.spawn ?? defaultSpawn;\n\n return withRecipe<Plan>({\n name: \"bump-deps\",\n site,\n // pnpm install (in plan) mutates the lockfile, so the clean-tree check\n // MUST happen first — otherwise a desynced-lockfile resync would silently\n // land on top of whatever else was in the tree.\n checkTreeFirst: true,\n plan: async () => {\n // Pre-flight: the recipe is pnpm-only. A package-lock.json or yarn.lock\n // without pnpm-lock.yaml means the site is still on a different package\n // manager; we refuse to run rather than emit confusing pnpm errors.\n const hasPnpmLock = await exists(join(site.path, \"pnpm-lock.yaml\"));\n if (!hasPnpmLock) {\n const hasNpmLock = await exists(join(site.path, \"package-lock.json\"));\n const hasYarnLock = await exists(join(site.path, \"yarn.lock\"));\n if (hasNpmLock || hasYarnLock) {\n const competing = hasNpmLock ? \"package-lock.json\" : \"yarn.lock\";\n return {\n kind: \"failed\",\n notes: `site has ${competing} but no pnpm-lock.yaml — run convert-to-pnpm first`,\n };\n }\n }\n\n // Ensure the lockfile reflects the current package.json before we ask\n // pnpm what's outdated. Without this, a desynced lockfile can produce\n // stale or empty outdated reports.\n await spawn(\"pnpm\", [\"install\"], { cwd: site.path, streaming: true });\n\n const outdated = await spawn(\n \"pnpm\",\n [\"outdated\", \"--json\", ...outdatedFlagsForGroup(group)],\n { cwd: site.path },\n );\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(outdated.stdout || \"{}\") as Record<string, unknown>;\n } catch {\n parsed = {};\n }\n if (Object.keys(parsed).length === 0) {\n return { kind: \"noop\", notes: `pnpm outdated reported nothing for group=${group}` };\n }\n return { kind: \"apply\", plan: { group } };\n },\n apply: async ({ group: g }, { commit, cwd }) => {\n // Stream pnpm up's output so long-running upgrades don't look frozen.\n await spawn(\"pnpm\", [\"up\", ...upFlagsForGroup(g)], { cwd, streaming: true });\n await commit(`chore(deps): bump dependencies (${g})`);\n return { kind: \"ok\" };\n },\n });\n}\n","import { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { readPackageJson } from \"../../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { bumpToSvelte5Versions } from \"./step-bump-versions.js\";\nimport { migrateSvelteConfig } from \"./step-svelte-config.js\";\nimport { runSvelteMigrate } from \"./step-svelte-migrate.js\";\nimport { upgradeTailwind } from \"./step-tailwind-upgrade.js\";\nimport { applyGotchaCodemods } from \"./step-gotchas.js\";\nimport { verifyMigration } from \"./step-verify.js\";\nimport { writeMigrationSummary } from \"./step-summary.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\n\nexport type UpgradeSvelte4to5Options = {\n spawn?: SpawnFn;\n};\n\nasync function alreadyOnSvelte5(cwd: string): Promise<boolean> {\n try {\n const pkg = await readPackageJson(join(cwd, \"package.json\"));\n const v = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte;\n return !!v && /^\\^?5\\./.test(v);\n } catch {\n return false;\n }\n}\n\nexport async function upgradeSvelte4to5(\n site: Site,\n opts: UpgradeSvelte4to5Options = {},\n): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n\n return withRecipe<true>({\n name: \"svelte-4-to-5\",\n site,\n plan: async () => {\n if (await alreadyOnSvelte5(site.path)) {\n return { kind: \"noop\", notes: \"site already declares svelte ^5.x\" };\n }\n return { kind: \"apply\", plan: true };\n },\n apply: async (_plan, { commit, cwd }) => {\n const bumped = await bumpToSvelte5Versions(cwd);\n if (bumped) {\n await commit(\"chore(svelte5): bump svelte/kit/vite/vite-plugin-svelte\");\n }\n\n const configChanged = await migrateSvelteConfig(cwd);\n if (configChanged) {\n await commit(\"refactor(svelte5): migrate svelte.config.js (drop vitePreprocess)\");\n }\n\n const migrate = await runSvelteMigrate(cwd, spawn);\n if (migrate.ran) {\n await commit(\"refactor(svelte5): run official svelte-migrate codemod\");\n }\n\n const tw = await upgradeTailwind(cwd, spawn);\n if (tw.ran) {\n await commit(\"chore(svelte5): tailwindcss 3 → 4 upgrade\");\n }\n\n const codemods = await applyGotchaCodemods(cwd);\n if (codemods.filesChanged > 0) {\n await commit(`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`);\n }\n\n await verifyMigration(cwd, spawn);\n await commit(\"chore(svelte5): pnpm install + check\");\n\n await writeMigrationSummary({\n cwd,\n filesChangedByCodemods: codemods.filesChanged,\n svelteMigrateRan: migrate.ran,\n tailwindUpgraded: tw.ran,\n });\n await commit(\"docs(svelte5): add MIGRATION_SVELTE_5.md summary\");\n\n return { kind: \"ok\" };\n },\n });\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\n\nexport type PackageJsonLike = {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n [key: string]: unknown;\n};\n\nexport async function readPackageJson(path: string): Promise<PackageJsonLike> {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as PackageJsonLike;\n}\n\n/** Sniff the indent style (tab vs 2 vs 4 vs N spaces) from existing package.json\n * content by looking at the first indented `\"key\"` line. Defaults to two spaces. */\nfunction detectIndentFromContent(raw: string): string {\n const match = raw.match(/\\n([ \\t]+)\"/);\n return match ? (match[1] ?? \" \") : \" \";\n}\n\nexport async function writePackageJson(path: string, pkg: PackageJsonLike): Promise<void> {\n let indent = \" \";\n try {\n const existing = await readFile(path, \"utf-8\");\n indent = detectIndentFromContent(existing);\n } catch {\n // file doesn't exist yet — first write — keep the 2-space default\n }\n const content = JSON.stringify(pkg, null, indent) + \"\\n\";\n await writeFile(path, content, \"utf-8\");\n}\n\nexport type BumpDepMode =\n | \"ensure\" // default: add to devDependencies if missing\n | \"bump-only\"; // never add; only update existing entries\n\nexport type BumpDepOptions = {\n mode?: BumpDepMode;\n};\n\nexport function bumpDep(\n pkg: PackageJsonLike,\n name: string,\n version: string,\n opts: BumpDepOptions = {},\n): PackageJsonLike {\n const mode = opts.mode ?? \"ensure\";\n\n const next: PackageJsonLike = {\n ...pkg,\n };\n\n if (pkg.dependencies) {\n next.dependencies = { ...pkg.dependencies };\n }\n if (pkg.devDependencies) {\n next.devDependencies = { ...pkg.devDependencies };\n }\n\n if (next.dependencies && name in next.dependencies) {\n if (next.dependencies[name] === version) return pkg;\n next.dependencies[name] = version;\n return next;\n }\n if (next.devDependencies && name in next.devDependencies) {\n if (next.devDependencies[name] === version) return pkg;\n next.devDependencies[name] = version;\n return next;\n }\n // Not present in either map. bump-only leaves the pkg alone so recipes\n // can express \"raise the floor on packages this site already uses\" without\n // also installing every related dep across the fleet.\n if (mode === \"bump-only\") return pkg;\n next.devDependencies = { ...(next.devDependencies ?? {}), [name]: version };\n return next;\n}\n","import { join } from \"node:path\";\nimport { readPackageJson, writePackageJson, bumpDep } from \"../../util/pkg.js\";\n\nconst SVELTE_5_VERSIONS: Record<string, string> = {\n svelte: \"^5.55.5\",\n \"@sveltejs/kit\": \"^2.59.0\",\n \"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n \"@sveltejs/adapter-netlify\": \"^6.0.4\",\n \"@sveltejs/adapter-auto\": \"^7.0.0\",\n vite: \"^8.0.10\",\n \"svelte-check\": \"^4.4.7\",\n typescript: \"^6.0.3\",\n \"typescript-svelte-plugin\": \"^0.3.52\",\n};\n\nexport async function bumpToSvelte5Versions(cwd: string): Promise<boolean> {\n const pkgPath = join(cwd, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n let next = pkg;\n // bump-only: a svelte-4 site that doesn't declare e.g. adapter-netlify\n // should not get it added during the upgrade.\n for (const [name, version] of Object.entries(SVELTE_5_VERSIONS)) {\n next = bumpDep(next, name, version, { mode: \"bump-only\" });\n }\n if (next === pkg) return false;\n await writePackageJson(pkgPath, next);\n return true;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst VITE_PLUGIN_PKG = \"@sveltejs/vite-plugin-svelte\";\n\n/** Match an import statement that pulls one or more named bindings from\n * `@sveltejs/vite-plugin-svelte`. Group 1 is the comma-separated name list. */\nconst IMPORT_FROM_VITE_PLUGIN = new RegExp(\n String.raw`^import\\s+\\{\\s*([^}]+?)\\s*\\}\\s+from\\s+[\"']` +\n VITE_PLUGIN_PKG.replace(/[/]/g, \"\\\\/\") +\n String.raw`[\"'];?[ \\t]*\\n`,\n \"m\",\n);\n\n/** Rewrite the import to drop only `vitePreprocess`, preserving any other\n * named bindings. If `vitePreprocess` was the sole import, the whole line\n * is removed. */\nfunction dropVitePreprocessImport(source: string): string {\n return source.replace(IMPORT_FROM_VITE_PLUGIN, (full, names: string) => {\n const remaining = names\n .split(\",\")\n .map((n) => n.trim())\n .filter((n) => n.length > 0 && n !== \"vitePreprocess\");\n if (remaining.length === 0) return \"\"; // drop entire line including its trailing newline\n return `import { ${remaining.join(\", \")} } from \"${VITE_PLUGIN_PKG}\";\\n`;\n });\n}\n\n/** Find the end of a balanced-paren call starting at `openIdx`, which must\n * point at the `(` character. Returns the index of the matching `)`, or -1\n * if unbalanced. */\nfunction findMatchingParen(source: string, openIdx: number): number {\n if (source[openIdx] !== \"(\") return -1;\n let depth = 0;\n for (let i = openIdx; i < source.length; i++) {\n const ch = source[i];\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/** Remove a `preprocess: vitePreprocess(<anything>),?` key from a config\n * object. Handles the call with empty parens or with an options object. */\nfunction dropPreprocessKey(source: string): string {\n // Anchor on the start of the preprocess key on its own line so we don't\n // also strip whitespace / commas from neighboring keys.\n const startRe = /^(\\s*)preprocess:\\s*vitePreprocess\\(/m;\n const m = startRe.exec(source);\n if (!m) return source;\n\n const indent = m[1] ?? \"\";\n const parenOpenAbs = m.index + m[0].length - 1; // points at `(`\n const parenCloseAbs = findMatchingParen(source, parenOpenAbs);\n if (parenCloseAbs < 0) return source;\n\n // Consume an optional trailing comma and whitespace through end-of-line.\n let tailIdx = parenCloseAbs + 1;\n while (tailIdx < source.length && /[ \\t,]/.test(source[tailIdx] ?? \"\")) tailIdx++;\n if (source[tailIdx] === \"\\n\") tailIdx++;\n\n return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\\\n`), \"\");\n}\n\nexport async function migrateSvelteConfig(cwd: string): Promise<boolean> {\n const path = join(cwd, \"svelte.config.js\");\n let src: string;\n try {\n src = await readFile(path, \"utf-8\");\n } catch {\n return false;\n }\n\n let next = src;\n next = dropPreprocessKey(next);\n next = dropVitePreprocessImport(next);\n\n if (next === src) return false;\n await writeFile(path, next, \"utf-8\");\n return true;\n}\n","import { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\n\nexport async function runSvelteMigrate(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<{ ran: boolean; stderr: string }> {\n try {\n const { code, stderr } = await spawn(\n \"npx\",\n [\"--yes\", \"svelte-migrate\", \"svelte-5\", \"--no-install\"],\n { cwd, timeoutMs: 5 * 60_000 },\n );\n if (code !== 0) {\n return { ran: false, stderr };\n }\n return { ran: true, stderr };\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return { ran: false, stderr: \"npx unavailable\" };\n }\n throw err;\n }\n}\n","import { readPackageJson } from \"../../util/pkg.js\";\nimport { join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\n\nexport async function upgradeTailwind(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<{ ran: boolean; reason?: string }> {\n const pkg = await readPackageJson(join(cwd, \"package.json\"));\n const tailwindVersion = pkg.devDependencies?.tailwindcss ?? pkg.dependencies?.tailwindcss;\n if (!tailwindVersion) return { ran: false, reason: \"tailwindcss not installed\" };\n if (/^\\^?4\\./.test(tailwindVersion)) return { ran: false, reason: \"already on tailwind 4.x\" };\n\n try {\n const { code, stderr } = await spawn(\"npx\", [\"--yes\", \"@tailwindcss/upgrade\", \"--force\"], {\n cwd,\n timeoutMs: 5 * 60_000,\n });\n if (code !== 0) return { ran: false, reason: stderr.slice(0, 200) };\n return { ran: true };\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return { ran: false, reason: \"npx unavailable\" };\n }\n throw err;\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport { onEventToHandler } from \"./codemods/on-event-to-handler.js\";\nimport { exportLetToProps } from \"./codemods/dollar-props.js\";\nimport { removeDollarRestProps } from \"./codemods/dollar-restprops.js\";\nimport { stateEffectSyncToDerived } from \"./codemods/state-effect-sync.js\";\nimport { dollarPropsClass } from \"./codemods/dollar-props-class.js\";\nimport { legacyReactiveToRunes } from \"./codemods/legacy-reactive.js\";\n\nconst SVELTE_GLOBS = [\"src/**/*.svelte\"];\nconst IGNORE = [\"node_modules/**\", \".svelte-kit/**\", \"build/**\"];\n\ntype Codemod = (src: string) => string;\n\n// Order matters: exportLetToProps creates the $props() destructuring that\n// dollarPropsClass extends with a `class:` named prop.\nconst CODEMODS: Codemod[] = [\n onEventToHandler,\n exportLetToProps,\n removeDollarRestProps,\n stateEffectSyncToDerived,\n dollarPropsClass,\n legacyReactiveToRunes,\n];\n\nexport type CodemodChange = { rel: string; after: string };\n\nexport async function planGotchaCodemods(cwd: string): Promise<CodemodChange[]> {\n const changes: CodemodChange[] = [];\n const relPaths = await glob(SVELTE_GLOBS, { cwd, ignore: IGNORE, absolute: false });\n for (const rel of relPaths) {\n const path = join(cwd, rel);\n const before = await readFile(path, \"utf-8\");\n const after = CODEMODS.reduce((s, fn) => fn(s), before);\n if (after !== before) changes.push({ rel, after });\n }\n return changes;\n}\n\nexport async function applyGotchaCodemods(cwd: string): Promise<{ filesChanged: number }> {\n const changes = await planGotchaCodemods(cwd);\n for (const c of changes) {\n await writeFile(join(cwd, c.rel), c.after, \"utf-8\");\n }\n return { filesChanged: changes.length };\n}\n","const SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst SIMPLE_ON_EVENT = /\\bon:([a-z]+)(?=\\s*=)/g;\nconst MODIFIER_EVENT = /\\bon:[a-z]+\\|[a-zA-Z]+(?=\\s*=)/g;\n\n/** Svelte 5 removed event modifier syntax (`on:click|preventDefault={fn}`).\n * The rewrite is non-trivial — the modifier behavior must be inlined into\n * the handler body — so this codemod doesn't attempt it automatically.\n * Instead it inserts a `@migration-task` marker immediately above each\n * offending element so the user gets a visible audit trail rather than\n * a silent build error from the Svelte 5 compiler. */\nfunction flagEventModifiers(source: string): string {\n const insertions: Array<{ tagStart: number; indent: string; modifier: string }> = [];\n let m: RegExpExecArray | null;\n MODIFIER_EVENT.lastIndex = 0;\n while ((m = MODIFIER_EVENT.exec(source)) !== null) {\n const tagStart = source.lastIndexOf(\"<\", m.index);\n if (tagStart === -1) continue;\n\n // Idempotency: if the line immediately above the tag already carries an\n // @migration-task marker for this site, don't double-insert on re-run.\n const prevLineEnd = tagStart - 1;\n if (prevLineEnd >= 0) {\n const prevLineStart = source.lastIndexOf(\"\\n\", prevLineEnd - 1) + 1;\n const prevLine = source.slice(prevLineStart, prevLineEnd + 1);\n if (/<!--\\s*@migration-task/.test(prevLine)) continue;\n }\n\n const lineStart = source.lastIndexOf(\"\\n\", tagStart - 1) + 1;\n const indent = source.slice(lineStart, tagStart);\n const safeIndent = /^[ \\t]*$/.test(indent) ? indent : \"\";\n insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });\n }\n\n // Apply back-to-front so earlier insertion offsets stay valid.\n let out = source;\n for (let i = insertions.length - 1; i >= 0; i--) {\n const { tagStart, indent, modifier } = insertions[i]!;\n const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\\`${modifier}\\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->\\n${indent}`;\n out = out.slice(0, tagStart) + comment + out.slice(tagStart);\n }\n return out;\n}\n\nexport function onEventToHandler(source: string): string {\n const masked: string[] = [];\n const placeholder = (i: number): string => ` SCRIPT_${i} `;\n const intermediate = source.replace(SCRIPT_BLOCK, (match) => {\n masked.push(match);\n return placeholder(masked.length - 1);\n });\n\n let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name: string) => `on${name}`);\n processed = flagEventModifiers(processed);\n\n let out = processed;\n masked.forEach((blk, i) => {\n out = out.replace(placeholder(i), blk);\n });\n\n return out;\n}\n","const SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst EXPORT_LET = /^\\s*export\\s+let\\s+(\\w+)\\s*(?::\\s*([^=;\\n]+))?\\s*(?:=\\s*([^;\\n]+))?;?\\s*$/gm;\n\ntype Prop = { name: string; type?: string | undefined; defaultExpr?: string | undefined };\n\nfunction transformScript(scriptBody: string, isTs: boolean): { body: string; changed: boolean } {\n const props: Prop[] = [];\n const cleaned = scriptBody.replace(\n EXPORT_LET,\n (_full, name: string, type?: string, defaultExpr?: string) => {\n props.push({\n name,\n type: type?.trim(),\n defaultExpr: defaultExpr?.trim(),\n });\n return \"\";\n },\n );\n if (props.length === 0) return { body: scriptBody, changed: false };\n\n const destructured = props\n .map((p) => (p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name))\n .join(\", \");\n\n let decl: string;\n if (isTs) {\n const typeSig = props\n .map((p) => {\n const optional = p.defaultExpr ? \"?\" : \"\";\n return `${p.name}${optional}: ${p.type ?? \"unknown\"}`;\n })\n .join(\"; \");\n decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;\n } else {\n decl = ` let { ${destructured} } = $props();`;\n }\n\n const next = cleaned.replace(/^(\\s*)/, (m) => `${m}${decl}\\n`);\n return { body: next, changed: true };\n}\n\nexport function exportLetToProps(source: string): string {\n const match = source.match(SCRIPT_BLOCK);\n if (!match) return source;\n const attrs = match[1] ?? \"\";\n const inner = match[2] ?? \"\";\n const isTs = /\\blang=[\"']ts[\"']/.test(attrs);\n const { body, changed } = transformScript(inner, isTs);\n if (!changed) return source;\n return source.replace(SCRIPT_BLOCK, (full) => full.replace(inner, body));\n}\n","/** Find the index of the closing quote for a string literal that opens at\n * `openIdx`. Handles backslash escapes. Returns -1 if the string is\n * unterminated.\n *\n * Treats backtick template literals the same as `'…'` / `\"…\"` — the\n * closing backtick terminates. Callers needing precise `${…}` interpolation\n * handling will need a real parser; this helper is intentionally simple\n * and good enough for the codemod-grade string masking we do today. */\nexport function findStringEnd(source: string, openIdx: number): number {\n const quote = source[openIdx];\n let i = openIdx + 1;\n while (i < source.length) {\n const ch = source[i];\n if (ch === \"\\\\\") {\n i += 2;\n continue;\n }\n if (ch === quote) return i;\n i++;\n }\n return -1;\n}\n","/** Locate `interface $$Props {` declarations and remove them, including\n * the matching closing `}` even if the body has nested braces or spans\n * multiple lines. Regex alone can't do balanced-brace matching, so we\n * walk the string manually. */\nfunction removeInterfaceBlock(source: string): string {\n const re = /^\\s*interface\\s+\\$\\$Props\\s*\\{/m;\n let out = source;\n while (true) {\n const match = re.exec(out);\n if (!match) return out;\n\n const openBraceIdx = match.index + match[0].length - 1;\n let depth = 1;\n let i = openBraceIdx + 1;\n while (i < out.length && depth > 0) {\n const ch = out[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") depth--;\n i++;\n }\n if (depth !== 0) return out; // unbalanced; bail rather than corrupt\n\n // Consume trailing whitespace through end-of-line.\n let endIdx = i;\n while (endIdx < out.length && /[ \\t]/.test(out[endIdx] ?? \"\")) endIdx++;\n if (out[endIdx] === \"\\n\") endIdx++;\n\n out = out.slice(0, match.index) + out.slice(endIdx);\n }\n}\n\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\n/** Mask every `'…'`, `\"…\"`, and template literal in `source` with a placeholder\n * so subsequent regex passes can rewrite identifiers without corrupting string\n * contents. Returns the masked body and a function to restore originals. */\nfunction maskStringLiterals(source: string): {\n masked: string;\n restore: (s: string) => string;\n} {\n const strings: string[] = [];\n let out = \"\";\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeIdx = findStringEnd(source, i);\n if (closeIdx === -1) {\n out += source.slice(i);\n break;\n }\n const literal = source.slice(i, closeIdx + 1);\n out += `__RDMNT_STR_${strings.length}__`;\n strings.push(literal);\n i = closeIdx + 1;\n } else {\n out += ch;\n i++;\n }\n }\n return {\n masked: out,\n restore: (s) => s.replace(/__RDMNT_STR_(\\d+)__/g, (_full, idx) => strings[Number(idx)] ?? \"\"),\n };\n}\n\nconst PROPS_DECL = /let\\s*\\{([^}]*)\\}\\s*(?::\\s*\\{([^}]*)\\})?\\s*=\\s*\\$props\\(\\)\\s*;?/;\n\n/** If the script declares `let { … } = $props();` (with or without an inline\n * type annotation) and doesn't already collect `...rest`, inject it. For TS,\n * widen the inline type with an `[key: string]: unknown` index signature so\n * the rest binding actually captures excess attributes (without the widening,\n * TS infers `rest` as `{}` and the spread forwards nothing). */\nfunction injectRestIntoProps(scriptBody: string): string {\n const match = scriptBody.match(PROPS_DECL);\n if (!match) return scriptBody;\n const destructured = match[1] ?? \"\";\n if (/\\.\\.\\.\\s*\\w+/.test(destructured)) return scriptBody; // already has rest\n\n // Strip any trailing comma left over from a multi-line destructuring shape\n // (e.g. `{ foo, bar, }`). Without this, the template literal below emits\n // `bar,, ...rest` — invalid syntax that the codemod was happily committing\n // (regression: caltex's Accordian.svelte, 2026-05-27).\n const trimmed = destructured.trim().replace(/,\\s*$/, \"\");\n const newDestructured = trimmed === \"\" ? \" ...rest \" : ` ${trimmed}, ...rest `;\n\n let replacement: string;\n if (match[2] !== undefined) {\n const typeBody = match[2];\n const hasIndexSig = /\\[\\s*key\\s*:\\s*string\\s*\\]\\s*:/.test(typeBody);\n const newTypeBody = hasIndexSig\n ? typeBody\n : `${typeBody.trimEnd().replace(/;?\\s*$/, \"\")}; [key: string]: unknown `;\n replacement = `let {${newDestructured}}: {${newTypeBody}} = $props();`;\n } else {\n replacement = `let {${newDestructured}} = $props();`;\n }\n return scriptBody.replace(PROPS_DECL, replacement);\n}\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst HAS_PROPS_CALL = /\\$props\\(\\s*\\)/;\n\nexport function removeDollarRestProps(source: string): string {\n const next = removeInterfaceBlock(source);\n\n const scriptMatch = next.match(SCRIPT_BLOCK);\n if (!scriptMatch) return next;\n if (!HAS_PROPS_CALL.test(scriptMatch[2] ?? \"\")) {\n // No $props() in this script — refuse to rewrite $$restProps anywhere, since\n // doing so would emit references to an undeclared identifier. The user sees\n // the original $$restProps and a clear Svelte 5 build error to migrate by hand.\n return next;\n }\n\n const scriptInner = scriptMatch[2] ?? \"\";\n const { masked, restore } = maskStringLiterals(scriptInner);\n let processed = injectRestIntoProps(masked);\n processed = processed.replace(/\\$\\$restProps/g, \"rest\");\n const restoredInner = restore(processed);\n\n // Use a function callback so `$$` in the restored script body isn't\n // interpreted as the `$` substitution pattern by String.prototype.replace.\n const newScriptBlock = scriptMatch[0].replace(scriptInner, () => restoredInner);\n const before = next.slice(0, scriptMatch.index!);\n const after = next.slice(scriptMatch.index! + scriptMatch[0].length);\n\n // Template (outside script) gets a plain swap. Template attribute strings\n // containing the literal text \"$$restProps\" are vanishingly rare in practice;\n // accept the limitation rather than parse the whole template.\n return (\n before.replace(/\\$\\$restProps/g, \"rest\") +\n newScriptBlock +\n after.replace(/\\$\\$restProps/g, \"rest\")\n );\n}\n","/**\n * Collapses the \"manual sync state with prop\" anti-pattern into `$derived`.\n *\n * Input:\n * let content = $state(data.page.data);\n * $effect(() => { data; content = data.page.data });\n *\n * Output:\n * let content = $derived(data.page.data);\n *\n * Only transforms when the `$state(...)` initializer expression matches the\n * effect's right-hand assignment exactly (after trim). Intervening statements\n * between the `let` and the `$effect` block prevent the match — keeps the\n * codemod conservative.\n *\n * Triggered by Svelte 5's `state_referenced_locally` warning, which fires\n * whenever a local `let X = $state(prop.expr)` captures a prop reference\n * only at init time.\n */\n// `;?` before the closing `}` so the multi-line effect form matches:\n// $effect(() => {\n// data;\n// content = data.page.data;\n// });\n// as well as the single-line form: $effect(() => { data; content = data.page.data })\nconst PATTERN =\n /let\\s+(\\w+)\\s*=\\s*\\$state\\(\\s*([^)]+?)\\s*\\)\\s*;[ \\t\\r\\n]*\\$effect\\(\\s*\\(\\s*\\)\\s*=>\\s*\\{\\s*\\w+\\s*;\\s*\\1\\s*=\\s*([^;}]+?)\\s*;?\\s*\\}\\s*\\)\\s*;?/g;\n\nexport function stateEffectSyncToDerived(source: string): string {\n return source.replace(PATTERN, (full, name: string, initExpr: string, effectExpr: string) => {\n if (initExpr.trim() !== effectExpr.trim()) return full;\n return `let ${name} = $derived(${initExpr.trim()});`;\n });\n}\n","/**\n * Converts the legacy `$$props.class` pattern (passing extra HTML class from\n * a parent component) to a Svelte 5 named-prop destructuring.\n *\n * Input:\n * <script lang=\"ts\">\n * let { foo }: { foo?: string } = $props();\n * </script>\n * <div class=\"other {$$props.class || ''}\">x</div>\n *\n * Output:\n * <script lang=\"ts\">\n * let { foo, class: className = \"\" }: { foo?: string; class?: string } = $props();\n * </script>\n * <div class=\"other {className || ''}\">x</div>\n *\n * Triggered by Svelte 5 build errors:\n * \"Cannot use `$$props` in runes mode\" (svelte.dev/e/legacy_props_invalid)\n *\n * The original svelte-migrate tool flagged this with a `@migration-task`\n * comment because it couldn't safely combine `$$props` with already-named\n * props. We can: `class` is the dominant case across the reddoor fleet,\n * so we destructure it as `class: className = \"\"` (renamed because `class`\n * is a JS reserved word as a bare binding) and rewrite template references.\n *\n * Conservative: only transforms files that have BOTH a template\n * `$$props.class` reference AND an existing `$props()` destructuring.\n * Files using `$$props.class` without a `$props()` declaration are left\n * for the `exportLetToProps` codemod to handle in a prior pass.\n */\n// Note: lazy `[\\s\\S]*?` (not `[^}]*`) so default values containing braces\n// — `() => {}`, `{ foo: 1 }`, etc. — don't truncate the match early.\nconst PROPS_DESTRUCTURE = /let\\s*\\{([\\s\\S]*?)\\}(\\s*:\\s*\\{([\\s\\S]*?)\\})?\\s*=\\s*\\$props\\(\\)/;\n// Two regexes: a stateless one for \"does this string contain $$props.class?\"\n// existence checks, and the /g one for the iterating template rewrite. Mixing\n// .test() and .replace() on the same /g regex makes lastIndex management\n// fragile — easy to forget the reset on a future edit.\nconst HAS_DOLLAR_PROPS_CLASS = /\\$\\$props\\.class\\b/;\nconst DOLLAR_PROPS_CLASS_GLOBAL = /\\$\\$props\\.class\\b/g;\nconst DOLLAR_PROPS_ANY = /\\$\\$props\\b/;\nconst SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst MIGRATION_TASK = /^<!--\\s*@migration-task[\\s\\S]*?-->\\s*\\n?/gm;\nconst IDENT = \"className\";\n\nfunction maskScripts(source: string): { masked: string; blocks: string[] } {\n const blocks: string[] = [];\n const masked = source.replace(SCRIPT_BLOCK, (m) => {\n blocks.push(m);\n return `__SCRIPT_${blocks.length - 1}__`;\n });\n return { masked, blocks };\n}\n\nfunction restoreScripts(masked: string, blocks: string[]): string {\n let out = masked;\n blocks.forEach((blk, i) => {\n out = out.replace(`__SCRIPT_${i}__`, blk);\n });\n return out;\n}\n\nexport function dollarPropsClass(source: string): string {\n // Bail early if the template doesn't reference $$props.class\n const { masked } = maskScripts(source);\n if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;\n\n // Bail if there's no $props() destructuring to extend\n if (!PROPS_DESTRUCTURE.test(source)) return source;\n\n let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {\n // Already migrated (someone added class manually)\n if (/\\bclass\\s*:/.test(body as string)) return full;\n\n const cleanBody = (body as string).trim().replace(/,\\s*$/, \"\").trim();\n const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = \"\"` : `class: ${IDENT} = \"\"`;\n\n if (typeAnno) {\n const cleanType = ((typeBody as string) ?? \"\").trim().replace(/;\\s*$/, \"\").trim();\n const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;\n return `let { ${newBody} }: { ${newType} } = $props()`;\n }\n return `let { ${newBody} } = $props()`;\n });\n\n // Replace $$props.class in template only (re-mask after destructuring update)\n const reMasked = maskScripts(updated);\n const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);\n updated = restoreScripts(templateRewritten, reMasked.blocks);\n\n // Strip @migration-task comments if no $$props references remain anywhere\n // EXCEPT inside those very comments. Strip-then-check, restore if still dirty.\n const stripped = updated.replace(MIGRATION_TASK, \"\");\n if (!DOLLAR_PROPS_ANY.test(stripped)) {\n updated = stripped;\n }\n\n return updated;\n}\n","/**\n * Converts Svelte 4 `$:` reactive statements to Svelte 5 runes.\n *\n * - `$: var = expr;` → `let var = $derived(expr);`\n * - `$: { body }` → `$effect(() => { body });`\n *\n * Triggered by:\n * \"`$:` is not allowed in runes mode, use `$derived` or `$effect` instead\"\n * (svelte.dev/e/legacy_reactive_statement_invalid)\n *\n * Block patterns become `$effect` rather than per-variable `$derived` calls\n * because the block typically mutates multiple already-declared `let`\n * variables with conditional logic — too contextual for a safe automatic\n * decomposition into discrete derived values. The user can refine each\n * `$effect` into idiomatic `$derived` calls afterward if desired.\n *\n * Scoped to `<script>` content only — `$:` in template/style text is left\n * alone (it would only ever appear there as literal text anyway).\n */\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/g;\nconst SIMPLE_REACTIVE = /^([ \\t]*)\\$:\\s*(\\w+)\\s*=\\s*([^;\\n]+);?[ \\t]*$/gm;\nconst BLOCK_REACTIVE_HEAD = /(^|\\n)([ \\t]*)\\$:\\s*\\{/g;\n\nfunction findMatchingClose(source: string, openIdx: number): number {\n let depth = 0;\n let i = openIdx;\n while (i < source.length) {\n const ch = source[i];\n // Skip over string literals so braces inside strings don't fool the counter.\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeStr = findStringEnd(source, i);\n if (closeStr === -1) return -1;\n i = closeStr + 1;\n continue;\n }\n // Skip over comments so braces inside `// }` or `/* } */` don't fool the\n // counter. Regression: the old version silently corrupted source (depth\n // went off, real closing brace mis-matched) on inputs like `$: { // } ... }`.\n // The corrupted output still compiles in Svelte 5 — no parser to scream.\n if (ch === \"/\") {\n const next = source[i + 1];\n if (next === \"/\") {\n const eol = source.indexOf(\"\\n\", i + 2);\n i = eol === -1 ? source.length : eol; // step onto newline; outer loop handles it\n continue;\n }\n if (next === \"*\") {\n const end = source.indexOf(\"*/\", i + 2);\n if (end === -1) return -1; // unterminated block comment — bail rather than corrupt\n i = end + 2;\n continue;\n }\n }\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) return i;\n }\n i++;\n }\n return -1;\n}\n\n/** Flag each converted `$effect` block for manual review. The conversion is\n * syntactically safe (compiles), but if any of the locals the block mutates\n * was declared as plain `let` (not `$state`), the `$effect` runs once on\n * mount and never again — code silently loses its reactivity. We can't\n * detect that automatically (it would require scope analysis on the\n * declaration sites), so we leave a breadcrumb for the human reviewer. */\nconst MIGRATION_MARKER =\n \"// @migration-task: $effect won't trigger UI updates on plain `let` bindings — refine mutated locals to $state or split into per-variable $derived.\";\n\nfunction transformBlocks(body: string): string {\n const out: string[] = [];\n let last = 0;\n BLOCK_REACTIVE_HEAD.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = BLOCK_REACTIVE_HEAD.exec(body)) !== null) {\n const leadingNewline = m[1] ?? \"\";\n const indent = m[2] ?? \"\";\n const headEnd = m.index + m[0].length; // position just after `{`\n const openBraceIdx = headEnd - 1;\n const closeBraceIdx = findMatchingClose(body, openBraceIdx);\n if (closeBraceIdx === -1) continue;\n out.push(body.slice(last, m.index));\n out.push(leadingNewline);\n const blockBody = body.slice(openBraceIdx + 1, closeBraceIdx);\n out.push(`${indent}${MIGRATION_MARKER}\\n`);\n out.push(`${indent}$effect(() => {${blockBody}});`);\n last = closeBraceIdx + 1;\n BLOCK_REACTIVE_HEAD.lastIndex = last;\n }\n out.push(body.slice(last));\n return out.join(\"\");\n}\n\nfunction transformSimple(body: string): string {\n return body.replace(SIMPLE_REACTIVE, (_full, indent: string, name: string, expr: string) => {\n return `${indent}let ${name} = $derived(${expr.trim()});`;\n });\n}\n\nexport function legacyReactiveToRunes(source: string): string {\n return source.replace(SCRIPT_BLOCK, (full, _attrs: string, body: string) => {\n // Blocks first so an outer `$: { ... }` containing nothing matchable\n // for the simple pass still gets wrapped. Order doesn't matter for the\n // patterns currently in the fleet but keeps the codemod robust to future\n // shapes.\n let next = transformBlocks(body);\n next = transformSimple(next);\n if (next === body) return full;\n return full.replace(body, next);\n });\n}\n","import { defaultSpawn, type SpawnFn, type SpawnResult } from \"../../audits/util/spawn.js\";\n\nexport type VerifyResult = {\n install: SpawnResult | { skipped: true };\n check: SpawnResult | { skipped: true };\n};\n\nexport async function verifyMigration(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<VerifyResult> {\n let install: VerifyResult[\"install\"];\n try {\n install = await spawn(\"pnpm\", [\"install\"], { cwd, timeoutMs: 10 * 60_000 });\n } catch {\n install = { skipped: true };\n }\n\n let check: VerifyResult[\"check\"];\n try {\n check = await spawn(\"pnpm\", [\"run\", \"check\"], { cwd, timeoutMs: 5 * 60_000 });\n } catch {\n check = { skipped: true };\n }\n\n return { install, check };\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type SummaryInput = {\n cwd: string;\n filesChangedByCodemods: number;\n svelteMigrateRan: boolean;\n tailwindUpgraded: boolean;\n};\n\nexport async function writeMigrationSummary(input: SummaryInput): Promise<string> {\n const lines = [\n `# Svelte 4 → 5 migration summary`,\n ``,\n `Generated by @reddoorla/maintenance.`,\n ``,\n `- svelte-migrate run: ${input.svelteMigrateRan ? \"yes\" : \"no\"}`,\n `- @tailwindcss/upgrade run: ${input.tailwindUpgraded ? \"yes\" : \"no\"}`,\n `- .svelte files touched by gotcha codemods: ${input.filesChangedByCodemods}`,\n ``,\n `Next steps:`,\n `- Run \\`pnpm run check\\` and resolve any remaining warnings.`,\n `- Spot-check rune migrations in components that use \\`reactive\\` statements.`,\n `- Verify Playwright a11y tests still pass.`,\n ];\n const content = lines.join(\"\\n\") + \"\\n\";\n const path = join(input.cwd, \"MIGRATION_SVELTE_5.md\");\n await writeFile(path, content, \"utf-8\");\n return path;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { planGotchaCodemods } from \"./svelte-5/step-gotchas.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\ntype Change = { rel: string; after: string };\n\n/**\n * Standalone codemod pass for sites already on Svelte 5.\n *\n * Applies the same gotcha codemods the full `svelte-4-to-5` migration runs,\n * but skips the version checks and migration steps — useful when Svelte 5\n * surfaces new strictness warnings post-upgrade (e.g. `state_referenced_locally`)\n * and the fleet needs a clean re-application.\n *\n * Plans changes in memory first; only creates the branch + writes + commits\n * when there is something to apply. Re-runs against a clean tree are noop.\n */\nexport async function svelteCodemods(site: Site): Promise<RecipeResult> {\n return withRecipe<Change[]>({\n name: \"svelte-codemods\",\n site,\n plan: async () => {\n const changes = await planGotchaCodemods(site.path);\n if (changes.length === 0) {\n return { kind: \"noop\", notes: \"no codemod targets matched\" };\n }\n return { kind: \"apply\", plan: changes };\n },\n apply: async (changes, { commit, cwd }) => {\n for (const c of changes) {\n await writeFile(join(cwd, c.rel), c.after, \"utf-8\");\n }\n await commit(`refactor(svelte5): apply codemods (${changes.length} files)`);\n return { kind: \"ok\" };\n },\n });\n}\n","import { rm, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { readPackageJson, writePackageJson, type PackageJsonLike } from \"../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { rewriteScriptsForPnpm } from \"./convert-to-pnpm/script-rewrites.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type ConvertToPnpmOptions = {\n spawn?: SpawnFn;\n /** Version string written into package.json's `packageManager` field.\n * Defaults to the version baked into this package's own pnpm setup. */\n pnpmVersion?: string;\n};\n\n/** Pinned default — matches the `packageManager` field of this package\n * (kept in sync with package.json). Sites can override per-recipe. */\nconst DEFAULT_PNPM_VERSION = \"10.33.1\";\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\ntype Plan = { hasNpmLock: boolean; hasYarnLock: boolean };\n\nexport async function convertToPnpm(\n site: Site,\n opts: ConvertToPnpmOptions = {},\n): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;\n\n const pnpmLockPath = join(site.path, \"pnpm-lock.yaml\");\n const npmLockPath = join(site.path, \"package-lock.json\");\n const yarnLockPath = join(site.path, \"yarn.lock\");\n\n return withRecipe<Plan>({\n name: \"convert-to-pnpm\",\n site,\n plan: async () => {\n if (await exists(pnpmLockPath)) {\n return { kind: \"noop\", notes: \"site already has pnpm-lock.yaml\" };\n }\n const hasNpmLock = await exists(npmLockPath);\n const hasYarnLock = await exists(yarnLockPath);\n if (!hasNpmLock && !hasYarnLock) {\n return {\n kind: \"noop\",\n notes: \"no convertible lockfile (package-lock.json or yarn.lock) at site root\",\n };\n }\n return { kind: \"apply\", plan: { hasNpmLock, hasYarnLock } };\n },\n apply: async ({ hasNpmLock, hasYarnLock }, { commit, cwd }) => {\n // Step 1: remove the npm/yarn lockfile(s).\n if (hasNpmLock) await rm(npmLockPath, { force: true });\n if (hasYarnLock) await rm(yarnLockPath, { force: true });\n const sourceLock = hasNpmLock ? \"package-lock.json\" : \"yarn.lock\";\n await commit(`chore(pnpm): remove ${sourceLock}`);\n\n // Step 2: pin packageManager + rewrite scripts (single commit — they\n // both touch package.json).\n const pkgPath = join(cwd, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n const next: PackageJsonLike = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };\n\n if (pkg.scripts && typeof pkg.scripts === \"object\") {\n const { scripts: rewritten, changedCount } = rewriteScriptsForPnpm(\n pkg.scripts as Record<string, string>,\n );\n if (changedCount > 0) {\n next.scripts = rewritten;\n }\n }\n\n await writePackageJson(pkgPath, next);\n await commit(\"chore(pnpm): pin packageManager + rewrite npm scripts\");\n\n // Step 3: remove any existing flat node_modules from a prior npm/yarn run\n // before pnpm installs. Sharing a node_modules across package managers\n // produces phantom-dep resolution issues (pnpm's nested layout disagrees\n // with what's already on disk). node_modules is gitignored on every\n // reddoor site so this doesn't dirty the tree.\n await rm(join(cwd, \"node_modules\"), { recursive: true, force: true });\n\n // Step 4: run pnpm install to materialize pnpm-lock.yaml.\n const installResult = await spawn(\"pnpm\", [\"install\"], { cwd, streaming: true });\n if (installResult.code !== 0) {\n return { kind: \"failed\", notes: `pnpm install failed (exit ${installResult.code})` };\n }\n\n await commit(\"chore(pnpm): add pnpm-lock.yaml\");\n return { kind: \"ok\" };\n },\n });\n}\n","/**\n * Rewrite a single package.json script value to use pnpm equivalents\n * where the substitution is safe. Conservative on purpose: we only touch\n * patterns whose semantics are identical under pnpm.\n *\n * - `npm run <token>` → `pnpm run <token>` (identical behavior)\n * - `npx <token>` → `pnpm dlx <token>` (identical behavior in pnpm 7+)\n *\n * Intentionally NOT rewritten:\n * - `npm install`, `npm install <pkg>`, `npm install --save-dev <pkg>` —\n * subtle flag mapping (e.g. `--save-dev` → `-D`) and edge cases like\n * `--save-exact` / `--save-optional`. Better to leave for an operator\n * eyeball than to silently mis-translate.\n * - Hyphenated identifiers like `npm-check-updates` (word-boundary protected).\n * - `concurrently \"npm:scriptName\"` shorthand syntax — it isn't actually\n * running npm; it's a concurrently-specific script reference.\n */\nexport function rewriteScriptForPnpm(script: string): string {\n let out = script;\n // `npm run <name>` → `pnpm run <name>`. \\b before npm prevents\n // matching inside hyphenated identifiers. Lookahead `(?=\\s)` after run\n // ensures we don't match `runner`.\n out = out.replace(/\\bnpm run(?=\\s)/g, \"pnpm run\");\n // `npx ` → `pnpm dlx `. \\b before npx prevents matching `npx-something`.\n out = out.replace(/\\bnpx(?=\\s)/g, \"pnpm dlx\");\n return out;\n}\n\n/**\n * Rewrite every entry in a package.json `scripts` map. Returns the new\n * map alongside a count of scripts that were actually changed.\n */\nexport function rewriteScriptsForPnpm(scripts: Record<string, string>): {\n scripts: Record<string, string>;\n changedCount: number;\n} {\n const next: Record<string, string> = {};\n let changedCount = 0;\n for (const [name, value] of Object.entries(scripts)) {\n const rewritten = rewriteScriptForPnpm(value);\n next[name] = rewritten;\n if (rewritten !== value) changedCount++;\n }\n return { scripts: next, changedCount };\n}\n","import { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { readPackageJson, writePackageJson, bumpDep, type PackageJsonLike } from \"../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { selfCaretRange } from \"../util/self-version.js\";\nimport { baselineVersions } from \"../configs/baseline-versions.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type OnboardAudit = \"lighthouse\" | \"a11y\";\n\nexport type OnboardOptions = {\n spawn?: SpawnFn;\n /** Which audit-related deps to ensure. Defaults to all known audits. */\n audits?: OnboardAudit[];\n /** Version range to pin for @reddoorla/maintenance. Defaults to a caret\n * range against this package's own version at runtime — no manual\n * syncing required at each minor bump. */\n packageVersion?: string;\n};\n\nconst PACKAGE_NAME = \"@reddoorla/maintenance\";\n\nconst AUDIT_DEP_NAMES: Record<OnboardAudit, string[]> = {\n lighthouse: [\"@lhci/cli\"],\n a11y: [\"@playwright/test\", \"@axe-core/playwright\"],\n};\n\n/** Framework deps onboard ensures for every site, independent of which audits\n * are requested. The sync-configs svelte.config.js template does\n * `import adapter from \"@sveltejs/adapter-netlify\"`, so a site that lacks the\n * adapter declared can't build once configs are synced — onboard closes that\n * gap at the same time it adds the maintenance package. */\nconst FRAMEWORK_DEP_NAMES = [\"@sveltejs/adapter-netlify\"];\n\n/** Resolve framework dep versions from baselineVersions at module load so they\n * can't drift from the single source of truth — mirrors AUDIT_DEPS. Throws at\n * import time if a name is missing there (a programming error). */\nexport const FRAMEWORK_DEPS: Array<{ name: string; version: string }> = FRAMEWORK_DEP_NAMES.map(\n (name) => {\n const version = baselineVersions[name];\n if (!version) {\n throw new Error(\n `baseline-versions is missing framework dep \"${name}\" — add it to src/configs/baseline-versions.ts`,\n );\n }\n return { name, version };\n },\n);\n\n/** Look up each audit dep's version in baselineVersions at module load so\n * AUDIT_DEPS can't drift from the single source of truth across releases.\n * Throws at import time if baseline-versions is missing an audit dep —\n * which would be a programming error (every audit dep name above must\n * appear in baselineVersions). */\nexport const AUDIT_DEPS: Record<\n OnboardAudit,\n Array<{ name: string; version: string }>\n> = Object.fromEntries(\n (Object.entries(AUDIT_DEP_NAMES) as Array<[OnboardAudit, string[]]>).map(([audit, names]) => [\n audit,\n names.map((name) => {\n const version = baselineVersions[name];\n if (!version) {\n throw new Error(\n `baseline-versions is missing audit dep \"${name}\" — add it to src/configs/baseline-versions.ts`,\n );\n }\n return { name, version };\n }),\n ]),\n) as Record<OnboardAudit, Array<{ name: string; version: string }>>;\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isDeclared(pkg: PackageJsonLike, name: string): boolean {\n return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);\n}\n\ntype Plan = {\n pkg: PackageJsonLike;\n toAdd: Array<{ name: string; version: string }>;\n};\n\nexport async function onboard(site: Site, opts: OnboardOptions = {}): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n const audits = opts.audits ?? ([\"lighthouse\", \"a11y\"] as OnboardAudit[]);\n const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);\n\n return withRecipe<Plan>({\n name: \"onboard\",\n site,\n plan: async () => {\n // Pre-flight: site must already be on pnpm. We don't auto-convert here;\n // that's the convert-to-pnpm recipe's job, and combining them would\n // hide the package-manager transition inside a bigger PR.\n if (!(await exists(join(site.path, \"pnpm-lock.yaml\")))) {\n return {\n kind: \"failed\",\n notes: \"no pnpm-lock.yaml at site root — run convert-to-pnpm first\",\n };\n }\n\n const pkgPath = join(site.path, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n\n // Determine what's missing. Anything already declared (even at a wildly\n // different version) is left alone — onboard never downgrades.\n const toAdd: Array<{ name: string; version: string }> = [];\n if (!isDeclared(pkg, PACKAGE_NAME)) {\n toAdd.push({ name: PACKAGE_NAME, version: packageVersion });\n }\n for (const dep of FRAMEWORK_DEPS) {\n if (!isDeclared(pkg, dep.name)) toAdd.push(dep);\n }\n for (const audit of audits) {\n for (const dep of AUDIT_DEPS[audit]) {\n if (!isDeclared(pkg, dep.name)) toAdd.push(dep);\n }\n }\n\n if (toAdd.length === 0) {\n return {\n kind: \"noop\",\n notes: `site already has ${PACKAGE_NAME}, framework deps, and audit deps (${audits.join(\"+\")})`,\n };\n }\n return { kind: \"apply\", plan: { pkg, toAdd } };\n },\n apply: async ({ pkg, toAdd }, { commit, cwd }) => {\n const pkgPath = join(cwd, \"package.json\");\n let next: PackageJsonLike = pkg;\n for (const dep of toAdd) {\n next = bumpDep(next, dep.name, dep.version);\n }\n await writePackageJson(pkgPath, next);\n\n // Run pnpm install so the lockfile reflects the new deps before we commit.\n // Stream output — install on a real site can take 30s+.\n const installResult = await spawn(\"pnpm\", [\"install\"], { cwd, streaming: true });\n if (installResult.code !== 0) {\n return {\n kind: \"failed\",\n notes: `pnpm install failed (exit ${installResult.code})`,\n };\n }\n\n await commit(`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`);\n return {\n kind: \"ok\",\n notes: `Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(\", \")}`,\n };\n },\n });\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Read this package's own version at runtime so recipe defaults don't go\n * stale at each minor bump.\n *\n * Pass `import.meta.url` from the calling file. Walks UP from the caller\n * looking for the first `package.json` whose `name` matches this package\n * (`@reddoorla/maintenance`). The older \"two levels up\" shortcut held for\n * `src/X/Y.ts` and `dist/cli/bin.js` (both happen to be 2 dirs deep) but\n * broke for `dist/index.js` (only 1 dir deep) — silently returned \"0.0.0\"\n * and pinned consumers to `^0.0.0`. Same bug class as the 0.10.1 bundled-\n * assets ENOENT (2026-05-27). Walk-up is robust regardless of bundling\n * layout.\n *\n * Returns \"0.0.0\" if no matching package.json is reachable (defensive\n * fallback; callers should treat that as a signal to either override\n * explicitly or fail loudly).\n */\nexport function selfPackageVersion(callerImportMetaUrl: string): string {\n try {\n let dir = dirname(fileURLToPath(callerImportMetaUrl));\n while (true) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n const raw = readFileSync(candidate, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n // Only accept OUR package.json — keep walking past random ancestor\n // package.jsons (the consumer's own, anything in node_modules) that\n // happen to sit above the bundle.\n if (pkg.name === \"@reddoorla/maintenance\") {\n return pkg.version ?? \"0.0.0\";\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return \"0.0.0\";\n dir = parent;\n }\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Caret-pinned range against this package's own version: e.g. \"^0.6.2\". */\nexport function selfCaretRange(callerImportMetaUrl: string): string {\n return `^${selfPackageVersion(callerImportMetaUrl)}`;\n}\n","import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { A11Y_FIXTURES_PAGE_RELATIVE, A11Y_FIXTURES_PAGE_TEMPLATE } from \"./template.js\";\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route\n * doesn't already exist. The hardcoded URL in `src/configs/lighthouse.ts` +\n * `src/configs/playwright-a11y.ts` targets this path — newly-onboarded sites\n * need the route to exist for either audit to pass. Operator edits to an\n * existing page are never clobbered (noop on existing file).\n */\nexport async function a11yFixturesPage(site: Site): Promise<RecipeResult> {\n const target = join(site.path, A11Y_FIXTURES_PAGE_RELATIVE);\n return withRecipe<{ target: string }>({\n name: \"a11y-fixtures-page\",\n site,\n plan: async () => {\n if (await fileExists(target)) {\n return { kind: \"noop\", notes: `${A11Y_FIXTURES_PAGE_RELATIVE} already exists` };\n }\n return { kind: \"apply\", plan: { target } };\n },\n apply: async (planned, { commit }) => {\n await mkdir(dirname(planned.target), { recursive: true });\n await writeFile(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, \"utf-8\");\n await commit(\"feat: add /dev/a11y-fixtures starter route\");\n return { kind: \"ok\" };\n },\n });\n}\n","/** Relative path inside a site where the a11y fixtures route lives. The\n * hardcoded URL in `src/configs/lighthouse.ts` + `src/configs/playwright-a11y.ts`\n * is `/dev/a11y-fixtures`, so a SvelteKit `+page.svelte` here resolves. */\nexport const A11Y_FIXTURES_PAGE_RELATIVE = \"src/routes/dev/a11y-fixtures/+page.svelte\";\n\n/** Stub `+page.svelte` for newly-onboarded sites. Generic on purpose —\n * landmarks, heading hierarchy, and a relative link cover the axe-core +\n * lhci defaults without committing the operator to any specific fixture\n * shape. Replace with site-specific patterns over time. */\nexport const A11Y_FIXTURES_PAGE_TEMPLATE = `<svelte:head>\n <title>a11y fixtures — Reddoor</title>\n <meta\n name=\"description\"\n content=\"Reddoor accessibility fixtures — semantic landmarks, heading hierarchy, and a stable target for @lhci/cli and Playwright + axe-core coverage. Not linked from the public site.\"\n />\n</svelte:head>\n\n<main>\n <header>\n <h1>Accessibility fixtures</h1>\n <p>\n This page exists so <code>@lhci/cli</code> and Playwright + axe-core have a\n stable target with predictable a11y characteristics. It is not linked from\n the public site.\n </p>\n </header>\n\n <section aria-labelledby=\"landmarks-heading\">\n <h2 id=\"landmarks-heading\">Landmarks</h2>\n <p>\n A single <code>main</code> wraps the page; sections each declare\n <code>aria-labelledby</code> matched to their heading id so screen readers\n and axe both see a clean outline.\n </p>\n </section>\n\n <section aria-labelledby=\"links-heading\">\n <h2 id=\"links-heading\">Links</h2>\n <p>\n <a href=\"/\">Back to home</a> — relative link with descriptive visible text,\n so no <code>aria-label</code> override is needed.\n </p>\n </section>\n</main>\n`;\n","import type { AuditResult, RecipeResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { convertToPnpm } from \"./convert-to-pnpm.js\";\nimport { onboard } from \"./onboard.js\";\nimport { syncConfigs } from \"./sync-configs.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport { runAudits } from \"../audits/index.js\";\n\nexport type InitStepResult =\n | { kind: \"recipe\"; result: RecipeResult }\n | { kind: \"audit\"; results: AuditResult[] }\n | { kind: \"error\"; message: string };\n\nexport type InitStep = {\n name: string;\n run: (site: Site) => Promise<InitStepResult>;\n};\n\nexport type InitResult = {\n site: string;\n steps: Array<{ name: string; result: InitStepResult }>;\n /** True if every step ran; false if an `error` or `failed` recipe result\n * short-circuited the chain. `noop` recipes do not break completeness. */\n complete: boolean;\n};\n\nexport type InitOptions = {\n /** Override the default step list. Tests inject mocked steps; production\n * code relies on the default. */\n steps?: InitStep[];\n};\n\nfunction recipeStep(name: string, fn: (site: Site) => Promise<RecipeResult>): InitStep {\n return {\n name,\n run: async (site) => ({ kind: \"recipe\", result: await fn(site) }),\n };\n}\n\n/** convert-to-pnpm → onboard → sync-configs → svelte-codemods →\n * a11y-fixtures-page → audit. Order is deliberate — every step depends on\n * the prior one's output (pnpm before onboard's installs, onboard's deps\n * before sync-configs writes lighthouserc, fixtures page before audit\n * actually has a route to hit). */\nexport const DEFAULT_INIT_STEPS: InitStep[] = [\n recipeStep(\"convert-to-pnpm\", convertToPnpm),\n recipeStep(\"onboard\", onboard),\n recipeStep(\"sync-configs\", syncConfigs),\n recipeStep(\"svelte-codemods\", svelteCodemods),\n recipeStep(\"a11y-fixtures-page\", a11yFixturesPage),\n {\n name: \"audit\",\n run: async (site) => ({ kind: \"audit\", results: await runAudits(site) }),\n },\n];\n\n/**\n * One-shot guided onboarding. Runs the default step sequence against a\n * site, collecting per-step results into an InitResult. Each underlying\n * recipe still creates its own branch — init is a thin orchestrator, not\n * a branch-collapser; the operator ends up with one stack of branches per\n * mutated step (recipes that noop don't branch).\n *\n * Stops the chain on the first uncaught error or `failed` recipe result.\n * `noop` results are expected (e.g. running init twice) and continue the\n * chain. The final audit pass runs if no prior step errored.\n */\nexport async function init(site: Site, opts: InitOptions = {}): Promise<InitResult> {\n const steps = opts.steps ?? DEFAULT_INIT_STEPS;\n const out: Array<{ name: string; result: InitStepResult }> = [];\n\n for (const step of steps) {\n let result: InitStepResult;\n try {\n result = await step.run(site);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n out.push({ name: step.name, result: { kind: \"error\", message } });\n return { site: siteLabel(site), steps: out, complete: false };\n }\n out.push({ name: step.name, result });\n if (result.kind === \"recipe\" && result.result.status === \"failed\") {\n return { site: siteLabel(site), steps: out, complete: false };\n }\n }\n\n return { site: siteLabel(site), steps: out, complete: true };\n}\n","import type { RecipeName } from \"../types.js\";\nimport { syncConfigs, type SyncConfigsOptions } from \"./sync-configs.js\";\nimport { bumpDeps, type BumpDepsOptions } from \"./bump-deps.js\";\nimport { upgradeSvelte4to5, type UpgradeSvelte4to5Options } from \"./svelte-5/index.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { convertToPnpm, type ConvertToPnpmOptions } from \"./convert-to-pnpm.js\";\nimport { onboard, type OnboardOptions, type OnboardAudit } from \"./onboard.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport {\n init,\n DEFAULT_INIT_STEPS,\n type InitOptions,\n type InitResult,\n type InitStep,\n type InitStepResult,\n} from \"./init.js\";\n\nexport {\n syncConfigs,\n bumpDeps,\n upgradeSvelte4to5,\n svelteCodemods,\n convertToPnpm,\n onboard,\n a11yFixturesPage,\n init,\n DEFAULT_INIT_STEPS,\n};\nexport type {\n SyncConfigsOptions,\n BumpDepsOptions,\n UpgradeSvelte4to5Options,\n ConvertToPnpmOptions,\n OnboardOptions,\n OnboardAudit,\n InitOptions,\n InitResult,\n InitStep,\n InitStepResult,\n};\n\nexport const ALL_RECIPE_NAMES: RecipeName[] = [\n \"sync-configs\",\n \"bump-deps\",\n \"svelte-4-to-5\",\n \"svelte-codemods\",\n \"convert-to-pnpm\",\n \"onboard\",\n \"a11y-fixtures-page\",\n \"self-updating\",\n \"init\",\n];\n\nexport function isRecipeName(value: string): value is RecipeName {\n return (ALL_RECIPE_NAMES as string[]).includes(value);\n}\n","import { basename } from \"node:path\";\nimport type { InventoryProvider, Site } from \"../types.js\";\n\nexport type LocalPathOptions = {\n name?: string;\n};\n\nexport function localPath(path: string, opts: LocalPathOptions = {}): InventoryProvider {\n const site: Site = { path, name: opts.name ?? basename(path) };\n return async () => [site];\n}\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute } from \"node:path\";\nimport type { InventoryProvider, Site } from \"../types.js\";\n\nfunction validate(raw: unknown): Site[] {\n if (!Array.isArray(raw)) {\n throw new Error(\"inventory JSON must be an array of sites\");\n }\n return raw.map((entry, i) => {\n if (!entry || typeof entry !== \"object\") {\n throw new Error(`inventory entry ${i} is not an object`);\n }\n const e = entry as Record<string, unknown>;\n if (typeof e.path !== \"string\" || e.path.length === 0) {\n throw new Error(`inventory entry ${i} is missing required field: path`);\n }\n if (!isAbsolute(e.path)) {\n throw new Error(\n `inventory entry ${i}: path must be absolute (got \"${e.path}\"). ` +\n `Relative paths are rejected so cwd at invocation can't change which site is targeted.`,\n );\n }\n const site: Site = { path: e.path };\n if (typeof e.name === \"string\") site.name = e.name;\n if (typeof e.repoUrl === \"string\") site.repoUrl = e.repoUrl;\n if (typeof e.meta === \"object\" && e.meta !== null) {\n site.meta = e.meta as Record<string, unknown>;\n }\n return site;\n });\n}\n\nexport function fromJsonFile(path: string): InventoryProvider {\n return async () => {\n const raw = JSON.parse(await readFile(path, \"utf-8\")) as unknown;\n return validate(raw);\n };\n}\n","import type { FieldSet } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport type { LighthouseScores } from \"../types.js\";\n\nexport const WEBSITES_TABLE = \"Websites\";\n\nexport type Frequency = \"None\" | \"Monthly\" | \"Quarterly\" | \"Yearly\";\n\nexport type Status =\n | \"in development\"\n | \"launch period\"\n | \"maintenance\"\n | \"hosting\"\n | \"probably not our problem\"\n | \"deprecated\";\n\nexport type WebsiteRow = {\n id: string;\n name: string;\n url: string;\n status: Status | null;\n pointOfContact: string | null;\n maintenanceFreq: Frequency;\n testingFreq: Frequency;\n /** Last manually-recorded maintenance day (used as fallback when no Reports row exists). */\n maintenanceDay: string | null;\n testingDay: string | null;\n ga4PropertyId: string | null;\n /** Operator-supplied query for the Google search-presence check (e.g. the business name).\n * Null = no query set → the check is skipped for this site. */\n searchQuery: string | null;\n /** Explicit Search Console property for this site (`sc-domain:...` or `https://.../`).\n * Null = auto-resolve from the SA's visible properties by host. */\n searchConsoleProperty: string | null;\n /** GitHub repo identity as `owner/repo`. Null = no git wiring → self-update ops skip\n * (or, for local runs, fall back to the checkout's origin remote). */\n gitRepo: string | null;\n reportRecipientsTo: string | null;\n reportRecipientsCc: string | null;\n /** First attachment in the Header image field (Airtable's signed URL — fetch before expiry). */\n headerImage: { url: string; filename: string; type: string } | null;\n /** Lighthouse \"current state\" snapshot, kept fresh by `audit lighthouse --write-airtable`. */\n pScore: number | null;\n rScore: number | null;\n bpScore: number | null;\n seoScore: number | null;\n /** ISO timestamp set by `audit lighthouse --write-airtable` when scores were last refreshed. */\n lastLighthouseAuditAt: string | null;\n /** Last-known counts from non-lighthouse audits, written by\n * `audit --write-airtable`. `null` = never audited (or this audit\n * type was skipped on the last run). 0 = audited, clean. */\n a11yViolations: number | null;\n depsDrifted: number | null;\n depsMajorBehind: number | null;\n securityVulnsCritical: number | null;\n securityVulnsHigh: number | null;\n securityVulnsModerate: number | null;\n securityVulnsLow: number | null;\n /** Shared-link gate for the per-site dashboard at /s/<slug>?t=<token>.\n * Operator generates and pastes into the \"Dashboard Token\" Airtable field;\n * rotated by replacing the value. `null` means the site has no dashboard\n * link yet — the function returns 403 with a clear setup message. */\n dashboardToken: string | null;\n};\n\nexport function siteSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nexport function mapRow(rec: { id: string; fields: Record<string, unknown> }): WebsiteRow {\n const f = rec.fields;\n const attachments =\n (f[\"Header image\"] as Array<{ url: string; filename: string; type: string }> | undefined) ?? [];\n const header = attachments[0] ?? null;\n return {\n id: rec.id,\n name: String(f[\"Name\"] ?? \"\"),\n url: String(f[\"url\"] ?? \"\"),\n status: (f[\"Status\"] as Status | undefined) ?? null,\n pointOfContact: (f[\"point of contact\"] as string | undefined) ?? null,\n maintenanceFreq: ((f[\"maintenence freq\"] as string | undefined) ?? \"None\") as Frequency,\n testingFreq: ((f[\"testing freq\"] as string | undefined) ?? \"None\") as Frequency,\n maintenanceDay: (f[\"maintenance day\"] as string | undefined) ?? null,\n testingDay: (f[\"testing day\"] as string | undefined) ?? null,\n ga4PropertyId: (f[\"GA4 property ID\"] as string | undefined) ?? null,\n searchQuery: (f[\"Search query\"] as string | undefined) ?? null,\n searchConsoleProperty: (f[\"Search Console property\"] as string | undefined) ?? null,\n gitRepo: (f[\"Git repo\"] as string | undefined) ?? null,\n reportRecipientsTo: (f[\"Report recipients (To)\"] as string | undefined) ?? null,\n reportRecipientsCc: (f[\"Report recipients (CC)\"] as string | undefined) ?? null,\n headerImage: header,\n pScore: (f[\"pScore\"] as number | undefined) ?? null,\n rScore: (f[\"rScore\"] as number | undefined) ?? null,\n bpScore: (f[\"bpScore\"] as number | undefined) ?? null,\n seoScore: (f[\"seoScore\"] as number | undefined) ?? null,\n lastLighthouseAuditAt: (f[\"Last lighthouse audit at\"] as string | undefined) ?? null,\n a11yViolations: (f[\"A11y Violations\"] as number | undefined) ?? null,\n depsDrifted: (f[\"Deps Drifted\"] as number | undefined) ?? null,\n depsMajorBehind: (f[\"Deps Major Behind\"] as number | undefined) ?? null,\n securityVulnsCritical: (f[\"Security Vulns Critical\"] as number | undefined) ?? null,\n securityVulnsHigh: (f[\"Security Vulns High\"] as number | undefined) ?? null,\n securityVulnsModerate: (f[\"Security Vulns Moderate\"] as number | undefined) ?? null,\n securityVulnsLow: (f[\"Security Vulns Low\"] as number | undefined) ?? null,\n dashboardToken: (() => {\n const raw = f[\"Dashboard Token\"];\n if (typeof raw !== \"string\") return null;\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n })(),\n };\n}\n\nexport async function listWebsites(base: AirtableBase): Promise<WebsiteRow[]> {\n const out: WebsiteRow[] = [];\n await base(WEBSITES_TABLE)\n .select({ pageSize: 100 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\nexport async function getWebsiteBySlug(\n base: AirtableBase,\n slug: string,\n): Promise<WebsiteRow | null> {\n const all = await listWebsites(base);\n return all.find((w) => siteSlug(w.name) === slug) ?? null;\n}\n\n/**\n * Write the four Lighthouse scores + a refreshed-at timestamp onto a Websites row.\n * Called by `audit lighthouse --write-airtable` after a successful audit run, so\n * the operator never has to paste numbers manually before drafting a report.\n */\nexport async function updateScores(\n base: AirtableBase,\n recordId: string,\n scores: LighthouseScores,\n): Promise<void> {\n const fields: FieldSet = {\n pScore: scores.performance,\n rScore: scores.accessibility,\n bpScore: scores.bestPractices,\n seoScore: scores.seo,\n \"Last lighthouse audit at\": new Date().toISOString(),\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist a11y violation count. */\nexport async function updateA11yCounts(\n base: AirtableBase,\n recordId: string,\n counts: { violations: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"A11y Violations\": counts.violations,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist deps drift counts. */\nexport async function updateDepsCounts(\n base: AirtableBase,\n recordId: string,\n counts: { drifted: number; majorBehind: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"Deps Drifted\": counts.drifted,\n \"Deps Major Behind\": counts.majorBehind,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist security vulnerability counts by severity. */\nexport async function updateSecurityCounts(\n base: AirtableBase,\n recordId: string,\n counts: { critical: number; high: number; moderate: number; low: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"Security Vulns Critical\": counts.critical,\n \"Security Vulns High\": counts.high,\n \"Security Vulns Moderate\": counts.moderate,\n \"Security Vulns Low\": counts.low,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n","import type { Site, InventoryProvider } from \"../types.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { listWebsites, siteSlug } from \"../reports/airtable/websites.js\";\n\nexport type AirtableInventoryOptions = {\n /**\n * Local workdir to compute each site's path as `{workdir}/{slug}`.\n * Defaults to REDDOOR_FLEET_WORKDIR env var if not provided.\n * Airtable doesn't store local checkout paths, so this is required.\n */\n workdir?: string;\n};\n\n/**\n * Read sites from the Airtable Websites table as an InventoryProvider.\n * Each row becomes one Site; `path` is computed as `{workdir}/{slug}`.\n * Sites where BOTH maintenance freq AND testing freq are \"None\" are excluded\n * (they're inactive — no scheduled audits or reports).\n *\n * Note: `repoUrl` is set to the production URL (Websites.url). For sites\n * cloned via `--workdir` semantics this is wrong — the convention should be\n * tightened (e.g. add a `repo` field to Websites) when fleet-clone-from-\n * airtable becomes a real flow. For local audits where the site is already\n * checked out at `path`, the `repoUrl` is unused.\n */\nexport function fromAirtableBase(\n base: AirtableBase,\n opts: AirtableInventoryOptions = {},\n): InventoryProvider {\n return async (): Promise<Site[]> => {\n const workdir = opts.workdir ?? process.env.REDDOOR_FLEET_WORKDIR;\n if (!workdir) {\n throw new Error(\n \"fromAirtableBase requires `workdir` option or REDDOOR_FLEET_WORKDIR env (sites need a local path)\",\n );\n }\n const websites = await listWebsites(base);\n return websites\n .filter((w) => w.maintenanceFreq !== \"None\" || w.testingFreq !== \"None\")\n .map((w) => {\n const slug = siteSlug(w.name);\n const site: Site = {\n path: `${workdir}/${slug}`,\n name: slug,\n meta: { airtableRowId: w.id, displayName: w.name },\n };\n if (w.url) site.repoUrl = w.url;\n if (w.gitRepo) site.gitRepo = w.gitRepo;\n return site;\n });\n };\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { ReportType, LighthouseScores } from \"./types.js\";\nimport { renderReportHtml } from \"./render.js\";\nimport { siteSlug } from \"./airtable/websites.js\";\nimport type { WebsiteRow } from \"./airtable/websites.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport { createDraft, setDraftReady, listReportsForSite } from \"./airtable/reports.js\";\nimport { uploadAttachment } from \"./airtable/attachments.js\";\nimport type { AirtableBase } from \"./airtable/client.js\";\nimport { readGaConfig } from \"./ga/config.js\";\nimport { fetchPeriodUsers } from \"./ga/client.js\";\nimport { fetchSearchPresence } from \"./search/client.js\";\nimport type { SearchPresence } from \"./search/client.js\";\n\nexport type DraftOptions = {\n /** Where to write the local preview HTML when `previewOnly`. Defaults to `reports/<slug>/draft.html`. */\n previewPath?: string;\n /** If true: render locally only, never touch Airtable. */\n previewOnly?: boolean;\n};\n\nexport type DraftResult = {\n /** null when previewOnly. */\n reportRow: ReportRow | null;\n /** Path to the local preview file (only set when previewOnly). */\n htmlPath: string | null;\n /** Always present — the rendered HTML string. */\n html: string;\n};\n\nfunction scoresFromWebsite(siteRow: WebsiteRow): LighthouseScores {\n const { pScore, rScore, bpScore, seoScore } = siteRow;\n if (pScore === null || rScore === null || bpScore === null || seoScore === null) {\n throw new Error(\n `Site '${siteRow.name}' is missing one or more Lighthouse scores on the Websites row (pScore, rScore, bpScore, seoScore). ` +\n `Run 'reddoor-maint audit lighthouse' from the site's checkout and paste the four numbers into Airtable, then retry.`,\n );\n }\n return { performance: pScore, accessibility: rScore, bestPractices: bpScore, seo: seoScore };\n}\n\nfunction daysAgo(today: Date, n: number): Date {\n // UTC accessors to stay TZ-consistent with `due.ts` (and avoid landing\n // Airtable's `Period start` on a different calendar day than the operator\n // expects on late-night runs near a month boundary). See morning brief\n // 2026-05-29 (M1) for context.\n const out = new Date(today);\n out.setUTCDate(out.getUTCDate() - n);\n return out;\n}\n\nexport async function draftReportForSite(\n base: AirtableBase | null,\n siteRow: WebsiteRow,\n reportType: ReportType,\n options: DraftOptions = {},\n): Promise<DraftResult> {\n const scores = scoresFromWebsite(siteRow);\n\n const today = new Date();\n const slug = siteSlug(siteRow.name);\n\n const periodStart =\n base !== null ? await derivePeriodStart(base, siteRow, reportType, today) : daysAgo(today, 30);\n\n const periodEnd = today;\n const completedOn = today;\n const lastTestedDate =\n reportType === \"Maintenance\" && siteRow.testingDay ? new Date(siteRow.testingDay) : null;\n\n // GA enrichment (real path only). Soft-fail: any GA problem leaves the numbers null so\n // the draft still proceeds (operator fills them manually) — GA is an enhancement, not a\n // gate. Rendered with the fetched numbers so the review HTML matches the Airtable fields.\n const gaUsers = base !== null ? await fetchGaUsers(siteRow, periodStart, periodEnd) : null;\n const search = base !== null ? await fetchSearch(siteRow, periodStart, periodEnd) : null;\n\n const cidName = `${slug}-header`;\n const { html } = await renderReportHtml({\n siteName: siteRow.name,\n siteUrl: siteRow.url,\n reportType,\n completedOn,\n lighthouse: scores,\n gaUsersCurrent: gaUsers?.current,\n gaUsersPrevious: gaUsers?.previous,\n searchPosition: search?.foundOnPage1 ? (search.position ?? undefined) : undefined,\n lastTestedDate,\n commentary: null,\n headerImageCid: cidName,\n });\n\n if (options.previewOnly) {\n const path = options.previewPath ?? `reports/${slug}/draft.html`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, html, \"utf-8\");\n return { reportRow: null, htmlPath: path, html };\n }\n\n if (base === null) throw new Error(\"base required when previewOnly=false\");\n\n const reportId = `${siteRow.name} — ${reportType} — ${periodEnd.toISOString().slice(0, 10)}`;\n const created = await createDraft(base, {\n reportId,\n siteId: siteRow.id,\n reportType,\n periodStart,\n periodEnd,\n completedOn,\n lighthouse: scores,\n lastTestedDate,\n ...(gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {}),\n ...(search ? { searchFoundPage1: search.foundOnPage1 } : {}),\n ...(search?.foundOnPage1 && search.position !== null\n ? { searchPosition: search.position }\n : {}),\n });\n\n const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;\n await uploadAttachment(created.id, \"Rendered HTML\", html, htmlFilename, \"text/html\");\n await setDraftReady(base, created.id, true);\n\n return { reportRow: created, htmlPath: null, html };\n}\n\n/**\n * Fetch GA \"Users\" for the period, soft-failing to null. Returns null (no enrichment) when\n * GA isn't configured (`GA_SUBJECT` unset), the site has no GA4 property ID, or the GA API\n * errors — logging a one-line warning in the error case. Never throws, so a GA problem can\n * never block a draft; the operator can always enter the numbers by hand.\n */\nasync function fetchGaUsers(\n siteRow: WebsiteRow,\n periodStart: Date,\n periodEnd: Date,\n): Promise<{ current: number; previous: number } | null> {\n const cfg = readGaConfig();\n if (!cfg || !siteRow.ga4PropertyId) return null;\n try {\n return await fetchPeriodUsers(\n { propertyId: siteRow.ga4PropertyId, subject: cfg.subject, keyPath: cfg.keyPath },\n periodStart,\n periodEnd,\n );\n } catch (e) {\n console.warn(`⚠ GA skipped for ${siteRow.name}: ${(e as Error).message}`);\n return null;\n }\n}\n\n/**\n * Fetch the site's Google search presence for the period, soft-failing to null. Returns null\n * when GA/SA isn't configured (`readGaConfig()` null — search shares the SA credentials), the\n * site has no `searchQuery`, or the Search Console API errors (logging a one-line warning).\n * Never throws, so a search problem can never block a draft.\n */\nasync function fetchSearch(\n siteRow: WebsiteRow,\n periodStart: Date,\n periodEnd: Date,\n): Promise<SearchPresence | null> {\n const cfg = readGaConfig();\n if (!cfg || !siteRow.searchQuery) return null;\n try {\n return await fetchSearchPresence(\n {\n keyPath: cfg.keyPath,\n subject: cfg.subject,\n property: siteRow.searchConsoleProperty ?? undefined,\n host: siteRow.url,\n query: siteRow.searchQuery,\n },\n periodStart,\n periodEnd,\n );\n } catch (e) {\n console.warn(`⚠ Search presence skipped for ${siteRow.name}: ${(e as Error).message}`);\n return null;\n }\n}\n\nasync function derivePeriodStart(\n base: AirtableBase,\n siteRow: WebsiteRow,\n reportType: ReportType,\n today: Date,\n): Promise<Date> {\n const prior = await listReportsForSite(base, siteRow.id);\n const sameType = prior\n .filter((r) => r.reportType === reportType && r.periodEnd)\n .map((r) => r.periodEnd!)\n .sort();\n const latest = sameType[sameType.length - 1];\n return latest ? new Date(latest) : daysAgo(today, 30);\n}\n","import mjml2html from \"mjml\";\nimport type { ReportData } from \"./types.js\";\nimport { buildMjml } from \"./maintenance-email/template.js\";\n\nexport type RenderResult = {\n html: string;\n warnings: Array<{ line: number; message: string }>;\n};\n\nexport async function renderReportHtml(data: ReportData): Promise<RenderResult> {\n const mjml = buildMjml(data);\n const out = await mjml2html(mjml, { validationLevel: \"strict\" });\n return { html: out.html, warnings: out.errors ?? [] };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const CHECK_CID = \"rd-check-png\";\nexport const BLURRED_CID = \"rd-blurred-tests-jpg\";\n\nexport type BundledImage = {\n bytes: Uint8Array;\n contentType: string;\n cid: string;\n filename: string;\n};\n\n// Walk up from the current module's URL looking for the assets dir in either\n// the dev layout (src/reports/maintenance-email/assets/) or the published\n// layout (dist/reports/maintenance-email/assets/). REQUIRED because tsup\n// inlines this module into dist/cli/bin.js — so `import.meta.url`-based\n// sibling resolution looks in dist/cli/ for the PNGs and fails with ENOENT.\n// Regression that shipped in 0.10.0–0.10.1; tests passed in dev because\n// vitest evaluates the source file where import.meta.url is already correct.\nlet cachedAssetsDir: string | null = null;\nfunction resolveAssetsDir(): string {\n if (cachedAssetsDir) return cachedAssetsDir;\n let dir = dirname(fileURLToPath(import.meta.url));\n while (true) {\n // Source layout preferred — single source of truth in the workspace\n // and the only one present in dev/test environments.\n const srcCandidate = join(dir, \"src\", \"reports\", \"maintenance-email\", \"assets\", \"check.png\");\n if (existsSync(srcCandidate)) {\n cachedAssetsDir = dirname(srcCandidate);\n return cachedAssetsDir;\n }\n // Published layout — only `dist/` ships per package.json#files, so\n // consumers fall through to here.\n const distCandidate = join(dir, \"dist\", \"reports\", \"maintenance-email\", \"assets\", \"check.png\");\n if (existsSync(distCandidate)) {\n cachedAssetsDir = dirname(distCandidate);\n return cachedAssetsDir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(\n `loadBundledImages: could not locate maintenance-email assets dir by walking up from ${fileURLToPath(import.meta.url)}. Checked both src/ and dist/ layouts.`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Read the bundled image bytes from disk. Both Maintenance and Testing\n * variants reference `check.png`; only the Maintenance variant references\n * `blurredTests.jpg`.\n */\nexport async function loadBundledImages(): Promise<{\n check: BundledImage;\n blurred: BundledImage;\n}> {\n const assetsDir = resolveAssetsDir();\n const [check, blurred] = await Promise.all([\n readFile(join(assetsDir, \"check.png\")),\n readFile(join(assetsDir, \"blurredTests.jpg\")),\n ]);\n return {\n check: {\n bytes: new Uint8Array(check),\n contentType: \"image/png\",\n cid: CHECK_CID,\n filename: \"check.png\",\n },\n blurred: {\n bytes: new Uint8Array(blurred),\n contentType: \"image/jpeg\",\n cid: BLURRED_CID,\n filename: \"blurredTests.jpg\",\n },\n };\n}\n","import type { ReportData } from \"../types.js\";\nimport { CHECK_CID, BLURRED_CID } from \"./assets/index.js\";\n\n// Bundled images: shipped in dist/ via tsup onSuccess copy, attached inline via\n// CID by orchestrate.ts at send time. No external CDN dependency.\nconst CHECK_PNG = `cid:${CHECK_CID}`;\nconst BLURRED_TESTS = `cid:${BLURRED_CID}`;\n\nfunction fmtDate(d: Date | null): string {\n if (!d) return \"\";\n // Airtable date fields are wall-clock YYYY-MM-DD strings parsed as UTC midnight.\n // Use UTC accessors so the rendered date matches what the operator entered.\n // US format: MM.DD.YYYY (Reddoor is Texas-based, clients are US).\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const yyyy = d.getUTCFullYear();\n return `${mm}.${dd}.${yyyy}`;\n}\n\nfunction fmtUsers(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n/**\n * Escape operator/site-controlled strings before interpolating into the MJML markup.\n * MJML parses as XML with `validationLevel: \"strict\"`, so a raw `&`, `<`, `>`, or `\"`\n * in a site name (e.g. \"Brown & Co\"), URL, or commentary throws at render time and blocks\n * the send. Apply to every interpolation of siteName / siteUrl / commentary.\n */\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nconst TREND_UP = \"#2E7D32\"; // positive green — growth reads as good\nconst TREND_NEUTRAL = \"#757575\"; // muted grey — dips/flat aren't failures (and brand red is reserved)\n\nfunction trendText(color: string, text: string): string {\n return `<mj-text color=\"${color}\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">${text}</mj-text>`;\n}\n\n/**\n * The line under \"{N} Users\": a directional trend vs the previous period when both numbers\n * are real, else a graceful fallback. `undefined` means GA was unavailable (distinct from a\n * real 0). Up = green; down/flat = muted grey (a traffic dip isn't a failure).\n */\nfunction analyticsTrendLine(cur: number | undefined, prev: number | undefined): string {\n if (cur === undefined || prev === undefined) {\n // GA unavailable for one/both — show the prior count if we have it, else an em dash.\n return trendText(TREND_NEUTRAL, `Last Period: ${prev !== undefined ? fmtUsers(prev) : \"—\"}`);\n }\n if (prev === 0) {\n return cur > 0\n ? trendText(TREND_UP, \"▲ New this period (0 last period)\")\n : trendText(TREND_NEUTRAL, \"Last Period: 0\");\n }\n const pct = Math.round(((cur - prev) / prev) * 100);\n const range = `(${fmtUsers(prev)} → ${fmtUsers(cur)})`;\n if (pct > 0) return trendText(TREND_UP, `▲ ${pct}% vs last period ${range}`);\n if (pct < 0) return trendText(TREND_NEUTRAL, `▼ ${Math.abs(pct)}% vs last period ${range}`);\n return trendText(TREND_NEUTRAL, `No change vs last period (${fmtUsers(prev)})`);\n}\n\nfunction maintenanceChecksSection(searchPosition?: number): string {\n const googleLabel =\n searchPosition !== undefined ? `Page 1 Google Result (#${searchPosition})` : \"Google Indexed\";\n const rows = [\n \"Reviewed Logs\",\n \"CMS Checked\",\n \"DNS Checked\",\n googleLabel,\n \"Reviewed Certificate\",\n \"Security Updates\",\n ];\n return rows\n .map(\n (label, i) => `\n <mj-section background-color=\"white\" padding=\"0px\"${i === rows.length - 1 ? ' padding-bottom=\"36px\"' : \"\"}>\n <mj-group>\n <mj-column padding-left=\"0px\" width=\"90%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"}>\n <mj-text height=\"25px\" padding-left=\"0px\" color=\"#757575\" padding-top=\"20px\" padding-bottom=\"7.5px\" font-size=\"16px\">${label}</mj-text>\n </mj-column>\n <mj-column width=\"10%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"} padding-top=\"15px\">\n <mj-image align=\"right\" padding-right=\"0px\" width=\"20px\" height=\"20px\" padding-top=\"2.5px\" padding-bottom=\"15px\" src=\"${CHECK_PNG}\" />\n </mj-column>\n </mj-group>\n </mj-section>`,\n )\n .join(\"\");\n}\n\nfunction testingChecklistSection(): string {\n const rows = [\n \"Desktop Browsers\",\n \"Mobile Browsers\",\n \"Package Updates\",\n \"Bottlenecks\",\n \"Form Functionality\",\n \"Animation Functionality\",\n ];\n return rows\n .map(\n (label, i) => `\n <mj-section background-color=\"#F4F4F4\" padding=\"0px\"${i === rows.length - 1 ? ' padding-bottom=\"60px\"' : \"\"}>\n <mj-group>\n <mj-column width=\"90%\" padding-left=\"0px\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"}>\n <mj-text height=\"25px\" padding-left=\"0px\" color=\"#757575\" padding-top=\"20px\" padding-bottom=\"7.5px\" font-size=\"16px\">${label}</mj-text>\n </mj-column>\n <mj-column width=\"10%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"} padding-top=\"15px\">\n <mj-image align=\"right\" padding-right=\"0px\" width=\"20px\" height=\"20px\" padding-top=\"2.5px\" padding-bottom=\"15px\" src=\"${CHECK_PNG}\" />\n </mj-column>\n </mj-group>\n </mj-section>`,\n )\n .join(\"\");\n}\n\nfunction maintenanceTestingPlaceholder(lastTested: Date | null): string {\n return `\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-image href=\"mailto:info@reddoorla.com\" src=\"${BLURRED_TESTS}\" />\n </mj-column>\n </mj-section>\n <mj-section background-color=\"#F4F4F4\" padding-top=\"0px\">\n <mj-column>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Last Tested: ${fmtDate(lastTested)}</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction testingIntroSection(): string {\n return `\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">TESTING</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Testing includes checks similar to those at launch: testing on common browsers and operating systems, at different screen sizes, and checking every function, and updating all packages for performance rather than just those needed for security.</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction commentarySection(text: string): string {\n return `\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"55px\">NOTES</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">${escapeXml(text).replace(/\\n/g, \"<br/>\")}</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction hasHeaderDims(\n data: ReportData,\n): data is ReportData & { headerWidth: number; headerHeight: number; headerBgColor: string } {\n return Boolean(data.headerWidth && data.headerHeight && data.headerBgColor);\n}\n\nfunction headerImageTag(data: ReportData): string {\n const src = `cid:${data.headerImageCid}`;\n const alt = `${escapeXml(data.siteName)} maintenance report`;\n const href = escapeXml(data.siteUrl);\n // Reserve the box and show a matched placeholder while the image loads / if blocked.\n // Critically, we do NOT set an mj-image `height` — MJML would emit `height:<px>` while\n // keeping `width:100%`, locking the height while the width scales and distorting the\n // image at any rendered width != the design width (mobile, narrow panes). Instead the\n // image stays `height:auto` (proportional) and the box is reserved via `aspect-ratio`\n // in the head <mj-style> below (see headerStyleBlock). `container-background-color` is\n // the placeholder; the bare fallback (no dims, e.g. local preview) keeps today's behavior.\n if (hasHeaderDims(data)) {\n return `<mj-image href=\"${href}\" src=\"${src}\" alt=\"${alt}\" width=\"${data.headerWidth}px\" css-class=\"rd-header\" container-background-color=\"${data.headerBgColor}\" />`;\n }\n return `<mj-image href=\"${href}\" src=\"${src}\" alt=\"${alt}\" />`;\n}\n\nfunction headerStyleBlock(data: ReportData): string {\n if (!hasHeaderDims(data)) return \"\";\n // Reserve the header's vertical space by aspect ratio so it scales proportionally with\n // its fluid (width:100%) width — no fixed pixel height, so it never squishes.\n // `height:auto !important` defends against any client honoring MJML's inline height.\n return `<mj-style>.rd-header img { height: auto !important; aspect-ratio: ${data.headerWidth} / ${data.headerHeight}; }</mj-style>`;\n}\n\nexport function buildMjml(data: ReportData): string {\n const isTesting = data.reportType === \"Testing\";\n const previewText = `Checked up on ${escapeXml(data.siteName)}`;\n\n return `<mjml>\n <mj-head>\n <mj-attributes>\n <mj-text font-family=\"helvetica, sans-serif\" padding-left=\"5px\" padding-right=\"5px\" />\n <mj-section padding-left=\"11%\" padding-right=\"11%\"/>\n <mj-image padding=\"0px\" />\n </mj-attributes>\n <mj-preview>${previewText}</mj-preview>\n ${headerStyleBlock(data)}\n </mj-head>\n <mj-body background-color=\"white\">\n <mj-section background-color=\"#F4F4F4\" padding-top=\"0px\" padding-bottom=\"0px\" padding-left=\"0px\" padding-right=\"0px\">\n <mj-column>\n ${headerImageTag(data)}\n </mj-column>\n </mj-section>\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">COMPLETED ON</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\">${fmtDate(data.completedOn)}</mj-text>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">MAINTENANCE CHECKS</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Includes checking the hosting, DNS, Content Management System (CMS, if applicable), search indexing and security of the site for major flaws and updating as necessary.</mj-text>\n </mj-column>\n </mj-section>\n ${maintenanceChecksSection(data.searchPosition)}\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"55px\">LIGHTHOUSE SCORES*</mj-text>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Performance</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.performance}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 50–89 // Ideal 90–100</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Readability</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.accessibility}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 80–99 // Ideal 100</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Best Practices</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.bestPractices}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 60–79 // Ideal 80–92</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Site Structure</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.seo}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 50–89 // Ideal 90–100</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" padding-bottom=\"36px\" line-height=\"20px\">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>\n </mj-column>\n </mj-section>\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">ANALYTICS</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\">${data.gaUsersCurrent !== undefined ? fmtUsers(data.gaUsersCurrent) : \"—\"} Users</mj-text>\n ${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" padding-bottom=\"36px\" line-height=\"20px\">Contact us if you are interested in more in-depth data or have questions about SEO.</mj-text>\n </mj-column>\n </mj-section>\n ${isTesting ? testingIntroSection() + testingChecklistSection() : maintenanceTestingPlaceholder(data.lastTestedDate)}\n ${data.commentary ? commentarySection(data.commentary) : \"\"}\n <mj-section background-color=\"white\">\n <mj-column padding-top=\"36px\">\n <mj-text color=\"#C00\" font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"700\" padding-top=\"36px\" line-height=\"36px\">Any questions, concerns or requests?</mj-text>\n <mj-text font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"300\" line-height=\"30px\">Just hit reply.</mj-text>\n <mj-text font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"300\" padding-top=\"0px\" line-height=\"30px\" padding-bottom=\"36px\">We're here to help in any way we can.</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" line-height=\"20px\" font-style=\"italic\">Copyright ${new Date().getFullYear()} Reddoor Creative, LLC. All rights reserved.</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"700\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Our mailing address is:</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Reddoor Creative, LLC</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">29027 Dapper Dan</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Fair Oaks Ranch, TX 78015</mj-text>\n </mj-column>\n </mj-section>\n </mj-body>\n</mjml>`;\n}\n","import type { FieldSet, Records } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport type { ReportType, LighthouseScores } from \"../types.js\";\n\nexport const REPORTS_TABLE = \"Reports\";\n\nexport type DeliveryStatus = \"pending\" | \"delivered\" | \"bounced\" | \"complained\";\n\nexport type ReportRow = {\n id: string;\n reportId: string;\n siteId: string;\n reportType: ReportType;\n periodStart: string | null;\n periodEnd: string | null;\n completedOn: string | null;\n lighthouse: LighthouseScores | null;\n gaUsersCurrent: number | null;\n gaUsersPrevious: number | null;\n searchFoundPage1: boolean | null;\n searchPosition: number | null;\n lastTestedDate: string | null;\n commentary: string | null;\n subjectOverride: string | null;\n draftReady: boolean;\n approvedToSend: boolean;\n sentAt: string | null;\n deliveryStatus: DeliveryStatus;\n renderedHtmlAttachment: { url: string; filename: string } | null;\n /** Read out of the Resend response and stored in a hidden field; needed for webhook reconciliation. */\n resendMessageId: string | null;\n};\n\nfunction mapRow(rec: { id: string; fields: Record<string, unknown> }): ReportRow {\n const f = rec.fields;\n const linkSites = (f[\"Site\"] as string[] | undefined) ?? [];\n const html =\n ((f[\"Rendered HTML\"] as Array<{ url: string; filename: string }> | undefined) ?? [])[0] ?? null;\n return {\n id: rec.id,\n reportId: String(f[\"Report ID\"] ?? \"\"),\n siteId: linkSites[0] ?? \"\",\n reportType: ((f[\"Report type\"] as string | undefined) ?? \"Maintenance\") as ReportType,\n periodStart: (f[\"Period start\"] as string | undefined) ?? null,\n periodEnd: (f[\"Period end\"] as string | undefined) ?? null,\n completedOn: (f[\"Completed on\"] as string | undefined) ?? null,\n lighthouse: lighthouseFromFields(f),\n gaUsersCurrent: (f[\"GA users (period)\"] as number | undefined) ?? null,\n gaUsersPrevious: (f[\"GA users (prev period)\"] as number | undefined) ?? null,\n searchFoundPage1:\n typeof f[\"Search found page 1\"] === \"boolean\" ? (f[\"Search found page 1\"] as boolean) : null,\n searchPosition: (f[\"Search position\"] as number | undefined) ?? null,\n lastTestedDate: (f[\"Last tested date\"] as string | undefined) ?? null,\n commentary: (f[\"Commentary\"] as string | undefined) ?? null,\n subjectOverride: (f[\"Subject override\"] as string | undefined) ?? null,\n draftReady: Boolean(f[\"Draft ready\"]),\n approvedToSend: Boolean(f[\"Approved to send\"]),\n sentAt: (f[\"Sent at\"] as string | undefined) ?? null,\n deliveryStatus: ((f[\"Delivery status\"] as string | undefined) ?? \"pending\") as DeliveryStatus,\n renderedHtmlAttachment: html,\n resendMessageId: (f[\"Resend message ID\"] as string | undefined) ?? null,\n };\n}\n\nfunction lighthouseFromFields(f: Record<string, unknown>): LighthouseScores | null {\n const p = f[\"Lighthouse — Performance\"];\n const a = f[\"Lighthouse — Accessibility\"];\n const b = f[\"Lighthouse — Best Practices\"];\n const s = f[\"Lighthouse — SEO\"];\n if (\n typeof p !== \"number\" ||\n typeof a !== \"number\" ||\n typeof b !== \"number\" ||\n typeof s !== \"number\"\n )\n return null;\n return { performance: p, accessibility: a, bestPractices: b, seo: s };\n}\n\nexport type DraftInput = {\n reportId: string;\n siteId: string;\n reportType: ReportType;\n periodStart: Date;\n periodEnd: Date;\n completedOn: Date;\n lighthouse: LighthouseScores;\n lastTestedDate: Date | null;\n /** GA \"Users\" for the period / previous period. Omitted when GA is not configured\n * for the site or the fetch failed — the operator fills the fields manually. */\n gaUsersCurrent?: number;\n gaUsersPrevious?: number;\n /** Search-presence result. `searchFoundPage1` is written whenever the check ran (true or\n * false — false is the operator-only negative signal). `searchPosition` only when found. */\n searchFoundPage1?: boolean;\n searchPosition?: number;\n};\n\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Escape a string for safe interpolation into an Airtable filterByFormula.\n * Airtable formulas use SQL-like string literals; we escape backslash and\n * double quote. Used wherever an externally-supplied string flows into a\n * formula (e.g. Resend message ids on the webhook path).\n */\nexport function escapeFormulaString(s: string): string {\n return s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nexport async function createDraft(base: AirtableBase, input: DraftInput): Promise<ReportRow> {\n // Set Delivery status to \"pending\" at creation time, NOT at send time. This\n // matters for H4: if stampSent wrote \"pending\" after the webhook had already\n // written \"delivered\" (race), the operator would see a regressed status.\n const fields: FieldSet = {\n \"Report ID\": input.reportId,\n Site: [input.siteId],\n \"Report type\": input.reportType,\n \"Period start\": ymd(input.periodStart),\n \"Period end\": ymd(input.periodEnd),\n \"Completed on\": ymd(input.completedOn),\n \"Lighthouse — Performance\": input.lighthouse.performance,\n \"Lighthouse — Accessibility\": input.lighthouse.accessibility,\n \"Lighthouse — Best Practices\": input.lighthouse.bestPractices,\n \"Lighthouse — SEO\": input.lighthouse.seo,\n \"Delivery status\": \"pending\",\n };\n if (input.lastTestedDate) fields[\"Last tested date\"] = ymd(input.lastTestedDate);\n // GA fields are written only when supplied (GA configured + fetch succeeded). When\n // omitted the row keeps them blank for manual entry — the pre-GA behavior.\n if (input.gaUsersCurrent !== undefined) fields[\"GA users (period)\"] = input.gaUsersCurrent;\n if (input.gaUsersPrevious !== undefined) fields[\"GA users (prev period)\"] = input.gaUsersPrevious;\n if (input.searchFoundPage1 !== undefined) fields[\"Search found page 1\"] = input.searchFoundPage1;\n if (input.searchPosition !== undefined) fields[\"Search position\"] = input.searchPosition;\n const created = (await base(REPORTS_TABLE).create([{ fields }])) as Records<FieldSet>;\n const rec = created[0];\n if (!rec) throw new Error(\"Airtable create returned no records\");\n return mapRow({ id: rec.id, fields: rec.fields });\n}\n\nexport async function setDraftReady(\n base: AirtableBase,\n recordId: string,\n ready: boolean,\n): Promise<void> {\n await base(REPORTS_TABLE).update([{ id: recordId, fields: { \"Draft ready\": ready } }]);\n}\n\nexport async function listSendableReports(base: AirtableBase): Promise<ReportRow[]> {\n const out: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula:\n \"AND({Draft ready} = TRUE(), {Approved to send} = TRUE(), {Sent at} = BLANK())\",\n pageSize: 100,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\nexport async function listReportsForSite(base: AirtableBase, siteId: string): Promise<ReportRow[]> {\n // Anchor with commas so a prefix collision (record id A is a substring of\n // record id B) can't pull in another site's reports. ARRAYJOIN({Site}, \",\")\n // produces \"rec1,rec2,rec3\"; wrap both sides with sentinels for safety.\n const safeId = escapeFormulaString(siteId);\n const out: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula: `FIND(\",${safeId},\", \",\" & ARRAYJOIN({Site}, \",\") & \",\") > 0`,\n pageSize: 100,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\n/**\n * Mark a row as sent: write `Sent at` and `Resend message ID` only. Crucially\n * does NOT touch `Delivery status` — that's set to \"pending\" in createDraft\n * and updated by the webhook from there. If we wrote \"pending\" here we could\n * clobber a \"delivered\" that the webhook raced ahead and wrote first (H4).\n */\nexport async function stampSent(\n base: AirtableBase,\n recordId: string,\n sentAt: Date,\n messageId: string,\n): Promise<void> {\n await base(REPORTS_TABLE).update([\n {\n id: recordId,\n fields: {\n \"Sent at\": sentAt.toISOString(),\n \"Resend message ID\": messageId,\n },\n },\n ]);\n}\n\nexport async function setDeliveryStatus(\n base: AirtableBase,\n recordId: string,\n status: DeliveryStatus,\n): Promise<void> {\n await base(REPORTS_TABLE).update([{ id: recordId, fields: { \"Delivery status\": status } }]);\n}\n\nexport async function findReportByMessageId(\n base: AirtableBase,\n messageId: string,\n): Promise<ReportRow | null> {\n const rows: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula: `{Resend message ID} = \"${escapeFormulaString(messageId)}\"`,\n maxRecords: 1,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) rows.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return rows[0] ?? null;\n}\n","export async function fetchAttachmentBytes(\n url: string,\n): Promise<{ bytes: Uint8Array; contentType: string }> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch Airtable attachment ${res.status} ${res.statusText} (url=${url})`,\n );\n }\n const contentType = res.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const ab = await res.arrayBuffer();\n return { bytes: new Uint8Array(ab), contentType };\n}\n\n/**\n * Upload bytes (or a string) as an attachment to a specific record + field.\n * Uses Airtable's content.airtable.com upload endpoint (base64 body) because\n * the standard SDK only accepts public URLs for attachments, and we don't\n * host the generated content anywhere public.\n *\n * Docs: https://airtable.com/developers/web/api/upload-attachment\n *\n * Requires AIRTABLE_PAT + AIRTABLE_BASE_ID in env (same as the rest of the\n * reports module). The fieldName is URL-encoded for the request path.\n */\nexport async function uploadAttachment(\n recordId: string,\n fieldName: string,\n body: Uint8Array | string,\n filename: string,\n contentType: string,\n): Promise<void> {\n const apiKey = process.env.AIRTABLE_PAT;\n const baseId = process.env.AIRTABLE_BASE_ID;\n if (!apiKey || !baseId) {\n throw new Error(\"AIRTABLE_PAT and AIRTABLE_BASE_ID must be set\");\n }\n const base64 =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf-8\").toString(\"base64\")\n : Buffer.from(body).toString(\"base64\");\n const payload = { contentType, file: base64, filename };\n const url = `https://content.airtable.com/v0/${baseId}/${recordId}/${encodeURIComponent(fieldName)}/uploadAttachment`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n throw new Error(`Airtable upload failed: ${res.status} ${res.statusText} ${await res.text()}`);\n }\n}\n","import { dirname, join } from \"node:path\";\nimport { defaultCredentialsPath } from \"../../util/credentials.js\";\n\nexport type GaConfig = {\n /** Workspace user the service account impersonates (domain-wide delegation). */\n subject: string;\n /** Absolute path to the service-account JSON key file. */\n keyPath: string;\n};\n\n/**\n * Read GA configuration from the environment (credentials.env is already loaded into\n * process.env by the CLI entrypoint). Returns null when `GA_SUBJECT` is unset — the\n * signal that GA enrichment is simply not configured, so drafting skips it silently.\n *\n * `GA_SA_KEY_PATH` is optional; it defaults to `ga-service-account.json` alongside the\n * credentials file (e.g. ~/.config/reddoor-maint/), keeping the key out of the repo.\n */\nexport function readGaConfig(): GaConfig | null {\n const subject = process.env.GA_SUBJECT?.trim();\n if (!subject) return null;\n const keyPath =\n process.env.GA_SA_KEY_PATH?.trim() ||\n join(dirname(defaultCredentialsPath()), \"ga-service-account.json\");\n return { subject, keyPath };\n}\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/** Resolve the canonical credentials file path. Respects $XDG_CONFIG_HOME\n * (Linux/macOS convention) and falls back to ~/.config/reddoor-maint/. */\nexport function defaultCredentialsPath(): string {\n const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\");\n return join(base, \"reddoor-maint\", \"credentials.env\");\n}\n\n/** Parse a tiny subset of dotenv: `KEY=value` per line, `# comments`,\n * blank lines. Quoted values strip the surrounding quotes. Unknown\n * shapes (no `=`, leading whitespace before `=`, etc.) are skipped\n * silently — this is a credentials file, not a config language. */\nexport function parseEnvFile(contents: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of contents.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n\n/** Load credentials from `path` (default: canonical file) into `process.env`.\n * `process.env` values win — file-defined keys are only applied when the\n * env var is currently undefined. Missing/unreadable file is a silent\n * no-op; commands that need the credentials will fail downstream with\n * their own clear error. Returns the keys actually applied (diagnostics). */\nexport function loadCredentialsIntoEnv(path: string = defaultCredentialsPath()): string[] {\n let contents: string;\n try {\n contents = readFileSync(path, \"utf-8\");\n } catch {\n return [];\n }\n const parsed = parseEnvFile(contents);\n const applied: string[] = [];\n for (const [k, v] of Object.entries(parsed)) {\n if (process.env[k] === undefined) {\n process.env[k] = v;\n applied.push(k);\n }\n }\n return applied;\n}\n","import { readFileSync } from \"node:fs\";\nimport { JWT } from \"google-auth-library\";\nimport { BetaAnalyticsDataClient } from \"@google-analytics/data\";\n\nconst ANALYTICS_READONLY = \"https://www.googleapis.com/auth/analytics.readonly\";\nconst MS_PER_DAY = 86_400_000;\n\nexport type GaQuery = {\n /** GA4 numeric property ID (e.g. \"471880366\"). */\n propertyId: string;\n /** Workspace user to impersonate via domain-wide delegation. */\n subject: string;\n /** Path to the service-account JSON key. */\n keyPath: string;\n};\n\n/** UTC YYYY-MM-DD — matches the rest of the reports pipeline's date handling. */\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Fetch GA4 `activeUsers` (\"Users\") for a report period and the equal-length window\n * immediately before it, via a domain-wide-delegation service account that impersonates\n * `subject`. Throws on any auth/API error — the caller (draftReportForSite) soft-fails.\n *\n * Previous window: same length as the current period, ending the day before `periodStart`.\n */\nexport async function fetchPeriodUsers(\n query: GaQuery,\n periodStart: Date,\n periodEnd: Date,\n): Promise<{ current: number; previous: number }> {\n const key = JSON.parse(readFileSync(query.keyPath, \"utf8\")) as {\n client_email: string;\n private_key: string;\n };\n const authClient = new JWT({\n email: key.client_email,\n key: key.private_key,\n scopes: [ANALYTICS_READONLY],\n subject: query.subject,\n });\n const client = new BetaAnalyticsDataClient({ authClient });\n\n const lengthDays = Math.round((periodEnd.getTime() - periodStart.getTime()) / MS_PER_DAY);\n const prevEnd = new Date(periodStart.getTime() - MS_PER_DAY);\n const prevStart = new Date(prevEnd.getTime() - lengthDays * MS_PER_DAY);\n\n const property = `properties/${query.propertyId}`;\n const run = async (start: Date, end: Date): Promise<number> => {\n const [resp] = await client.runReport({\n property,\n dateRanges: [{ startDate: ymd(start), endDate: ymd(end) }],\n metrics: [{ name: \"activeUsers\" }],\n });\n const raw = resp.rows?.[0]?.metricValues?.[0]?.value ?? \"0\";\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n };\n\n const current = await run(periodStart, periodEnd);\n const previous = await run(prevStart, prevEnd);\n return { current, previous };\n}\n","import { readFileSync } from \"node:fs\";\nimport { JWT } from \"google-auth-library\";\n\nconst WEBMASTERS_READONLY = \"https://www.googleapis.com/auth/webmasters.readonly\";\nconst SC_BASE = \"https://searchconsole.googleapis.com/webmasters/v3\";\n/** Average-position threshold for \"on page 1\" (10 organic results per page). */\nconst PAGE_1_MAX_POSITION = 10;\n\nexport type SearchPresenceQuery = {\n /** Path to the service-account JSON key (same one GA uses). */\n keyPath: string;\n /** Workspace user to impersonate via domain-wide delegation. */\n subject: string;\n /** Explicit Search Console property (`sc-domain:...` or `https://.../`). Overrides auto-resolution. */\n property?: string | undefined;\n /** Site host, used to auto-resolve the property from `sites.list` when `property` is absent. */\n host: string;\n /** Operator-supplied query string (e.g. the business name). */\n query: string;\n};\n\nexport type SearchPresence = {\n /** True when the average position for the query is on page 1 (<= 10). */\n foundOnPage1: boolean;\n /** Rounded average position, or null when not found / no data. */\n position: number | null;\n};\n\ntype SiteEntry = { siteUrl: string };\n\n/** Reduce any property string or URL to a bare host: no `sc-domain:`, scheme, `www.`, path, lowercased. */\nexport function bareHost(s: string): string {\n return s\n .trim()\n .replace(/^sc-domain:/i, \"\")\n .replace(/^https?:\\/\\//i, \"\")\n .split(\"/\")[0]!\n .replace(/^www\\./i, \"\")\n .toLowerCase();\n}\n\n/**\n * All Search Console properties matching `host`, ordered for query fallback: Domain\n * (`sc-domain:`) forms first (broadest coverage), then URL-prefix forms. A site can be verified\n * as both; a freshly-created Domain property has no backfilled history, so its data can be empty\n * even while a long-lived URL-prefix property has data — hence we return every match and let the\n * caller try them in order until one returns data. Empty list = nothing matches.\n */\nexport function resolvePropertyCandidates(entries: SiteEntry[], host: string): string[] {\n const target = bareHost(host);\n const matches = entries.filter((e) => bareHost(e.siteUrl) === target).map((e) => e.siteUrl);\n const domains = matches.filter((s) => s.toLowerCase().startsWith(\"sc-domain:\"));\n const prefixes = matches.filter((s) => !s.toLowerCase().startsWith(\"sc-domain:\"));\n return [...domains, ...prefixes];\n}\n\n/** UTC YYYY-MM-DD — matches the rest of the reports pipeline. */\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Query Google Search Console for the average position of `query` on the site over the report\n * period, via a domain-wide-delegation service account impersonating `subject`. Uses `property`\n * verbatim when given (operator's choice is final — no fallback); otherwise auto-discovers all\n * matching properties via `sites.list` and tries them in order (Domain first) until one returns\n * data. Throws on any auth/API error — the caller (draftReportForSite) soft-fails.\n */\nexport async function fetchSearchPresence(\n q: SearchPresenceQuery,\n periodStart: Date,\n periodEnd: Date,\n): Promise<SearchPresence> {\n const key = JSON.parse(readFileSync(q.keyPath, \"utf8\")) as {\n client_email: string;\n private_key: string;\n };\n const jwt = new JWT({\n email: key.client_email,\n key: key.private_key,\n scopes: [WEBMASTERS_READONLY],\n subject: q.subject,\n });\n\n const explicit = q.property?.trim();\n let candidates: string[];\n if (explicit) {\n candidates = [explicit];\n } else {\n const list = await jwt.request<{ siteEntry?: SiteEntry[] }>({\n url: `${SC_BASE}/sites`,\n method: \"GET\",\n });\n candidates = resolvePropertyCandidates(list.data.siteEntry ?? [], q.host);\n if (candidates.length === 0) return { foundOnPage1: false, position: null };\n }\n\n for (const property of candidates) {\n const res = await jwt.request<{ rows?: Array<{ position?: number }> }>({\n url: `${SC_BASE}/sites/${encodeURIComponent(property)}/searchAnalytics/query`,\n method: \"POST\",\n data: {\n startDate: ymd(periodStart),\n endDate: ymd(periodEnd),\n dimensions: [\"query\"],\n dimensionFilterGroups: [\n {\n filters: [\n { dimension: \"query\", operator: \"equals\", expression: q.query.toLowerCase() },\n ],\n },\n ],\n rowLimit: 1,\n },\n });\n const pos = res.data.rows?.[0]?.position;\n if (typeof pos === \"number\") {\n return { foundOnPage1: pos <= PAGE_1_MAX_POSITION, position: Math.round(pos) };\n }\n }\n return { foundOnPage1: false, position: null };\n}\n","import Airtable from \"airtable\";\nimport { defaultCredentialsPath } from \"../../util/credentials.js\";\n\nexport type AirtableConfig = {\n apiKey: string;\n baseId: string;\n};\n\nfunction missing(name: string): Error {\n return Object.assign(\n new Error(\n `${name} not set. Export it in your shell or put it in ${defaultCredentialsPath()} as ${name}=...`,\n ),\n { exitCode: 2 },\n );\n}\n\nexport function readAirtableConfig(): AirtableConfig {\n const apiKey = process.env.AIRTABLE_PAT;\n const baseId = process.env.AIRTABLE_BASE_ID;\n if (!apiKey) throw missing(\"AIRTABLE_PAT\");\n if (!baseId) throw missing(\"AIRTABLE_BASE_ID\");\n return { apiKey, baseId };\n}\n\nexport type AirtableBase = ReturnType<typeof openBase>;\n\nexport function openBase(cfg: AirtableConfig) {\n return new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);\n}\n","import sharp from \"sharp\";\n\nexport type PreparedHeaderImage = {\n /** Resized JPEG bytes to attach inline (CID) in place of the Airtable original. */\n bytes: Uint8Array;\n /** Always \"image/jpeg\" — we re-encode for predictable size and a flat white background. */\n contentType: string;\n /** CSS display width in px (≤ requested, never wider than the source has pixels for). */\n displayWidth: number;\n /** CSS display height in px, source aspect ratio preserved (no distortion). */\n displayHeight: number;\n /** Dominant-color hex (e.g. \"#cfc3a8\"), used as the loading/blocked placeholder box. */\n placeholderColor: string;\n};\n\nexport type PrepareHeaderImageOptions = {\n /** Intended CSS display width. The email body is 600px, so that's the default. */\n displayWidth?: number;\n};\n\nconst DEFAULT_DISPLAY_WIDTH = 600;\n/** Encode the source at 2× display width so it stays crisp on retina screens. */\nconst RETINA_SCALE = 2;\n/** Quality is for *resized* pixels — at 1200px the texture/text read as sharp; bytes are tiny. */\nconst JPEG_QUALITY = 82;\n\nfunction channelToHex(value: number): string {\n return Math.max(0, Math.min(255, Math.round(value)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/**\n * Downscale an oversized header image for email: 2× the display width (retina) at most,\n * never upscaled, re-encoded as JPEG on a flat white background. Also reports the display\n * dimensions (so the template can reserve the box and stop reflow) and a dominant color\n * (so the reserved box shows a matched placeholder while the image loads).\n *\n * Root cause this addresses: Airtable headers can be multi-MB / 2400px+ while the email\n * renders them at ~600px — shipping ~16× more pixels than the display can use.\n */\nexport async function prepareHeaderImage(\n bytes: Uint8Array,\n options: PrepareHeaderImageOptions = {},\n): Promise<PreparedHeaderImage> {\n const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;\n const input = Buffer.from(bytes);\n\n const meta = await sharp(input).metadata();\n const origWidth = meta.width;\n const origHeight = meta.height;\n if (!origWidth || !origHeight) {\n throw new Error(\"prepareHeaderImage: could not read source image dimensions\");\n }\n\n // Never claim a wider display than the source can fill at 1×.\n const displayWidth = Math.min(requestedDisplayWidth, origWidth);\n const displayHeight = Math.round((displayWidth * origHeight) / origWidth);\n\n // Encode at 2× display for retina, but never enlarge a smaller original.\n const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);\n\n const out = await sharp(input)\n .resize({ width: targetSourceWidth, withoutEnlargement: true })\n .flatten({ background: \"#ffffff\" })\n .jpeg({ quality: JPEG_QUALITY })\n .toBuffer();\n\n const { dominant } = await sharp(out).stats();\n const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;\n\n return {\n bytes: new Uint8Array(out),\n contentType: \"image/jpeg\",\n displayWidth,\n displayHeight,\n placeholderColor,\n };\n}\n","import { Resend } from \"resend\";\n\nexport type ResendSendInput = {\n from: string;\n to: string[];\n cc?: string[];\n replyTo?: string;\n subject: string;\n html: string;\n attachments?: Array<{\n filename: string;\n content: string; // base64\n contentType?: string;\n /** Setting this attaches the file as inline; reference it from HTML as `src=\"cid:<id>\"`. */\n inlineContentId?: string;\n }>;\n /**\n * Stable key forwarded as the `Idempotency-Key` header. Resend dedupes calls\n * with the same key for 24 hours, returning the original message id. Use a\n * key that's stable across retries of the same logical send (e.g. the\n * Reports row id), so a network blip during stamping doesn't cause a\n * duplicate email to the client.\n */\n idempotencyKey?: string;\n};\n\nexport type ResendSendResult = {\n messageId: string;\n};\n\nexport type ResendClient = {\n send: (input: ResendSendInput) => Promise<ResendSendResult>;\n};\n\nexport function defaultResendClient(): ResendClient {\n const key = process.env.RESEND_API_KEY;\n if (!key) throw Object.assign(new Error(\"RESEND_API_KEY not set\"), { exitCode: 2 });\n const resend = new Resend(key);\n return {\n async send(input) {\n const payload: Parameters<typeof resend.emails.send>[0] = {\n from: input.from,\n to: input.to,\n subject: input.subject,\n html: input.html,\n };\n if (input.cc) payload.cc = input.cc;\n if (input.replyTo) payload.replyTo = input.replyTo;\n if (input.attachments) payload.attachments = input.attachments;\n const options: Parameters<typeof resend.emails.send>[1] = {};\n if (input.idempotencyKey) options.idempotencyKey = input.idempotencyKey;\n const { data, error } = await resend.emails.send(payload, options);\n if (error) throw new Error(`Resend error: ${error.message}`);\n if (!data?.id) throw new Error(\"Resend returned no message id\");\n return { messageId: data.id };\n },\n };\n}\n","import { openBase, readAirtableConfig } from \"../airtable/client.js\";\nimport { listSendableReports, stampSent } from \"../airtable/reports.js\";\nimport { listWebsites, siteSlug } from \"../airtable/websites.js\";\nimport type { WebsiteRow } from \"../airtable/websites.js\";\nimport type { ReportRow } from \"../airtable/reports.js\";\nimport { fetchAttachmentBytes } from \"../airtable/attachments.js\";\nimport { renderReportHtml } from \"../render.js\";\nimport { loadBundledImages } from \"../maintenance-email/assets/index.js\";\nimport { prepareHeaderImage } from \"../maintenance-email/header-image.js\";\nimport { defaultResendClient, type ResendClient } from \"./resend.js\";\n\nconst FROM_ADDRESS = \"Reddoor Reports <reports@reddoorla.com>\";\nconst REPLY_TO = \"info@reddoorla.com\";\n\nconst MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** \"May 2026\" — UTC month/year, consistent with the rest of the reports pipeline's dates. */\nfunction monthYear(d: Date): string {\n return `${MONTHS[d.getUTCMonth()]} ${d.getUTCFullYear()}`;\n}\n\nexport type OrchestrateOptions = {\n resend?: ResendClient;\n};\n\nexport async function sendApprovedReports(\n options: OrchestrateOptions = {},\n): Promise<{ output: string; code: number }> {\n const base = openBase(readAirtableConfig());\n const client = options.resend ?? defaultResendClient();\n\n const sendable = await listSendableReports(base);\n if (sendable.length === 0) return { output: \"No reports ready to send.\", code: 0 };\n\n const websites = await listWebsites(base);\n const sites = new Map(websites.map((w) => [w.id, w]));\n\n const lines: string[] = [];\n let anyFailed = false;\n for (const report of sendable) {\n const site = sites.get(report.siteId);\n if (!site) {\n lines.push(`✗ ${report.reportId} — Site row not found for id=${report.siteId}`);\n anyFailed = true;\n continue;\n }\n try {\n const messageId = await sendOne(client, base, site, report);\n lines.push(`✓ sent: ${report.reportId} (${messageId})`);\n } catch (e) {\n lines.push(`✗ ${report.reportId} — ${(e as Error).message}`);\n anyFailed = true;\n }\n }\n return { output: lines.join(\"\\n\"), code: anyFailed ? 1 : 0 };\n}\n\nasync function sendOne(\n client: ResendClient,\n base: ReturnType<typeof openBase>,\n site: WebsiteRow,\n report: ReportRow,\n): Promise<string> {\n if (!site.headerImage) {\n throw new Error(`Site '${site.name}' has no Header image set on the Websites row`);\n }\n if (!report.lighthouse) {\n throw new Error(`Report ${report.reportId} has no Lighthouse scores`);\n }\n\n const original = await fetchAttachmentBytes(site.headerImage.url);\n // Downscale the (often multi-MB / 2400px+) Airtable header to email display size, and get\n // back display dims + a placeholder color so the template can reserve the box.\n const header = await prepareHeaderImage(original.bytes);\n const bundled = await loadBundledImages();\n\n const slug = siteSlug(site.name);\n const cidName = `${slug}-header`;\n const { html } = await renderReportHtml({\n siteName: site.name,\n siteUrl: site.url,\n reportType: report.reportType,\n completedOn: report.completedOn ? new Date(report.completedOn) : new Date(),\n lighthouse: report.lighthouse,\n gaUsersCurrent: report.gaUsersCurrent ?? undefined,\n gaUsersPrevious: report.gaUsersPrevious ?? undefined,\n searchPosition:\n report.searchFoundPage1 && report.searchPosition !== null ? report.searchPosition : undefined,\n lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,\n commentary: report.commentary,\n headerImageCid: cidName,\n headerWidth: header.displayWidth,\n headerHeight: header.displayHeight,\n headerBgColor: header.placeholderColor,\n });\n\n const reportDate = report.completedOn ? new Date(report.completedOn) : new Date();\n const subject =\n report.subjectOverride ?? `${site.name} — ${monthYear(reportDate)} ${report.reportType} Report`;\n const explicitTo = parseAddresses(site.reportRecipientsTo);\n // Run pointOfContact through the parser too — operators sometimes paste\n // \"a@x, b@y\" into that single-line field.\n const fallbackTo = parseAddresses(site.pointOfContact);\n const to = explicitTo ?? fallbackTo ?? [];\n if (to.length === 0) {\n throw new Error(\n `Site '${site.name}' has no recipients (Report recipients (To) AND point of contact are both empty)`,\n );\n }\n for (const addr of to) {\n if (!isProbablyEmail(addr)) {\n throw new Error(\n `Site '${site.name}' recipient is malformed: ${addr} — fix Report recipients (To) or point of contact in Airtable`,\n );\n }\n }\n const cc = parseAddresses(site.reportRecipientsCc);\n if (cc) {\n for (const addr of cc) {\n if (!isProbablyEmail(addr)) {\n throw new Error(\n `Site '${site.name}' CC is malformed: ${addr} — fix Report recipients (CC) in Airtable`,\n );\n }\n }\n }\n\n const payload: Parameters<ResendClient[\"send\"]>[0] = {\n from: FROM_ADDRESS,\n to,\n replyTo: REPLY_TO,\n subject,\n html,\n attachments: [\n {\n filename: `${cidName}.jpg`,\n content: Buffer.from(header.bytes).toString(\"base64\"),\n contentType: header.contentType,\n inlineContentId: cidName,\n },\n // Bundled images referenced via cid:rd-check-png / cid:rd-blurred-tests-jpg\n // in the template. Attached inline so the email is self-contained — no\n // external CDN dependency, no image-blocked broken icons in webmail.\n {\n filename: bundled.check.filename,\n content: Buffer.from(bundled.check.bytes).toString(\"base64\"),\n contentType: bundled.check.contentType,\n inlineContentId: bundled.check.cid,\n },\n {\n filename: bundled.blurred.filename,\n content: Buffer.from(bundled.blurred.bytes).toString(\"base64\"),\n contentType: bundled.blurred.contentType,\n inlineContentId: bundled.blurred.cid,\n },\n ],\n // Stable across retries of the same row — if Airtable stamping fails after a\n // successful Resend, the next --send-ready replays with the same key and\n // Resend returns the original message id rather than sending a duplicate.\n idempotencyKey: `report:${report.id}`,\n };\n if (cc) payload.cc = cc;\n\n const result = await client.send(payload);\n await stampSent(base, report.id, new Date(), result.messageId);\n return result.messageId;\n}\n\n/**\n * Split a comma/newline-separated address field into a clean array.\n * Lowercases (case-insensitive dedupe) and removes empty entries. Returns\n * null if nothing survives. Does NOT understand `Display Name <email>` —\n * operators should put a bare address in the Airtable field, or use multiple\n * lines if needing multiple recipients.\n */\nexport function parseAddresses(field: string | null): string[] | null {\n if (!field) return null;\n const seen = new Set<string>();\n const list: string[] = [];\n for (const raw of field.split(/[,\\n]/)) {\n const trimmed = raw.trim().toLowerCase();\n if (!trimmed) continue;\n if (seen.has(trimmed)) continue;\n seen.add(trimmed);\n list.push(trimmed);\n }\n return list.length > 0 ? list : null;\n}\n\n/**\n * Cheap email shape check — must contain exactly one @, with non-empty\n * local and domain parts and at least one dot in the domain. We're not\n * trying to be a full RFC validator; we're trying to catch operator\n * mistakes like \"ops at acme dot com\" or a missing @ before they 422\n * at Resend.\n */\nexport function isProbablyEmail(s: string): boolean {\n const at = s.indexOf(\"@\");\n if (at < 1 || at !== s.lastIndexOf(\"@\")) return false;\n const local = s.slice(0, at);\n const domain = s.slice(at + 1);\n if (!local || !domain) return false;\n if (!domain.includes(\".\")) return false;\n if (/\\s/.test(s)) return false;\n return true;\n}\n","import type { WebsiteRow, Frequency, Status } from \"./airtable/websites.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport type { ReportType } from \"./types.js\";\n\n/** Statuses where reports are appropriate. Drops \"deprecated\" and\n * \"probably not our problem\" — even if the operator left a freq set, we don't\n * want to surface those sites in --due output. Sites with status=null pass\n * through (existing data is partial; better to surface than silently skip). */\nconst ELIGIBLE_STATUSES: ReadonlySet<Status> = new Set<Status>([\n \"in development\",\n \"launch period\",\n \"maintenance\",\n \"hosting\",\n]);\n\nexport type DueItem = {\n site: WebsiteRow;\n reportType: ReportType;\n /** Inclusive: the day the next report became due. */\n dueDate: Date;\n /** ISO date of the last `Sent at` for this (site, type), or null if there's never been one. */\n lastSent: string | null;\n};\n\nconst MONTHS: Record<Exclude<Frequency, \"None\">, number> = {\n Monthly: 1,\n Quarterly: 3,\n Yearly: 12,\n};\n\n/**\n * Add `n` calendar months in UTC, clamped to the last day of the target month.\n * Jan 31 + 1 month = Feb 28 (not Mar 3, which is what naive setMonth produces).\n * All-UTC accessors mean the result is timezone-independent.\n */\nfunction addMonths(d: Date, n: number): Date {\n const out = new Date(d);\n const day = out.getUTCDate();\n out.setUTCDate(1);\n out.setUTCMonth(out.getUTCMonth() + n);\n const lastDayOfTargetMonth = new Date(\n Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0),\n ).getUTCDate();\n out.setUTCDate(Math.min(day, lastDayOfTargetMonth));\n return out;\n}\n\n/** Truncate to UTC midnight. Avoids local-TZ skew when comparing Airtable date-only fields. */\nfunction startOfDay(d: Date): Date {\n const out = new Date(d);\n out.setUTCHours(0, 0, 0, 0);\n return out;\n}\n\nfunction lastSentForType(reports: ReportRow[], siteId: string, type: ReportType): string | null {\n const candidates = reports\n .filter((r) => r.siteId === siteId && r.reportType === type && r.sentAt !== null)\n .map((r) => r.sentAt!)\n .sort();\n return candidates[candidates.length - 1] ?? null;\n}\n\n/**\n * Computes which (site, type) pairs are due as of `today`.\n *\n * Algorithm per (site, type):\n * 1. If freq === \"None\", skip.\n * 2. baseDate = max(last Sent at for this type, site's `maintenance/testing day` fallback).\n * 3. If no baseDate exists at all, the site is due now.\n * 4. dueDate = baseDate + frequency months.\n * 5. Due iff startOfDay(today) >= startOfDay(dueDate).\n */\nexport function findDueReports(\n websites: WebsiteRow[],\n reports: ReportRow[],\n today: Date,\n): DueItem[] {\n const out: DueItem[] = [];\n const todayStart = startOfDay(today);\n\n for (const site of websites) {\n // Skip explicitly-non-active statuses (deprecated, \"probably not our problem\").\n // Null status is treated as active for backwards compat with rows that pre-date\n // the Status convention.\n if (site.status !== null && !ELIGIBLE_STATUSES.has(site.status)) continue;\n\n for (const type of [\"Maintenance\", \"Testing\"] as const) {\n const freq = type === \"Maintenance\" ? site.maintenanceFreq : site.testingFreq;\n if (freq === \"None\") continue;\n\n const lastSent = lastSentForType(reports, site.id, type);\n const fallback = type === \"Maintenance\" ? site.maintenanceDay : site.testingDay;\n const baseIso = lastSent ?? fallback;\n\n if (!baseIso) {\n out.push({ site, reportType: type, dueDate: todayStart, lastSent });\n continue;\n }\n\n const dueDate = addMonths(new Date(baseIso), MONTHS[freq]);\n if (todayStart.getTime() >= startOfDay(dueDate).getTime()) {\n out.push({ site, reportType: type, dueDate, lastSent });\n }\n }\n }\n\n return out;\n}\n","/** Render an absolute timestamp as a coarse \"Xd ago\" relative string for the\n * fleet card. Takes an explicit `now` for testability; defaults to wall clock\n * for callers (the Netlify function). Returns \"—\" for null / unparseable. */\nexport function relativeTimeFromNow(iso: string | null, now: Date = new Date()): string {\n if (!iso) return \"—\";\n const t = Date.parse(iso);\n if (Number.isNaN(t)) return \"—\";\n\n const seconds = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n if (seconds < 60) return \"just now\";\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) return `${weeks}w ago`;\n\n const months = Math.floor(days / 30);\n return `${months}mo ago`;\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport type { ReportRow } from \"../reports/airtable/reports.js\";\nimport { relativeTimeFromNow } from \"./relative-time.js\";\n\n/** Minimal HTML-escape; not for XML/attribute-edge cases, just for text + safe\n * attribute interpolation here. */\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\n/** Allow only http(s) URLs in href context; everything else collapses to \"#\". */\nfunction safeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (u.protocol === \"http:\" || u.protocol === \"https:\") return raw;\n } catch {\n // fall through\n }\n return \"#\";\n}\n\nfunction scoreTile(label: string, value: number | null): string {\n const display = value === null ? \"—\" : String(value);\n return `<div class=\"tile\"><div class=\"tile-value\">${escapeHtml(display)}</div><div class=\"tile-label\">${escapeHtml(label)}</div></div>`;\n}\n\nfunction healthTile(label: string, value: number | null, sub: string | null): string {\n const display = value === null ? \"—\" : String(value);\n const subLine = sub ? `<div class=\"tile-sub\">${escapeHtml(sub)}</div>` : \"\";\n return `<div class=\"tile\"><div class=\"tile-value\">${escapeHtml(display)}</div><div class=\"tile-label\">${escapeHtml(label)}</div>${subLine}</div>`;\n}\n\nfunction depsSub(majorBehind: number | null): string | null {\n if (majorBehind === null || majorBehind === 0) return null;\n return `${majorBehind} major behind`;\n}\n\nfunction securityTotal(site: WebsiteRow): number | null {\n const parts = [\n site.securityVulnsCritical,\n site.securityVulnsHigh,\n site.securityVulnsModerate,\n site.securityVulnsLow,\n ];\n if (parts.every((p) => p === null)) return null;\n return parts.reduce<number>((sum, p) => sum + (p ?? 0), 0);\n}\n\nfunction securitySub(site: WebsiteRow): string | null {\n const total = securityTotal(site);\n if (total === null || total === 0) return null;\n const c = site.securityVulnsCritical ?? 0;\n const h = site.securityVulnsHigh ?? 0;\n const m = site.securityVulnsModerate ?? 0;\n const l = site.securityVulnsLow ?? 0;\n return `${c}C / ${h}H / ${m}M / ${l}L`;\n}\n\nfunction reportRow(r: ReportRow): string {\n const date = r.completedOn ? escapeHtml(r.completedOn) : \"—\";\n const type = escapeHtml(r.reportType);\n const id = escapeHtml(r.reportId);\n const link = r.renderedHtmlAttachment\n ? `<a href=\"${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}\">view</a>`\n : `<span class=\"muted\">no attachment</span>`;\n return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${link}</td></tr>`;\n}\n\nconst STYLES = `\n:root { color-scheme: light dark; }\nbody { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 860px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }\n@media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }\nh1 { margin: 0 0 0.25rem; font-size: 1.75rem; }\n.meta { color: #666; margin-bottom: 2rem; }\n.meta a { color: inherit; }\n.audited { color: #999; font-size: 0.85rem; margin-bottom: 1.5rem; }\n.section { margin: 2rem 0; }\n.section h2 { font-size: 1.1rem; margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; }\n.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }\n.tile { padding: 1rem; border: 1px solid #ddd; border-radius: 6px; text-align: center; }\n@media (prefers-color-scheme: dark) { .tile { border-color: #333; } }\n.tile-value { font-size: 2rem; font-weight: 600; }\n.tile-label { font-size: 0.85rem; color: #666; margin-top: 0.25rem; }\n.tile-sub { font-size: 0.75rem; color: #999; margin-top: 0.15rem; }\ntable { width: 100%; border-collapse: collapse; }\nth, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }\n@media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }\n.muted { color: #999; }\n.empty { color: #999; padding: 1rem; border: 1px dashed #ccc; border-radius: 6px; text-align: center; }\n`;\n\n/**\n * Render the per-site dashboard as a single HTML document. Pure function:\n * no Airtable access, no env reads, no I/O. The Netlify function handler\n * fetches data, then hands it here. Easier to unit-test, easier to render\n * a static preview from CLI later.\n */\nexport function renderSiteDashboardHtml(site: WebsiteRow, reports: ReportRow[]): string {\n const name = escapeHtml(site.name);\n const urlSafe = safeUrl(site.url);\n const allScoresNull =\n site.pScore === null && site.rScore === null && site.bpScore === null && site.seoScore === null;\n\n const scoresSection = allScoresNull\n ? `<div class=\"empty\">No lighthouse data yet — run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>`\n : `<div class=\"tiles\">\n ${scoreTile(\"Performance\", site.pScore)}\n ${scoreTile(\"Accessibility\", site.rScore)}\n ${scoreTile(\"Best Practices\", site.bpScore)}\n ${scoreTile(\"SEO\", site.seoScore)}\n </div>`;\n\n const secTotal = securityTotal(site);\n const allHealthNull =\n site.a11yViolations === null && site.depsDrifted === null && secTotal === null;\n const healthSection = allHealthNull\n ? `<div class=\"empty\">No health data yet — run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>`\n : `<div class=\"tiles\">\n ${healthTile(\"Accessibility issues\", site.a11yViolations, null)}\n ${healthTile(\"Dependency updates\", site.depsDrifted, depsSub(site.depsMajorBehind))}\n ${healthTile(\"Security alerts\", secTotal, securitySub(site))}\n </div>`;\n\n const auditedLine = site.lastLighthouseAuditAt\n ? `<div class=\"audited\">Last audited ${escapeHtml(relativeTimeFromNow(site.lastLighthouseAuditAt))}</div>`\n : \"\";\n\n const reportsSection =\n reports.length === 0\n ? `<div class=\"empty\">No reports yet.</div>`\n : `<table>\n <thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>Report</th></tr></thead>\n <tbody>${reports.map(reportRow).join(\"\")}</tbody>\n </table>`;\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${name} — Reddoor maintenance</title>\n <style>${STYLES}</style>\n</head>\n<body>\n <h1>${name}</h1>\n <div class=\"meta\"><a href=\"${escapeHtml(urlSafe)}\">${escapeHtml(site.url)}</a></div>\n ${auditedLine}\n\n <div class=\"section\">\n <h2>Lighthouse</h2>\n ${scoresSection}\n </div>\n\n <div class=\"section\">\n <h2>Site Health</h2>\n ${healthSection}\n </div>\n\n <div class=\"section\">\n <h2>Reports</h2>\n ${reportsSection}\n </div>\n</body>\n</html>`;\n}\n","import { timingSafeEqual } from \"node:crypto\";\n\n/**\n * Constant-time comparison of a request-provided token against the token\n * stored on the Websites row in Airtable. Used by the per-site dashboard\n * Netlify function to gate /s/<slug>?t=<token>.\n *\n * Returns false for any of:\n * - provided token missing / empty\n * - expected token missing (the site has no Dashboard Token configured)\n * - lengths differ (constant-time path skipped because `timingSafeEqual`\n * throws on length mismatch — the length difference itself doesn't leak\n * anything secret since the expected token's length is fixed per site)\n *\n * Treats null/undefined/empty-string from the request as a single\n * \"no token\" state — keeps the handler's branching simple.\n */\nexport function verifyDashboardToken(\n provided: string | null | undefined,\n expected: string | null,\n): boolean {\n if (!provided || !expected) return false;\n if (provided.length !== expected.length) return false;\n return timingSafeEqual(Buffer.from(provided, \"utf-8\"), Buffer.from(expected, \"utf-8\"));\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\n\nexport type OnboardingStatus = {\n score: number;\n total: 4;\n checks: {\n firstAudit: boolean;\n recipients: boolean;\n schedule: boolean;\n poc: boolean;\n };\n};\n\nfunction isNonEmpty(s: string | null | undefined): boolean {\n return typeof s === \"string\" && s.trim().length > 0;\n}\n\n/** Four-point onboarding signal for the fleet card. A site is \"fully onboarded\"\n * when it has been audited at least once, has a To-recipient for monthly\n * reports, has a maintenance schedule that isn't \"None\", and has a named POC. */\nexport function onboardingStatus(row: WebsiteRow): OnboardingStatus {\n const checks = {\n firstAudit: isNonEmpty(row.lastLighthouseAuditAt),\n recipients: isNonEmpty(row.reportRecipientsTo),\n schedule: row.maintenanceFreq !== \"None\",\n poc: isNonEmpty(row.pointOfContact),\n };\n const score = Object.values(checks).filter(Boolean).length;\n return { score, total: 4, checks };\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { siteSlug } from \"../reports/airtable/websites.js\";\nimport { onboardingStatus } from \"./onboarding.js\";\nimport { relativeTimeFromNow } from \"./relative-time.js\";\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction safeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (u.protocol === \"http:\" || u.protocol === \"https:\") return raw;\n } catch {\n // fall through\n }\n return \"#\";\n}\n\nconst DASH = \"—\";\n\nfunction scoreSpan(category: \"perf\" | \"a11y-lh\" | \"bp\" | \"seo\", value: number | null): string {\n const display = value === null ? DASH : String(value);\n return `<span class=\"score ${category}\">${escapeHtml(display)}</span>`;\n}\n\nfunction a11ySpan(value: number | null): string {\n const display = value === null ? DASH : String(value);\n return `<span class=\"metric a11y\">${escapeHtml(display)}</span>`;\n}\n\nfunction depsSpan(drifted: number | null, majorBehind: number | null): string {\n if (drifted === null || majorBehind === null) {\n return `<span class=\"metric deps\">${DASH}</span>`;\n }\n const display = drifted === 0 ? \"0\" : `${drifted} drifted (${majorBehind} major)`;\n return `<span class=\"metric deps\">${escapeHtml(display)}</span>`;\n}\n\nfunction securitySpan(\n critical: number | null,\n high: number | null,\n moderate: number | null,\n low: number | null,\n): string {\n if (critical === null || high === null || moderate === null || low === null) {\n return `<span class=\"metric sec\">${DASH}</span>`;\n }\n const total = critical + high + moderate + low;\n const display = total === 0 ? \"0\" : `${critical}C/${high}H/${moderate}M/${low}L`;\n return `<span class=\"metric sec\">${escapeHtml(display)}</span>`;\n}\n\nfunction card(site: WebsiteRow): string {\n const name = escapeHtml(site.name);\n // Caller is responsible for filtering — render assumes every site has a\n // dashboardToken. The `?? \"\"` is a defensive nudge if a misuse ever slips through.\n const token = site.dashboardToken ?? \"\";\n const href = `/s/${escapeHtml(siteSlug(site.name))}?t=${escapeHtml(token)}`;\n const onboarding = onboardingStatus(site);\n const audited = relativeTimeFromNow(site.lastLighthouseAuditAt);\n const safeSiteUrl = escapeHtml(safeUrl(site.url));\n const visibleUrl = escapeHtml(site.url);\n\n return `<article class=\"card\">\n <header class=\"card-head\">\n <a class=\"site\" href=\"${href}\">${name}</a>\n <a class=\"url\" href=\"${safeSiteUrl}\" target=\"_blank\" rel=\"noopener\">${visibleUrl}</a>\n <span class=\"setup\">Setup: <strong>${onboarding.score}/${onboarding.total}</strong></span>\n <span class=\"audited\">Audited: <strong>${escapeHtml(audited)}</strong></span>\n </header>\n <div class=\"card-metrics\">\n <span class=\"cluster lighthouse\">\n ${scoreSpan(\"perf\", site.pScore)}\n ${scoreSpan(\"a11y-lh\", site.rScore)}\n ${scoreSpan(\"bp\", site.bpScore)}\n ${scoreSpan(\"seo\", site.seoScore)}\n </span>\n <span class=\"cluster health\">\n <span class=\"metric-label\">a11y</span> ${a11ySpan(site.a11yViolations)}\n <span class=\"metric-label\">deps</span> ${depsSpan(site.depsDrifted, site.depsMajorBehind)}\n <span class=\"metric-label\">sec</span> ${securitySpan(\n site.securityVulnsCritical,\n site.securityVulnsHigh,\n site.securityVulnsModerate,\n site.securityVulnsLow,\n )}\n </span>\n </div>\n </article>`;\n}\n\nconst STYLES = `\n:root { color-scheme: light dark; }\nbody { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }\n@media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }\nh1 { margin: 0 0 0.25rem; font-size: 1.75rem; }\n.meta { color: #666; margin-bottom: 1.5rem; }\n.empty { color: #999; padding: 2rem; text-align: center; border: 1px dashed #ccc; border-radius: 6px; }\n.cards { display: flex; flex-direction: column; gap: 0.75rem; }\n.card { border: 1px solid #e5e5e5; border-radius: 8px; padding: 0.9rem 1.1rem; }\n@media (prefers-color-scheme: dark) { .card { border-color: #2a2a2a; background: #181818; } }\n.card-head { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: baseline; }\n.card-head .site { font-weight: 600; font-size: 1.05rem; }\n.card-head .url { color: #666; font-size: 0.85rem; }\n.card-head .setup, .card-head .audited { color: #666; font-size: 0.85rem; }\n.card-head .setup { margin-left: auto; }\n.card-metrics { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin-top: 0.5rem; font-variant-numeric: tabular-nums; }\n.cluster { display: inline-flex; gap: 0.5rem; align-items: baseline; }\n.cluster.lighthouse .score { display: inline-block; min-width: 2.25rem; text-align: right; }\n.metric-label { color: #999; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }\n.metric { font-feature-settings: \"tnum\"; }\n`;\n\n/**\n * Render the fleet homepage as a single HTML document. Pure function:\n * no Airtable access, no env reads, no I/O. The Netlify function handler\n * filters Websites rows (drops anything without a dashboardToken), sorts,\n * and hands here. One <article class=\"card\"> per site, with a header row\n * (name · url · setup · audited) and a metrics row (lighthouse · a11y · deps · sec).\n */\nexport function renderFleetHomeHtml(sites: WebsiteRow[]): string {\n const body =\n sites.length === 0\n ? `<div class=\"empty\">No sites to display.</div>`\n : `<div class=\"cards\">${sites.map(card).join(\"\")}</div>`;\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Reddoor maintenance — fleet</title>\n <style>${STYLES}</style>\n</head>\n<body>\n <h1>Reddoor fleet</h1>\n <div class=\"meta\">${sites.length} site${sites.length === 1 ? \"\" : \"s\"} on the Reddoor stack.</div>\n ${body}\n</body>\n</html>`;\n}\n","import { timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an `Authorization: Basic <base64>` header against the configured\n * dashboard password. Username is intentionally ignored — operators may\n * type anything when the browser prompts; only the password gates entry.\n *\n * Returns false for any of:\n * - missing/empty Authorization header\n * - non-Basic auth scheme\n * - malformed base64 or payload (no colon to split user:password)\n * - wrong password\n * - expected password missing (DASHBOARD_PASSWORD not configured)\n *\n * Wrong-password compare is constant-time; lengths are checked first\n * (timingSafeEqual throws on mismatch, and the length itself doesn't\n * leak — operator's password length is fixed per deploy).\n */\nexport function verifyBasicAuth(\n authHeader: string | null | undefined,\n expectedPassword: string | null,\n): boolean {\n if (!authHeader || !expectedPassword) return false;\n // RFC 7235: scheme is case-insensitive.\n const match = /^basic\\s+(.+)$/i.exec(authHeader.trim());\n if (!match) return false;\n let decoded: string;\n try {\n decoded = Buffer.from(match[1]!, \"base64\").toString(\"utf-8\");\n } catch {\n return false;\n }\n // Base64-decoding never throws in Node, but a payload of garbage may\n // produce a string with no colon. user:password form is required.\n const colonIdx = decoded.indexOf(\":\");\n if (colonIdx === -1) return false;\n const provided = decoded.slice(colonIdx + 1);\n if (provided.length !== expectedPassword.length) return false;\n return timingSafeEqual(Buffer.from(provided, \"utf-8\"), Buffer.from(expectedPassword, \"utf-8\"));\n}\n"],"mappings":"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","names":["readFile","join","join","eslint","readFile","spawn","readFile","join","readFile","join","readFile","join","spawn","readFile","writeFile","mkdtemp","rm","join","readJsonMaybe","readFile","spawn","mkdtemp","join","writeFile","rm","spawn","readFile","writeFile","join","readFile","join","writeFile","commit","join","spawn","join","commit","join","readFile","writeFile","join","join","readFile","writeFile","join","spawn","join","spawn","join","readFile","writeFile","join","glob","SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK","IGNORE","glob","join","readFile","writeFile","spawn","writeFile","join","join","spawn","commit","writeFile","join","commit","writeFile","join","rm","stat","join","exists","stat","spawn","join","commit","rm","stat","join","existsSync","dirname","join","exists","stat","spawn","join","commit","mkdir","writeFile","dirname","join","join","commit","mkdir","dirname","writeFile","readFile","mkdir","writeFile","dirname","readFile","existsSync","dirname","join","fileURLToPath","mapRow","dirname","join","readFileSync","join","join","dirname","readFileSync","ymd","readFileSync","JWT","ymd","mkdir","dirname","writeFile","MONTHS","escapeHtml","safeUrl","STYLES","timingSafeEqual"]}
1
+ {"version":3,"sources":["../src/audits/util/spawn.ts","../src/audits/deps.ts","../src/util/site.ts","../src/configs/baseline-versions.ts","../src/audits/lint.ts","../src/audits/security.ts","../src/audits/lighthouse.ts","../src/configs/lighthouse.ts","../src/audits/util/site-config.ts","../src/util/free-port.ts","../src/audits/a11y.ts","../src/configs/playwright-a11y.ts","../src/audits/index.ts","../src/recipes/sync-configs.ts","../src/recipes/sync-configs/templates.ts","../src/recipes/sync-configs/gitignore.ts","../src/util/git.ts","../src/recipes/_with-recipe.ts","../src/recipes/bump-deps.ts","../src/recipes/svelte-5/index.ts","../src/util/pkg.ts","../src/recipes/svelte-5/step-bump-versions.ts","../src/recipes/svelte-5/step-svelte-config.ts","../src/recipes/svelte-5/step-svelte-migrate.ts","../src/recipes/svelte-5/step-tailwind-upgrade.ts","../src/recipes/svelte-5/step-gotchas.ts","../src/recipes/svelte-5/codemods/on-event-to-handler.ts","../src/recipes/svelte-5/codemods/dollar-props.ts","../src/util/svelte-source.ts","../src/recipes/svelte-5/codemods/dollar-restprops.ts","../src/recipes/svelte-5/codemods/state-effect-sync.ts","../src/recipes/svelte-5/codemods/dollar-props-class.ts","../src/recipes/svelte-5/codemods/legacy-reactive.ts","../src/recipes/svelte-5/step-verify.ts","../src/recipes/svelte-5/step-summary.ts","../src/recipes/svelte-codemods.ts","../src/recipes/convert-to-pnpm.ts","../src/recipes/convert-to-pnpm/script-rewrites.ts","../src/recipes/onboard.ts","../src/util/self-version.ts","../src/recipes/a11y-fixtures-page/index.ts","../src/recipes/a11y-fixtures-page/template.ts","../src/recipes/init.ts","../src/recipes/index.ts","../src/inventory/local.ts","../src/inventory/json.ts","../src/reports/airtable/websites.ts","../src/inventory/airtable.ts","../src/reports/draft.ts","../src/reports/render.ts","../src/reports/maintenance-email/assets/index.ts","../src/reports/maintenance-email/template.ts","../src/reports/airtable/reports.ts","../src/reports/airtable/attachments.ts","../src/reports/ga/config.ts","../src/util/credentials.ts","../src/reports/ga/client.ts","../src/reports/search/client.ts","../src/reports/airtable/client.ts","../src/reports/maintenance-email/header-image.ts","../src/reports/send/resend.ts","../src/reports/send/orchestrate.ts","../src/reports/due.ts","../src/dashboard/relative-time.ts","../src/dashboard/render.ts","../src/dashboard/auth.ts","../src/dashboard/onboarding.ts","../src/dashboard/fleet-render.ts","../src/dashboard/basic-auth.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport type SpawnResult = { code: number; stdout: string; stderr: string };\n\nexport type SpawnOptions = {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n timeoutMs?: number;\n /** When true, the child inherits stdout/stderr so the user sees live\n * progress (useful for long-running `pnpm up` / `npm install`). The\n * returned `stdout` and `stderr` will be empty strings in that case. */\n streaming?: boolean;\n};\n\nexport type SpawnFn = (\n cmd: string,\n args: readonly string[],\n opts?: SpawnOptions,\n) => Promise<SpawnResult>;\n\nexport const defaultSpawn: SpawnFn = (cmd, args, opts = {}) =>\n new Promise((resolve, reject) => {\n const streaming = opts.streaming === true;\n const child = spawn(cmd, [...args], {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n stdio: streaming ? [\"ignore\", \"inherit\", \"inherit\"] : [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n if (!streaming) {\n child.stdout?.on(\"data\", (chunk) => (stdout += String(chunk)));\n child.stderr?.on(\"data\", (chunk) => (stderr += String(chunk)));\n }\n\n const timer = opts.timeoutMs\n ? setTimeout(() => {\n child.kill(\"SIGTERM\");\n reject(new Error(`spawn timeout after ${opts.timeoutMs}ms: ${cmd}`));\n }, opts.timeoutMs)\n : undefined;\n\n child.on(\"error\", (err) => {\n if (timer) clearTimeout(timer);\n reject(err);\n });\n child.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ code: code ?? -1, stdout, stderr });\n });\n });\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { baselineVersions } from \"../configs/baseline-versions.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\nexport type Drift = \"same\" | \"patch\" | \"minor\" | \"major\" | \"newer\";\n\nexport type DepsDriftEntry = {\n pkg: string;\n baseline: string;\n actual: string;\n drift: Drift;\n};\n\nfunction stripCaret(range: string): string {\n return range.replace(/^[\\^~]/, \"\");\n}\n\nfunction parseSemver(v: string): [number, number, number] {\n const cleaned = stripCaret(v).split(\"-\")[0] ?? \"0.0.0\";\n const parts = cleaned.split(\".\").map((n) => Number.parseInt(n, 10));\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nfunction compareSemver(actual: string, baseline: string): Drift {\n const [aMajor, aMinor, aPatch] = parseSemver(actual);\n const [bMajor, bMinor, bPatch] = parseSemver(baseline);\n if (aMajor > bMajor) return \"newer\";\n if (aMajor < bMajor) return \"major\";\n if (aMinor > bMinor) return \"newer\";\n if (aMinor < bMinor) return \"minor\";\n if (aPatch > bPatch) return \"newer\";\n if (aPatch < bPatch) return \"patch\";\n return \"same\";\n}\n\nexport async function depsAudit(ctx: AuditContext): Promise<AuditResult> {\n const pkgPath = join(ctx.site.path, \"package.json\");\n let pkgRaw: string;\n try {\n pkgRaw = await readFile(pkgPath, \"utf-8\");\n } catch (err) {\n return {\n audit: \"deps\",\n site: siteLabel(ctx.site),\n status: \"skip\",\n summary: `no package.json at ${pkgPath}`,\n details: { error: String(err) },\n };\n }\n\n const pkg = JSON.parse(pkgRaw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const installed: Record<string, string> = {\n ...(pkg.dependencies ?? {}),\n ...(pkg.devDependencies ?? {}),\n };\n\n const details: DepsDriftEntry[] = [];\n for (const [name, baseline] of Object.entries(baselineVersions)) {\n const actual = installed[name];\n if (!actual) continue;\n details.push({\n pkg: name,\n baseline,\n actual,\n drift: compareSemver(actual, baseline),\n });\n }\n\n const anyMajor = details.some((d) => d.drift === \"major\");\n const anyMinor = details.some((d) => d.drift === \"minor\");\n const anyNewer = details.some((d) => d.drift === \"newer\");\n\n const status: AuditResult[\"status\"] = anyMajor ? \"fail\" : anyMinor || anyNewer ? \"warn\" : \"pass\";\n\n const summary =\n status === \"pass\"\n ? `all ${details.length} tracked deps in line with baseline`\n : status === \"warn\"\n ? `${details.filter((d) => d.drift !== \"same\").length} of ${details.length} tracked deps drifted`\n : `${details.filter((d) => d.drift === \"major\").length} deps lagging by a major version`;\n\n return {\n audit: \"deps\",\n site: siteLabel(ctx.site),\n status,\n summary,\n details,\n };\n}\n","import type { Site } from \"../types.js\";\n\n/** Human-friendly label for log/output formatting. Prefer the inventory's\n * `name` when present (e.g. \"caltex-landing\") and fall back to the\n * filesystem `path` when unnamed. Every audit + recipe uses this. */\nexport function siteLabel(site: Site): string {\n return site.name ?? site.path;\n}\n","// Curated map of the framework deps reddoor sites should stay close to.\n// Refreshed at each package release from reddoor-starter's package.json.\n// Versions are caret ranges to mirror what `pnpm add` would produce.\n\nexport const baselineVersions: Record<string, string> = {\n // SvelteKit core\n svelte: \"^5.55.10\",\n \"@sveltejs/kit\": \"^2.61.1\",\n \"@sveltejs/adapter-netlify\": \"^6.0.4\",\n \"@sveltejs/adapter-auto\": \"^7.0.1\",\n \"@sveltejs/vite-plugin-svelte\": \"^7.1.2\",\n \"svelte-check\": \"^4.4.8\",\n\n // Build tooling\n vite: \"^8.0.14\",\n vitest: \"^4.1.7\",\n typescript: \"^6.0.3\",\n\n // Tailwind 4\n tailwindcss: \"^4.3.0\",\n \"@tailwindcss/vite\": \"^4.3.0\",\n\n // Prismic\n \"@prismicio/client\": \"^7.21.8\",\n \"@prismicio/svelte\": \"^2.2.1\",\n \"@slicemachine/adapter-sveltekit\": \"^0.3.96\",\n \"slice-machine-ui\": \"^2.21.3\",\n\n // Test tooling\n \"@playwright/test\": \"^1.60.0\",\n \"@axe-core/playwright\": \"^4.11.3\",\n \"@lhci/cli\": \"^0.15.1\",\n\n // Lint\n eslint: \"^10.4.0\",\n \"eslint-plugin-svelte\": \"^3.18.0\",\n \"eslint-config-prettier\": \"^10.1.8\",\n prettier: \"^3.8.3\",\n \"prettier-plugin-svelte\": \"^4.0.1\",\n \"typescript-eslint\": \"^8.60.0\",\n \"@eslint/js\": \"^10.0.1\",\n globals: \"^17.6.0\",\n\n // Misc\n \"@lucide/svelte\": \"^1.17.0\",\n \"@zerodevx/svelte-img\": \"^2.1.2\",\n};\n\nexport default baselineVersions;\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { ESLint } from \"eslint\";\nimport { check as prettierCheck, resolveConfig as prettierResolveConfig } from \"prettier\";\nimport { glob } from \"tinyglobby\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\nconst TARGET_GLOBS = [\"**/*.{ts,js,svelte}\"];\nconst IGNORE = [\"node_modules/**\", \"dist/**\", \".svelte-kit/**\", \"build/**\", \".netlify/**\"];\n\nasync function listFiles(cwd: string): Promise<string[]> {\n return glob(TARGET_GLOBS, { cwd, ignore: IGNORE, absolute: false });\n}\n\nexport async function lintAudit(ctx: AuditContext): Promise<AuditResult> {\n const { site } = ctx;\n const configPath = join(site.path, \"eslint.config.js\");\n\n if (!existsSync(configPath)) {\n return {\n audit: \"lint\",\n site: siteLabel(site),\n status: \"skip\",\n summary: \"no eslint config at site root\",\n };\n }\n\n const eslint = new ESLint({\n cwd: site.path,\n overrideConfigFile: configPath,\n errorOnUnmatchedPattern: false,\n });\n\n const relFiles = await listFiles(site.path);\n\n // Pass relative paths to ESLint; its cwd is already site.path. Avoids\n // dereferencing symlinks on pnpm workspaces.\n const eslintResults = await eslint.lintFiles(relFiles);\n const eslintErrors = eslintResults.reduce((n, r) => n + r.errorCount, 0);\n const eslintWarnings = eslintResults.reduce((n, r) => n + r.warningCount, 0);\n\n const prettierUnformatted: string[] = [];\n for (const rel of relFiles) {\n const absForResolve = join(site.path, rel);\n const source = await readFile(absForResolve, \"utf-8\");\n const options = (await prettierResolveConfig(absForResolve)) ?? {};\n const ok = await prettierCheck(source, { ...options, filepath: absForResolve });\n if (!ok) prettierUnformatted.push(rel);\n }\n\n const status: AuditResult[\"status\"] =\n eslintErrors > 0 || prettierUnformatted.length > 0\n ? \"fail\"\n : eslintWarnings > 0\n ? \"warn\"\n : \"pass\";\n\n const summary =\n status === \"pass\"\n ? `lint clean across ${relFiles.length} files`\n : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;\n\n return {\n audit: \"lint\",\n site: siteLabel(site),\n status,\n summary,\n details: {\n eslintErrors,\n eslintWarnings,\n prettierUnformatted,\n files: relFiles.length,\n },\n };\n}\n","import type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { defaultSpawn, type SpawnResult } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\n\ntype Severity = \"low\" | \"moderate\" | \"high\" | \"critical\";\n\ntype Counts = { low: number; moderate: number; high: number; critical: number };\n\ntype AdvisoryEntry = {\n module: string;\n severity: Severity;\n title: string;\n cves?: string[];\n url?: string;\n};\n\n// pnpm audit output (npm-compat with extra advisories map keyed by ID).\ntype PnpmAuditJson = {\n metadata?: { vulnerabilities?: Partial<Counts> };\n advisories?: Record<\n string,\n {\n id?: number;\n title?: string;\n module_name?: string;\n severity?: string;\n cves?: string[];\n url?: string;\n }\n >;\n};\n\n// npm v7+ shape (vulnerabilities keyed by package name).\ntype NpmAuditJson = {\n metadata?: { vulnerabilities?: Partial<Counts> };\n vulnerabilities?: Record<\n string,\n {\n name?: string;\n severity?: string;\n via?: unknown;\n url?: string;\n }\n >;\n};\n\nfunction classify(v: Counts) {\n if (v.critical > 0 || v.high > 0) return \"fail\" as const;\n if (v.moderate > 0 || v.low > 0) return \"warn\" as const;\n return \"pass\" as const;\n}\n\nfunction normalizeSeverity(s: unknown): Severity {\n if (s === \"low\" || s === \"moderate\" || s === \"high\" || s === \"critical\") return s;\n // npm/pnpm sometimes emit \"info\" for informational advisories. Map down\n // rather than defaulting to \"moderate\" (which would inflate severity).\n return \"low\";\n}\n\nfunction extractAdvisoriesFromPnpm(parsed: PnpmAuditJson): AdvisoryEntry[] {\n const out: AdvisoryEntry[] = [];\n for (const a of Object.values(parsed.advisories ?? {})) {\n if (!a) continue;\n out.push({\n module: a.module_name ?? \"unknown\",\n severity: normalizeSeverity(a.severity),\n title: a.title ?? \"(no title)\",\n ...(a.cves ? { cves: a.cves } : {}),\n ...(a.url ? { url: a.url } : {}),\n });\n }\n return out;\n}\n\n/** Walk an npm v7+ `via` chain to find the root entry whose `via` array\n * contains a real advisory object (rather than another package name string).\n * Returns the package name at the root and the advisory detail. */\nfunction resolveNpmAdvisoryRoot(\n startName: string,\n vulnerabilities: NonNullable<NpmAuditJson[\"vulnerabilities\"]>,\n): { rootName: string; detail?: { title?: string; url?: string } } {\n const seen = new Set<string>();\n let current = startName;\n while (!seen.has(current)) {\n seen.add(current);\n const entry = vulnerabilities[current];\n if (!entry || !Array.isArray(entry.via)) return { rootName: current };\n\n const detailed = entry.via.find(\n (e): e is { title?: string; url?: string } => typeof e === \"object\" && e !== null,\n );\n if (detailed) return { rootName: current, detail: detailed };\n\n const next = entry.via.find((e): e is string => typeof e === \"string\");\n if (!next || next === current) return { rootName: current };\n current = next;\n }\n return { rootName: current };\n}\n\nfunction extractAdvisoriesFromNpm(parsed: NpmAuditJson): AdvisoryEntry[] {\n const vulnerabilities = parsed.vulnerabilities ?? {};\n const roots = new Map<string, AdvisoryEntry>();\n\n for (const [name, v] of Object.entries(vulnerabilities)) {\n if (!v) continue;\n const { rootName, detail } = resolveNpmAdvisoryRoot(name, vulnerabilities);\n if (roots.has(rootName)) continue; // already surfaced via another transitive entry\n\n const rootEntry = vulnerabilities[rootName];\n const severity = normalizeSeverity(rootEntry?.severity ?? v.severity);\n const title = detail?.title ?? rootName;\n const url = detail?.url;\n\n roots.set(rootName, {\n module: rootEntry?.name ?? rootName,\n severity,\n title,\n ...(url ? { url } : {}),\n });\n }\n\n return [...roots.values()];\n}\n\ntype ToolResult =\n | { kind: \"missing\" }\n | { kind: \"error\"; reason: string }\n | { kind: \"ok\"; parsed: PnpmAuditJson & NpmAuditJson };\n\nasync function runAuditTool(\n spawn: (cmd: string, args: readonly string[], opts?: { cwd?: string }) => Promise<SpawnResult>,\n cmd: string,\n args: readonly string[],\n cwd: string,\n): Promise<ToolResult> {\n let raw: SpawnResult;\n try {\n raw = await spawn(cmd, args, { cwd });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) return { kind: \"missing\" };\n return { kind: \"error\", reason: `spawn failed: ${String(err).slice(0, 200)}` };\n }\n\n // 0 = clean, 1 = vulns found. Anything else is a real error.\n if (raw.code !== 0 && raw.code !== 1) {\n return {\n kind: \"error\",\n reason: `exit ${raw.code}${raw.stderr ? `: ${raw.stderr.slice(0, 150)}` : \"\"}`,\n };\n }\n\n let parsed: PnpmAuditJson & NpmAuditJson;\n try {\n parsed = JSON.parse(raw.stdout || \"{}\") as PnpmAuditJson & NpmAuditJson;\n } catch (err) {\n return { kind: \"error\", reason: `unparseable JSON: ${String(err).slice(0, 100)}` };\n }\n\n // pnpm error envelope: { error: { code, message } }. npm sometimes emits\n // a top-level error too. Either means the audit didn't actually run.\n const errEnvelope = (parsed as unknown as { error?: { code?: string } }).error;\n if (errEnvelope && typeof errEnvelope === \"object\") {\n return { kind: \"error\", reason: errEnvelope.code ?? \"error envelope returned\" };\n }\n\n // Without metadata.vulnerabilities there are no counts to report and we\n // can't trust the result. An empty `{}` is just as suspect as a missing\n // key — counts default to 0 and we'd silently report \"pass\". Treat both\n // as a tool failure so the caller can fall through to the other audit.\n const vulnsMeta = parsed.metadata?.vulnerabilities;\n if (!vulnsMeta || Object.keys(vulnsMeta).length === 0) {\n return { kind: \"error\", reason: \"no metadata.vulnerabilities in output\" };\n }\n\n return { kind: \"ok\", parsed };\n}\n\nexport async function securityAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n let used: \"pnpm audit\" | \"npm audit\" = \"pnpm audit\";\n let result = await runAuditTool(spawn, \"pnpm\", [\"audit\", \"--json\", \"--prod\"], site.path);\n\n // Fall through to npm if pnpm is missing OR pnpm couldn't actually\n // audit the project (e.g., no pnpm-lock.yaml). Previously we only fell\n // through on ENOENT, which meant npm-using sites silently reported \"pass\"\n // because pnpm returned an error envelope with no metadata.\n if (result.kind !== \"ok\") {\n const pnpmReason = result.kind === \"missing\" ? \"not installed\" : result.reason;\n const npmResult = await runAuditTool(\n spawn,\n \"npm\",\n [\"audit\", \"--json\", \"--omit=dev\"],\n site.path,\n );\n if (npmResult.kind === \"ok\") {\n result = npmResult;\n used = \"npm audit\";\n } else {\n const npmReason = npmResult.kind === \"missing\" ? \"not installed\" : npmResult.reason;\n return {\n audit: \"security\",\n site: label,\n status: \"skip\",\n summary: `cannot run audit — pnpm: ${pnpmReason}; npm: ${npmReason}`,\n };\n }\n }\n\n const parsed = result.parsed;\n\n const counts: Counts = {\n low: parsed.metadata?.vulnerabilities?.low ?? 0,\n moderate: parsed.metadata?.vulnerabilities?.moderate ?? 0,\n high: parsed.metadata?.vulnerabilities?.high ?? 0,\n critical: parsed.metadata?.vulnerabilities?.critical ?? 0,\n };\n\n const advisories =\n used === \"pnpm audit\" ? extractAdvisoriesFromPnpm(parsed) : extractAdvisoriesFromNpm(parsed);\n\n const status = classify(counts);\n const total = counts.low + counts.moderate + counts.high + counts.critical;\n const summary =\n status === \"pass\"\n ? `${used}: 0 vulnerabilities`\n : `${used}: ${total} vulnerabilities (${counts.critical}C/${counts.high}H/${counts.moderate}M/${counts.low}L)`;\n\n return {\n audit: \"security\",\n site: label,\n status,\n summary,\n details: { counts, advisories },\n };\n}\n","import { readFile, writeFile, mkdtemp, rm, readdir } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { lighthouseConfig } from \"../configs/lighthouse.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { readSiteConfig } from \"./util/site-config.js\";\nimport { findFreePort, withFreePort } from \"../util/free-port.js\";\n\ntype ManifestEntry = {\n url: string;\n summary: Record<string, number>;\n htmlPath?: string;\n jsonPath?: string;\n};\n\ntype AssertionResult = {\n name: string;\n actual: number;\n expected: number;\n operator: string;\n passed: boolean;\n level: \"warn\" | \"error\";\n auditProperty?: string;\n auditId?: string;\n};\n\ntype NormalizedLhciResult = {\n summary: Record<string, number>;\n assertionsFailed: number;\n assertions: Array<{ category: string; level: \"warn\" | \"error\"; message: string }>;\n};\n\nasync function readJsonMaybe<T>(path: string): Promise<T | null> {\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\ntype LhrFile = {\n requestedUrl: string;\n finalUrl?: string;\n categories: Record<string, { score: number | null }>;\n};\n\n/**\n * Build manifest-equivalent entries by scanning the `.lighthouseci/` dir\n * for `lhr-*.json` files written by `lhci collect`. We used to read\n * `manifest.json` directly, but lhci 0.15+ no longer writes it — the\n * audit would silently return \"no manifest written\" against a perfectly\n * healthy run. Reproduced on caltex 2026-05-28 (0.10.5 dogfood).\n */\nasync function readLhrEntries(resultsDir: string): Promise<ManifestEntry[]> {\n const files = await readdir(resultsDir).catch(() => [] as string[]);\n const entries: ManifestEntry[] = [];\n for (const f of files) {\n if (!f.startsWith(\"lhr-\") || !f.endsWith(\".json\")) continue;\n const lhr = await readJsonMaybe<LhrFile>(join(resultsDir, f));\n if (!lhr || !lhr.categories) continue;\n const summary: Record<string, number> = {};\n for (const [k, v] of Object.entries(lhr.categories)) {\n if (typeof v?.score === \"number\") summary[k] = v.score;\n }\n entries.push({ url: lhr.requestedUrl, summary });\n }\n return entries;\n}\n\nfunction averageSummaries(entries: ManifestEntry[]): Record<string, number> {\n if (entries.length === 0) return {};\n const sums: Record<string, number> = {};\n const counts: Record<string, number> = {};\n for (const e of entries) {\n for (const [k, v] of Object.entries(e.summary ?? {})) {\n if (typeof v !== \"number\") continue;\n sums[k] = (sums[k] ?? 0) + v;\n counts[k] = (counts[k] ?? 0) + 1;\n }\n }\n const out: Record<string, number> = {};\n for (const k of Object.keys(sums)) {\n const total = sums[k] ?? 0;\n const count = counts[k] ?? 1;\n out[k] = total / count;\n }\n return out;\n}\n\nfunction categoryFromAssertion(a: AssertionResult): string {\n // `name` looks like \"categories:accessibility\" or \"audits:uses-http2\".\n const colonIdx = a.name.indexOf(\":\");\n return colonIdx >= 0 ? a.name.slice(colonIdx + 1) : a.name;\n}\n\nfunction messageForAssertion(a: AssertionResult): string {\n return `${a.name} ${a.operator} ${a.expected} (actual: ${a.actual.toFixed(2)})`;\n}\n\nexport async function lighthouseAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n const siteCfg = await readSiteConfig(site.path);\n // Allocate a free port + force vite to `--strictPort` so the spawned dev\n // server either binds the port we picked or fails loudly. Without this,\n // a zombie on 5173 makes vite bump to 5174 while lhci still probes 5173\n // and audits the wrong server (silently returns \"no manifest written\").\n const port = await findFreePort();\n const baseUrl = siteCfg.lighthouseUrl ?? lighthouseConfig.ci.collect.url[0];\n const resolvedConfig = {\n ...lighthouseConfig,\n ci: {\n ...lighthouseConfig.ci,\n collect: {\n ...lighthouseConfig.ci.collect,\n url: [withFreePort(baseUrl, port)],\n startServerCommand: `npm run vite:dev -- --port ${port} --strictPort`,\n },\n },\n };\n\n const configDir = await mkdtemp(join(tmpdir(), \"reddoor-lhci-\"));\n const configPath = join(configDir, \"lighthouserc.json\");\n await writeFile(configPath, JSON.stringify(resolvedConfig), \"utf-8\");\n\n const resultsDir = join(site.path, \".lighthouseci\");\n // Clear any stale artifacts before the run so we never confuse a failed\n // spawn with old results.\n await rm(resultsDir, { recursive: true, force: true });\n\n let raw;\n try {\n raw = await spawn(\"npx\", [\"--yes\", \"@lhci/cli\", \"autorun\", `--config=${configPath}`], {\n cwd: site.path,\n // lhci autorun boots the site's dev server, downloads Chrome on first\n // use, and runs the audit — easily 2–3 min on a cold tree. The shared\n // 30 s default in runAudits is fine for deps/lint/security but starves\n // lhci.\n timeoutMs: 5 * 60_000,\n });\n } catch (err) {\n await rm(configDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"skip\",\n summary: \"npx/@lhci/cli not available\",\n };\n }\n throw err;\n }\n await rm(configDir, { recursive: true, force: true });\n\n const manifest = await readLhrEntries(resultsDir);\n\n if (manifest.length === 0) {\n return {\n audit: \"lighthouse\",\n site: label,\n status: \"fail\",\n summary: `lighthouse: no lhr-*.json written (exit ${raw.code})${\n raw.stderr ? ` — ${raw.stderr.slice(0, 200)}` : \"\"\n }`,\n };\n }\n\n const assertionResults =\n (await readJsonMaybe<AssertionResult[]>(join(resultsDir, \"assertion-results.json\"))) ?? [];\n\n const failed = assertionResults.filter((a) => !a.passed);\n const assertions = failed.map((a) => ({\n category: categoryFromAssertion(a),\n level: a.level,\n message: messageForAssertion(a),\n }));\n\n const anyError = assertions.some((a) => a.level === \"error\");\n const anyWarn = assertions.some((a) => a.level === \"warn\");\n const status: AuditResult[\"status\"] = anyError ? \"fail\" : anyWarn ? \"warn\" : \"pass\";\n\n const normalized: NormalizedLhciResult = {\n summary: averageSummaries(manifest),\n assertionsFailed: failed.length,\n assertions,\n };\n\n const summary =\n status === \"pass\"\n ? \"lighthouse: all categories passing\"\n : `lighthouse: ${failed.length} assertion(s) failed`;\n\n return {\n audit: \"lighthouse\",\n site: label,\n status,\n summary,\n details: normalized,\n };\n}\n","export const lighthouseConfig = {\n ci: {\n collect: {\n url: [\"http://localhost:5173/dev/a11y-fixtures\"],\n // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects\n // the `run` form too. Keeps this config portable across the fleet\n // while sites transition to pnpm.\n startServerCommand: \"npm run vite:dev\",\n startServerReadyPattern: \"ready in\",\n startServerReadyTimeout: 120_000,\n numberOfRuns: 1,\n settings: {\n preset: \"desktop\",\n skipAudits: [\"uses-http2\"],\n },\n },\n assert: {\n assertions: {\n \"categories:accessibility\": [\"error\", { minScore: 0.95 }],\n \"categories:best-practices\": [\"error\", { minScore: 0.9 }],\n \"categories:seo\": [\"error\", { minScore: 0.9 }],\n \"categories:performance\": [\"warn\", { minScore: 0.7 }],\n },\n },\n upload: {\n target: \"temporary-public-storage\",\n },\n },\n} as const;\n\nexport default lighthouseConfig;\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type SiteConfig = {\n /** Override URL the lighthouse audit hits. Sites without the default\n * `/dev/a11y-fixtures` dev route set this to their homepage. */\n lighthouseUrl?: string;\n};\n\n/**\n * Read per-site overrides from `package.json#reddoor`. Returns `{}` on any\n * failure (missing file, malformed JSON, missing key, wrong type) so every\n * caller can safely fall back to its built-in default. Never throws.\n */\nexport async function readSiteConfig(sitePath: string): Promise<SiteConfig> {\n let raw: string;\n try {\n raw = await readFile(join(sitePath, \"package.json\"), \"utf-8\");\n } catch {\n return {};\n }\n let pkg: unknown;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return {};\n }\n if (!pkg || typeof pkg !== \"object\") return {};\n const cfg = (pkg as { reddoor?: unknown }).reddoor;\n if (!cfg || typeof cfg !== \"object\") return {};\n\n const out: SiteConfig = {};\n const url = (cfg as { lighthouseUrl?: unknown }).lighthouseUrl;\n if (typeof url === \"string\" && url.length > 0) {\n out.lighthouseUrl = url;\n }\n return out;\n}\n","import { createServer } from \"node:net\";\n\n/**\n * Bind an ephemeral TCP port, capture it, release it, and return it. Used\n * by the lighthouse and a11y audits to pick a port the audit's own dev\n * server will then bind via `--strictPort`.\n *\n * Why: vite's default behavior on a busy port is to bump to the next free\n * one (5173 → 5174 → …). When zombie vite processes (or any squatter) are\n * already on 5173, the audit's spawned vite lands on a higher port, but\n * the audit tooling (lhci, playwright) still probes 5173 — hits the\n * zombie — gets stale 404s — fails with \"no manifest written\" / \"no\n * results written (exit 1)\". Reproduced on caltex 2026-05-28 with 10\n * orphaned vite processes accumulated across this repo, the reports repo,\n * and caltex itself. Allocating a free port up front + `--strictPort`\n * makes the audit immune to port collisions.\n *\n * TOCTOU note: the small window between close() and the spawned vite\n * binding is theoretically racy, but in practice we run one audit at a\n * time and the OS keeps the port free for re-use. If vite still fails to\n * bind under `--strictPort`, the audit fails loudly — that's the correct\n * outcome (vs. silently auditing the wrong server).\n */\nexport async function findFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.unref();\n server.on(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const addr = server.address();\n if (typeof addr === \"object\" && addr) {\n const port = addr.port;\n server.close(() => resolve(port));\n } else {\n server.close();\n reject(new Error(\"findFreePort: could not determine assigned port from socket\"));\n }\n });\n });\n}\n\n/**\n * Swap the port (and force `localhost` host) on a URL so it points at the\n * audit's freshly-allocated dev server. Preserves the path + any query.\n * Used to rewrite the lighthouse `url` so lhci probes the correct port.\n */\nexport function withFreePort(url: string, port: number): string {\n const u = new URL(url);\n u.hostname = \"localhost\";\n u.port = String(port);\n return u.toString();\n}\n","import { readFile, writeFile, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AuditResult } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { a11yRoutes } from \"../configs/playwright-a11y.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { findFreePort } from \"../util/free-port.js\";\n\ntype Impact = \"minor\" | \"moderate\" | \"serious\" | \"critical\";\n\ntype AxeViolation = {\n id: string;\n impact: Impact;\n route: string;\n help?: string;\n helpUrl?: string;\n nodes?: Array<{ html?: string; target?: string[] }>;\n};\n\ntype NormalizedA11y = {\n totalViolations: number;\n byImpact: Partial<Record<Impact, number>>;\n violations: AxeViolation[];\n};\n\nconst RESULTS_REL = \".reddoor-a11y/results.json\";\n\nasync function readJsonMaybe<T>(path: string): Promise<T | null> {\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\n// The audit-controlled playwright config. We synthesize it (rather than\n// rely on the site's playwright.config.ts) so we can pin the dev server\n// port + force `--strictPort` — same fix as the lighthouse audit, same\n// reason (zombie vite processes squatting on 5173 would otherwise eat\n// the audit's request and return stale 404s).\nfunction buildPlaywrightConfig(port: number, sitePath: string): string {\n return `import { defineConfig } from \"@playwright/test\";\n\nexport default defineConfig({\n testDir: \".\",\n testMatch: /.*\\\\.spec\\\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: \"http://localhost:${port}\",\n trace: \"on-first-retry\",\n },\n webServer: {\n // --strictPort: refuse to bump to a different port if ours is taken,\n // so the audit fails loudly instead of probing a zombie.\n // reuseExistingServer:false: never reuse — we control the lifecycle.\n // cwd: playwright's default webServer.cwd is the config file's\n // directory. Our config lives in /tmp so without this override,\n // \"npm run vite:dev\" tries to read /tmp/.../package.json and\n // ENOENTs before vite ever starts. Caltex 2026-05-28 (0.10.5).\n command: \"npm run vite:dev -- --port ${port} --strictPort\",\n url: \"http://localhost:${port}/dev/a11y-fixtures\",\n cwd: ${JSON.stringify(sitePath)},\n reuseExistingServer: false,\n timeout: 120_000,\n },\n});\n`;\n}\n\n// The spec the audit writes runs all configured routes through axe in a single\n// test (so worker isolation doesn't fragment the collected violations) and\n// writes the structured result to <cwd>/.reddoor-a11y/results.json before\n// asserting. That way, the audit can read real axe details even when the\n// expect(...).toEqual([]) assertion fails.\nfunction buildSpec(): string {\n return `import { test, expect } from \"@playwright/test\";\nimport AxeBuilder from \"@axe-core/playwright\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nconst pages = ${JSON.stringify(a11yRoutes)};\nconst OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;\n\n// Playwright's default per-test timeout is 30s. We loop through every\n// configured route in a single test, so the budget needs to scale.\ntest.setTimeout(5 * 60_000);\n\ntest(\"a11y across configured routes\", async ({ page }) => {\n const violations = [];\n for (const { path, name } of pages) {\n await page.goto(path);\n const results = await new AxeBuilder({ page })\n .withTags([\"wcag2a\",\"wcag2aa\",\"wcag21a\",\"wcag21aa\",\"wcag22aa\"])\n .analyze();\n for (const v of results.violations) {\n violations.push({\n id: v.id,\n impact: v.impact ?? \"moderate\",\n route: name,\n help: v.help,\n helpUrl: v.helpUrl,\n nodes: v.nodes.map((n) => ({ html: n.html, target: n.target })),\n });\n }\n }\n const byImpact = {};\n for (const v of violations) {\n byImpact[v.impact] = (byImpact[v.impact] ?? 0) + 1;\n }\n if (OUTPUT) {\n await mkdir(dirname(OUTPUT), { recursive: true });\n await writeFile(\n OUTPUT,\n JSON.stringify({ totalViolations: violations.length, byImpact, violations }, null, 2),\n );\n }\n expect(violations).toEqual([]);\n});\n`;\n}\n\nexport async function a11yAudit(ctx: AuditContext): Promise<AuditResult> {\n const spawn = ctx.spawn ?? defaultSpawn;\n const site = ctx.site;\n const label = siteLabel(site);\n\n // specDir lives INSIDE site.path (not /tmp) so the spec's\n // `import AxeBuilder from \"@axe-core/playwright\"` resolves via Node's\n // walk-up — the site's node_modules is the nearest one. A spec written\n // to /tmp ENOENTs at module resolution before any test runs. Caltex\n // 2026-05-28 (0.10.6 dogfood), third layer of the same class as the\n // webServer.cwd bug.\n const specDir = await mkdtemp(join(site.path, \".reddoor-a11y-spec-\"));\n const specPath = join(specDir, \"a11y.spec.ts\");\n await writeFile(specPath, buildSpec(), \"utf-8\");\n\n const port = await findFreePort();\n const configPath = join(specDir, \"playwright.config.ts\");\n await writeFile(configPath, buildPlaywrightConfig(port, site.path), \"utf-8\");\n\n const resultsPath = join(site.path, RESULTS_REL);\n // Clear stale artifacts so a failed spawn never reports old data.\n await rm(join(site.path, \".reddoor-a11y\"), { recursive: true, force: true });\n\n let raw;\n try {\n raw = await spawn(\n \"npx\",\n [\"--yes\", \"playwright\", \"test\", `--config=${configPath}`, \"--reporter=line\", specPath],\n {\n cwd: site.path,\n env: { ...process.env, REDDOOR_A11Y_OUTPUT: resultsPath },\n // playwright on a cold tree downloads Chrome, boots the site's dev\n // server, and runs axe over every configured route. The shared 30 s\n // default in runAudits is fine for deps/lint/security but starves\n // playwright (mirrors the lighthouse fix shipped earlier).\n timeoutMs: 5 * 60_000,\n },\n );\n } catch (err) {\n await rm(specDir, { recursive: true, force: true });\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return {\n audit: \"a11y\",\n site: label,\n status: \"skip\",\n summary: \"npx/playwright not available\",\n };\n }\n throw err;\n }\n await rm(specDir, { recursive: true, force: true });\n\n const artifact = await readJsonMaybe<NormalizedA11y>(resultsPath);\n\n if (!artifact) {\n return {\n audit: \"a11y\",\n site: label,\n status: \"fail\",\n summary: `a11y: no results written (exit ${raw.code})${\n raw.stderr ? ` — ${raw.stderr.slice(0, 200)}` : \"\"\n }`,\n };\n }\n\n const hasSerious = (artifact.byImpact.serious ?? 0) > 0 || (artifact.byImpact.critical ?? 0) > 0;\n const hasAny = artifact.totalViolations > 0;\n\n const status: AuditResult[\"status\"] = hasSerious ? \"fail\" : hasAny ? \"warn\" : \"pass\";\n const summary =\n status === \"pass\"\n ? `a11y: 0 violations across ${a11yRoutes.length} routes`\n : `a11y: ${artifact.totalViolations} violations`;\n\n return {\n audit: \"a11y\",\n site: label,\n status,\n summary,\n details: artifact,\n };\n}\n","import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\nexport const playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: \"http://localhost:5173\",\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n command: \"npm run vite:dev\",\n url: \"http://localhost:5173/dev/a11y-fixtures\",\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n","import type { AuditName, AuditResult, Site } from \"../types.js\";\nimport type { AuditContext } from \"./util/inject.js\";\nimport { defaultSpawn } from \"./util/spawn.js\";\nimport type { SpawnFn } from \"./util/spawn.js\";\nimport { depsAudit } from \"./deps.js\";\nimport { lintAudit } from \"./lint.js\";\nimport { securityAudit } from \"./security.js\";\nimport { lighthouseAudit } from \"./lighthouse.js\";\nimport { a11yAudit } from \"./a11y.js\";\n\nconst REGISTRY: Record<AuditName, (ctx: AuditContext) => Promise<AuditResult>> = {\n deps: depsAudit,\n lint: lintAudit,\n security: securityAudit,\n lighthouse: lighthouseAudit,\n a11y: a11yAudit,\n};\n\nexport const ALL_AUDIT_NAMES = Object.keys(REGISTRY) as AuditName[];\n\n/** Default per-audit spawn timeout when running via runAudits (30 s). */\nconst DEFAULT_AUDIT_TIMEOUT_MS = 30_000;\n\nfunction timedSpawn(timeoutMs: number): SpawnFn {\n return (cmd, args, opts = {}) =>\n defaultSpawn(cmd, args, { ...opts, timeoutMs: opts.timeoutMs ?? timeoutMs });\n}\n\n/** Single-audit runner with the same error-to-result conversion that\n * `runAudits` applies. Exposed so the CLI can wrap each audit in its\n * own progress task (listr2) and surface per-audit completion timing,\n * while keeping audit implementations UI-free. */\nexport async function runOneAudit(site: Site, name: AuditName): Promise<AuditResult> {\n if (!(name in REGISTRY)) throw new Error(`unknown audit: ${name}`);\n const spawn = timedSpawn(DEFAULT_AUDIT_TIMEOUT_MS);\n const label = site.name ?? site.path;\n try {\n return await REGISTRY[name]({ site, spawn });\n } catch (err) {\n return {\n audit: name,\n site: label,\n status: \"fail\",\n summary: `${name}: unexpected error — ${String(err)}`,\n };\n }\n}\n\nexport async function runAudits(site: Site, which?: AuditName[]): Promise<AuditResult[]> {\n const names = which ?? ALL_AUDIT_NAMES;\n for (const n of names) {\n if (!(n in REGISTRY)) throw new Error(`unknown audit: ${n}`);\n }\n return Promise.all(names.map((n) => runOneAudit(site, n)));\n}\n\nexport async function runAuditsAcross(sites: Site[], which?: AuditName[]): Promise<AuditResult[]> {\n const all = await Promise.all(sites.map((s) => runAudits(s, which)));\n return all.flat();\n}\n\nexport { depsAudit, lintAudit, securityAudit, lighthouseAudit, a11yAudit };\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport { listTrackedFiles, removeFromIndex } from \"../util/git.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\nconst SVELTE_CONFIG: ConfigName = \"svelte\";\n\n/** A site's `svelte.config.js` is \"compliant\" — and left untouched by sync —\n * once it builds on the canonical helpers (createSvelteConfig + adapter-netlify).\n *\n * Unlike the other exact-match templates, svelte.config legitimately carries\n * site-specific `kit.alias` and `compilerOptions`; an exact overwrite would\n * clobber those on every sync (it silently dropped MSOT's $utils alias,\n * 2026-06-04). So once a config is on the canonical pattern we preserve it as-is\n * and only rewrite a genuinely off-pattern (or missing) config. The canonical\n * `createSvelteConfig` is unopinionated about aliases, so additive customization\n * is safe to keep. */\nfunction isSvelteConfigCompliant(contents: string): boolean {\n return contents.includes(\"createSvelteConfig\") && contents.includes(\"@sveltejs/adapter-netlify\");\n}\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"prettier-ignore\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n \"ci\",\n \"renovate-action\",\n \"renovate-config\",\n \"netlify\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing === t.contents) continue;\n // svelte.config is compliance-checked, not exact-matched: an existing config\n // already on the canonical pattern is left alone so its aliases/compilerOptions\n // survive. A missing (null) or off-pattern config still gets the canonical template.\n if (t.config === SVELTE_CONFIG && existing !== null && isSvelteConfigCompliant(existing)) {\n continue;\n }\n diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n | { kind: \"noop\" }\n | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n return withRecipe({\n name: \"sync-configs\",\n site,\n plan: async () => {\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return { kind: \"noop\", notes: \"all targeted configs already match\" };\n }\n return { kind: \"apply\", plan: { templateDiffs, gitignorePlan } };\n },\n apply: async ({ templateDiffs, gitignorePlan }, { commit }) => {\n for (const t of templateDiffs) {\n const dest = join(site.path, t.path);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, t.contents, \"utf-8\");\n await commit(`chore: sync ${t.config} config from @reddoorla/maintenance`);\n }\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n await commit(`chore: sync gitignore from @reddoorla/maintenance`);\n }\n return { kind: \"ok\" };\n },\n });\n}\n","import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst prettierIgnore: ConfigTemplate = {\n config: \"prettier-ignore\",\n path: \".prettierignore\",\n contents: `pnpm-lock.yaml\n.svelte-kit/\nbuild/\n.netlify/\ndist/\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-netlify\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter({ edge: false, split: false }) },\n});\n`,\n};\n\nconst ci: ConfigTemplate = {\n config: \"ci\",\n path: \".github/workflows/ci.yml\",\n contents: `name: ci\non:\n pull_request:\n push:\n branches: [main]\npermissions:\n contents: read\njobs:\n ci:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n cache: pnpm\n - run: pnpm install --frozen-lockfile\n - run: pnpm exec prettier --check .\n - run: pnpm exec eslint .\n - run: pnpm exec svelte-kit sync && pnpm exec svelte-check --tsconfig ./tsconfig.json\n - run: pnpm build\n - run: pnpm exec playwright install --with-deps chromium\n - run: pnpm exec reddoor-maint audit --only a11y --fail-on-violations\n - name: Test (if present)\n run: |\n if node -e \"process.exit(require('./package.json').scripts?.test ? 0 : 1)\"; then\n pnpm test\n else\n echo \"no test script — skipping\"\n fi\n`,\n};\n\nconst renovateAction: ConfigTemplate = {\n config: \"renovate-action\",\n path: \".github/workflows/renovate.yml\",\n contents: `name: renovate\non:\n schedule:\n - cron: \"0 7 * * 1\"\n workflow_dispatch:\njobs:\n renovate:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: renovatebot/github-action@v40\n with:\n token: \\${{ secrets.RENOVATE_TOKEN }}\n env:\n RENOVATE_REPOSITORIES: \\${{ github.repository }}\n`,\n};\n\nconst renovateConfig: ConfigTemplate = {\n config: \"renovate-config\",\n path: \"renovate.json\",\n contents: `{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"config:recommended\"],\n \"schedule\": [\"before 7am on monday\"],\n \"packageRules\": [\n { \"matchUpdateTypes\": [\"patch\", \"minor\"], \"automerge\": true, \"platformAutomerge\": true },\n { \"matchUpdateTypes\": [\"major\"], \"automerge\": false }\n ]\n}\n`,\n};\n\nconst netlify: ConfigTemplate = {\n config: \"netlify\",\n path: \"netlify.toml\",\n contents: `[build]\n command = \"pnpm build\"\n publish = \"build/\"\n functions = \"functions/\"\n\n[build.environment]\n NODE_VERSION = \"22\"\n COREPACK_INTEGRITY_KEYS = \"0\"\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n prettierIgnore,\n lighthouse,\n playwrightA11y,\n svelte,\n ci,\n renovateAction,\n renovateConfig,\n netlify,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n \".reddoor-a11y/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n\n/** Derive `owner/repo` from a git remote URL (https or scp-style). Null if unparseable. */\nexport function parseOwnerRepo(remoteUrl: string): string | null {\n const trimmed = remoteUrl\n .trim()\n .replace(/\\.git$/, \"\")\n .replace(/\\/$/, \"\");\n // scp-style: git@github.com:owner/repo\n const scp = trimmed.match(/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:(.+)$/);\n const path = scp ? scp[1]! : trimmed.replace(/^https?:\\/\\/[^/]+\\//, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n if (segments.length < 2) return null;\n return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;\n}\n\n/** `origin` remote URL for a checkout, trimmed. Throws (via git) if there's no origin. */\nexport async function getRemoteUrl(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"remote\", \"get-url\", \"origin\"]);\n return stdout.trim();\n}\n\n/** Push a branch to origin, setting upstream. Throws on non-zero (execFile rejects). */\nexport async function push(cwd: string, branch: string): Promise<void> {\n await git(cwd, [\"push\", \"-u\", \"origin\", branch]);\n}\n","import type { RecipeName, RecipeResult, Site } from \"../types.js\";\nimport { branchName, commit as gitCommit, createBranch, isWorkingTreeClean } from \"../util/git.js\";\nimport { siteLabel } from \"../util/site.js\";\n\n/** Outcome of the read-only planning phase. `noop` and `failed` short-circuit\n * without creating a branch; `apply` carries the recipe-specific plan data\n * forward to the apply phase. */\nexport type RecipePlan<P> =\n | { kind: \"noop\"; notes?: string }\n | { kind: \"failed\"; notes: string }\n | { kind: \"apply\"; plan: P };\n\nexport type RecipeApplyCtx = {\n /** Stage all current changes and commit. Returns the SHA, or null if\n * nothing was staged. The wrapper accumulates SHAs into the final\n * RecipeResult. */\n commit: (message: string) => Promise<string | null>;\n /** Branch name that was created for this run. */\n branch: string;\n /** Site path — same as `site.path`. */\n cwd: string;\n};\n\nexport type RecipeApplyResult = { kind: \"ok\"; notes?: string } | { kind: \"failed\"; notes: string };\n\nexport type RecipeBody<P> = {\n name: RecipeName;\n site: Site;\n /** Inspect the site and decide: noop, failed, or proceed (with plan data\n * passed to apply). Runs before the working-tree clean check unless\n * `checkTreeFirst: true` is set, so most recipes can noop on a dirty\n * tree without throwing. */\n plan: () => Promise<RecipePlan<P>>;\n /** Make the actual changes. Use `ctx.commit(msg)` for each logical step;\n * the wrapper collects SHAs into `RecipeResult.commits`. Return\n * `{ kind: \"failed\", notes }` to abort partway and surface the failure. */\n apply: (plan: P, ctx: RecipeApplyCtx) => Promise<RecipeApplyResult>;\n /** Check working tree clean BEFORE `plan()` runs. Use only when plan\n * itself mutates the tree (e.g. `bump-deps` runs `pnpm install` in plan\n * for an accurate outdated probe). Default false — clean check happens\n * after plan only if plan returns proceed, allowing noop-on-dirty for\n * read-only plans (a tree with stray edits + no recipe work to do\n * should not throw). */\n checkTreeFirst?: boolean;\n};\n\n/** Wrap a recipe's plan/apply phases. Centralises the siteLabel /\n * clean-tree check / branch creation / commit accumulation / RecipeResult\n * construction boilerplate that every recipe used to re-implement. */\nexport async function withRecipe<P>(body: RecipeBody<P>): Promise<RecipeResult> {\n const label = siteLabel(body.site);\n\n if (body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n const planned = await body.plan();\n\n if (planned.kind === \"noop\") {\n return {\n recipe: body.name,\n site: label,\n status: \"noop\",\n commits: [],\n ...(planned.notes ? { notes: planned.notes } : {}),\n };\n }\n if (planned.kind === \"failed\") {\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: [],\n notes: planned.notes,\n };\n }\n\n if (!body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n const branch = branchName(body.name);\n await createBranch(body.site.path, branch);\n\n const shas: string[] = [];\n const result = await body.apply(planned.plan, {\n cwd: body.site.path,\n branch,\n commit: async (msg) => {\n const sha = await gitCommit(body.site.path, msg);\n if (sha) shas.push(sha);\n return sha;\n },\n });\n\n if (result.kind === \"failed\") {\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: shas,\n notes: result.notes,\n };\n }\n\n const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;\n return {\n recipe: body.name,\n site: label,\n status: shas.length > 0 ? \"applied\" : \"noop\",\n commits: shas,\n notes,\n };\n}\n","import { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type BumpDepsGroup = \"patch\" | \"minor\" | \"major\";\n\nexport type BumpDepsOptions = {\n group?: BumpDepsGroup;\n spawn?: SpawnFn;\n};\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction outdatedFlagsForGroup(group: BumpDepsGroup): string[] {\n if (group === \"major\") return [\"--latest\"];\n if (group === \"minor\") return [];\n return [\"--depth\", \"0\"];\n}\n\nfunction upFlagsForGroup(group: BumpDepsGroup): string[] {\n if (group === \"major\") return [\"--latest\"];\n return [];\n}\n\ntype Plan = { group: BumpDepsGroup };\n\nexport async function bumpDeps(site: Site, opts: BumpDepsOptions = {}): Promise<RecipeResult> {\n const group: BumpDepsGroup = opts.group ?? \"minor\";\n const spawn = opts.spawn ?? defaultSpawn;\n\n return withRecipe<Plan>({\n name: \"bump-deps\",\n site,\n // pnpm install (in plan) mutates the lockfile, so the clean-tree check\n // MUST happen first — otherwise a desynced-lockfile resync would silently\n // land on top of whatever else was in the tree.\n checkTreeFirst: true,\n plan: async () => {\n // Pre-flight: the recipe is pnpm-only. A package-lock.json or yarn.lock\n // without pnpm-lock.yaml means the site is still on a different package\n // manager; we refuse to run rather than emit confusing pnpm errors.\n const hasPnpmLock = await exists(join(site.path, \"pnpm-lock.yaml\"));\n if (!hasPnpmLock) {\n const hasNpmLock = await exists(join(site.path, \"package-lock.json\"));\n const hasYarnLock = await exists(join(site.path, \"yarn.lock\"));\n if (hasNpmLock || hasYarnLock) {\n const competing = hasNpmLock ? \"package-lock.json\" : \"yarn.lock\";\n return {\n kind: \"failed\",\n notes: `site has ${competing} but no pnpm-lock.yaml — run convert-to-pnpm first`,\n };\n }\n }\n\n // Ensure the lockfile reflects the current package.json before we ask\n // pnpm what's outdated. Without this, a desynced lockfile can produce\n // stale or empty outdated reports.\n await spawn(\"pnpm\", [\"install\"], { cwd: site.path, streaming: true });\n\n const outdated = await spawn(\n \"pnpm\",\n [\"outdated\", \"--json\", ...outdatedFlagsForGroup(group)],\n { cwd: site.path },\n );\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(outdated.stdout || \"{}\") as Record<string, unknown>;\n } catch {\n parsed = {};\n }\n if (Object.keys(parsed).length === 0) {\n return { kind: \"noop\", notes: `pnpm outdated reported nothing for group=${group}` };\n }\n return { kind: \"apply\", plan: { group } };\n },\n apply: async ({ group: g }, { commit, cwd }) => {\n // Stream pnpm up's output so long-running upgrades don't look frozen.\n await spawn(\"pnpm\", [\"up\", ...upFlagsForGroup(g)], { cwd, streaming: true });\n await commit(`chore(deps): bump dependencies (${g})`);\n return { kind: \"ok\" };\n },\n });\n}\n","import { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { readPackageJson } from \"../../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { bumpToSvelte5Versions } from \"./step-bump-versions.js\";\nimport { migrateSvelteConfig } from \"./step-svelte-config.js\";\nimport { runSvelteMigrate } from \"./step-svelte-migrate.js\";\nimport { upgradeTailwind } from \"./step-tailwind-upgrade.js\";\nimport { applyGotchaCodemods } from \"./step-gotchas.js\";\nimport { verifyMigration } from \"./step-verify.js\";\nimport { writeMigrationSummary } from \"./step-summary.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\n\nexport type UpgradeSvelte4to5Options = {\n spawn?: SpawnFn;\n};\n\nasync function alreadyOnSvelte5(cwd: string): Promise<boolean> {\n try {\n const pkg = await readPackageJson(join(cwd, \"package.json\"));\n const v = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte;\n return !!v && /^\\^?5\\./.test(v);\n } catch {\n return false;\n }\n}\n\nexport async function upgradeSvelte4to5(\n site: Site,\n opts: UpgradeSvelte4to5Options = {},\n): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n\n return withRecipe<true>({\n name: \"svelte-4-to-5\",\n site,\n plan: async () => {\n if (await alreadyOnSvelte5(site.path)) {\n return { kind: \"noop\", notes: \"site already declares svelte ^5.x\" };\n }\n return { kind: \"apply\", plan: true };\n },\n apply: async (_plan, { commit, cwd }) => {\n const bumped = await bumpToSvelte5Versions(cwd);\n if (bumped) {\n await commit(\"chore(svelte5): bump svelte/kit/vite/vite-plugin-svelte\");\n }\n\n const configChanged = await migrateSvelteConfig(cwd);\n if (configChanged) {\n await commit(\"refactor(svelte5): migrate svelte.config.js (drop vitePreprocess)\");\n }\n\n const migrate = await runSvelteMigrate(cwd, spawn);\n if (migrate.ran) {\n await commit(\"refactor(svelte5): run official svelte-migrate codemod\");\n }\n\n const tw = await upgradeTailwind(cwd, spawn);\n if (tw.ran) {\n await commit(\"chore(svelte5): tailwindcss 3 → 4 upgrade\");\n }\n\n const codemods = await applyGotchaCodemods(cwd);\n if (codemods.filesChanged > 0) {\n await commit(`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`);\n }\n\n await verifyMigration(cwd, spawn);\n await commit(\"chore(svelte5): pnpm install + check\");\n\n await writeMigrationSummary({\n cwd,\n filesChangedByCodemods: codemods.filesChanged,\n svelteMigrateRan: migrate.ran,\n tailwindUpgraded: tw.ran,\n });\n await commit(\"docs(svelte5): add MIGRATION_SVELTE_5.md summary\");\n\n return { kind: \"ok\" };\n },\n });\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\n\nexport type PackageJsonLike = {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n [key: string]: unknown;\n};\n\nexport async function readPackageJson(path: string): Promise<PackageJsonLike> {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as PackageJsonLike;\n}\n\n/** Sniff the indent style (tab vs 2 vs 4 vs N spaces) from existing package.json\n * content by looking at the first indented `\"key\"` line. Defaults to two spaces. */\nfunction detectIndentFromContent(raw: string): string {\n const match = raw.match(/\\n([ \\t]+)\"/);\n return match ? (match[1] ?? \" \") : \" \";\n}\n\nexport async function writePackageJson(path: string, pkg: PackageJsonLike): Promise<void> {\n let indent = \" \";\n try {\n const existing = await readFile(path, \"utf-8\");\n indent = detectIndentFromContent(existing);\n } catch {\n // file doesn't exist yet — first write — keep the 2-space default\n }\n const content = JSON.stringify(pkg, null, indent) + \"\\n\";\n await writeFile(path, content, \"utf-8\");\n}\n\nexport type BumpDepMode =\n | \"ensure\" // default: add to devDependencies if missing\n | \"bump-only\"; // never add; only update existing entries\n\nexport type BumpDepOptions = {\n mode?: BumpDepMode;\n};\n\nexport function bumpDep(\n pkg: PackageJsonLike,\n name: string,\n version: string,\n opts: BumpDepOptions = {},\n): PackageJsonLike {\n const mode = opts.mode ?? \"ensure\";\n\n const next: PackageJsonLike = {\n ...pkg,\n };\n\n if (pkg.dependencies) {\n next.dependencies = { ...pkg.dependencies };\n }\n if (pkg.devDependencies) {\n next.devDependencies = { ...pkg.devDependencies };\n }\n\n if (next.dependencies && name in next.dependencies) {\n if (next.dependencies[name] === version) return pkg;\n next.dependencies[name] = version;\n return next;\n }\n if (next.devDependencies && name in next.devDependencies) {\n if (next.devDependencies[name] === version) return pkg;\n next.devDependencies[name] = version;\n return next;\n }\n // Not present in either map. bump-only leaves the pkg alone so recipes\n // can express \"raise the floor on packages this site already uses\" without\n // also installing every related dep across the fleet.\n if (mode === \"bump-only\") return pkg;\n next.devDependencies = { ...(next.devDependencies ?? {}), [name]: version };\n return next;\n}\n","import { join } from \"node:path\";\nimport { readPackageJson, writePackageJson, bumpDep } from \"../../util/pkg.js\";\n\nconst SVELTE_5_VERSIONS: Record<string, string> = {\n svelte: \"^5.55.5\",\n \"@sveltejs/kit\": \"^2.59.0\",\n \"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n \"@sveltejs/adapter-netlify\": \"^6.0.4\",\n \"@sveltejs/adapter-auto\": \"^7.0.0\",\n vite: \"^8.0.10\",\n \"svelte-check\": \"^4.4.7\",\n typescript: \"^6.0.3\",\n \"typescript-svelte-plugin\": \"^0.3.52\",\n};\n\nexport async function bumpToSvelte5Versions(cwd: string): Promise<boolean> {\n const pkgPath = join(cwd, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n let next = pkg;\n // bump-only: a svelte-4 site that doesn't declare e.g. adapter-netlify\n // should not get it added during the upgrade.\n for (const [name, version] of Object.entries(SVELTE_5_VERSIONS)) {\n next = bumpDep(next, name, version, { mode: \"bump-only\" });\n }\n if (next === pkg) return false;\n await writePackageJson(pkgPath, next);\n return true;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst VITE_PLUGIN_PKG = \"@sveltejs/vite-plugin-svelte\";\n\n/** Match an import statement that pulls one or more named bindings from\n * `@sveltejs/vite-plugin-svelte`. Group 1 is the comma-separated name list. */\nconst IMPORT_FROM_VITE_PLUGIN = new RegExp(\n String.raw`^import\\s+\\{\\s*([^}]+?)\\s*\\}\\s+from\\s+[\"']` +\n VITE_PLUGIN_PKG.replace(/[/]/g, \"\\\\/\") +\n String.raw`[\"'];?[ \\t]*\\n`,\n \"m\",\n);\n\n/** Rewrite the import to drop only `vitePreprocess`, preserving any other\n * named bindings. If `vitePreprocess` was the sole import, the whole line\n * is removed. */\nfunction dropVitePreprocessImport(source: string): string {\n return source.replace(IMPORT_FROM_VITE_PLUGIN, (full, names: string) => {\n const remaining = names\n .split(\",\")\n .map((n) => n.trim())\n .filter((n) => n.length > 0 && n !== \"vitePreprocess\");\n if (remaining.length === 0) return \"\"; // drop entire line including its trailing newline\n return `import { ${remaining.join(\", \")} } from \"${VITE_PLUGIN_PKG}\";\\n`;\n });\n}\n\n/** Find the end of a balanced-paren call starting at `openIdx`, which must\n * point at the `(` character. Returns the index of the matching `)`, or -1\n * if unbalanced. */\nfunction findMatchingParen(source: string, openIdx: number): number {\n if (source[openIdx] !== \"(\") return -1;\n let depth = 0;\n for (let i = openIdx; i < source.length; i++) {\n const ch = source[i];\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/** Remove a `preprocess: vitePreprocess(<anything>),?` key from a config\n * object. Handles the call with empty parens or with an options object. */\nfunction dropPreprocessKey(source: string): string {\n // Anchor on the start of the preprocess key on its own line so we don't\n // also strip whitespace / commas from neighboring keys.\n const startRe = /^(\\s*)preprocess:\\s*vitePreprocess\\(/m;\n const m = startRe.exec(source);\n if (!m) return source;\n\n const indent = m[1] ?? \"\";\n const parenOpenAbs = m.index + m[0].length - 1; // points at `(`\n const parenCloseAbs = findMatchingParen(source, parenOpenAbs);\n if (parenCloseAbs < 0) return source;\n\n // Consume an optional trailing comma and whitespace through end-of-line.\n let tailIdx = parenCloseAbs + 1;\n while (tailIdx < source.length && /[ \\t,]/.test(source[tailIdx] ?? \"\")) tailIdx++;\n if (source[tailIdx] === \"\\n\") tailIdx++;\n\n return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\\\n`), \"\");\n}\n\nexport async function migrateSvelteConfig(cwd: string): Promise<boolean> {\n const path = join(cwd, \"svelte.config.js\");\n let src: string;\n try {\n src = await readFile(path, \"utf-8\");\n } catch {\n return false;\n }\n\n let next = src;\n next = dropPreprocessKey(next);\n next = dropVitePreprocessImport(next);\n\n if (next === src) return false;\n await writeFile(path, next, \"utf-8\");\n return true;\n}\n","import { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\n\nexport async function runSvelteMigrate(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<{ ran: boolean; stderr: string }> {\n try {\n const { code, stderr } = await spawn(\n \"npx\",\n [\"--yes\", \"svelte-migrate\", \"svelte-5\", \"--no-install\"],\n { cwd, timeoutMs: 5 * 60_000 },\n );\n if (code !== 0) {\n return { ran: false, stderr };\n }\n return { ran: true, stderr };\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return { ran: false, stderr: \"npx unavailable\" };\n }\n throw err;\n }\n}\n","import { readPackageJson } from \"../../util/pkg.js\";\nimport { join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\n\nexport async function upgradeTailwind(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<{ ran: boolean; reason?: string }> {\n const pkg = await readPackageJson(join(cwd, \"package.json\"));\n const tailwindVersion = pkg.devDependencies?.tailwindcss ?? pkg.dependencies?.tailwindcss;\n if (!tailwindVersion) return { ran: false, reason: \"tailwindcss not installed\" };\n if (/^\\^?4\\./.test(tailwindVersion)) return { ran: false, reason: \"already on tailwind 4.x\" };\n\n try {\n const { code, stderr } = await spawn(\"npx\", [\"--yes\", \"@tailwindcss/upgrade\", \"--force\"], {\n cwd,\n timeoutMs: 5 * 60_000,\n });\n if (code !== 0) return { ran: false, reason: stderr.slice(0, 200) };\n return { ran: true };\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\" || /ENOENT/.test(String(err))) {\n return { ran: false, reason: \"npx unavailable\" };\n }\n throw err;\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport { onEventToHandler } from \"./codemods/on-event-to-handler.js\";\nimport { exportLetToProps } from \"./codemods/dollar-props.js\";\nimport { removeDollarRestProps } from \"./codemods/dollar-restprops.js\";\nimport { stateEffectSyncToDerived } from \"./codemods/state-effect-sync.js\";\nimport { dollarPropsClass } from \"./codemods/dollar-props-class.js\";\nimport { legacyReactiveToRunes } from \"./codemods/legacy-reactive.js\";\n\nconst SVELTE_GLOBS = [\"src/**/*.svelte\"];\nconst IGNORE = [\"node_modules/**\", \".svelte-kit/**\", \"build/**\"];\n\ntype Codemod = (src: string) => string;\n\n// Order matters: exportLetToProps creates the $props() destructuring that\n// dollarPropsClass extends with a `class:` named prop.\nconst CODEMODS: Codemod[] = [\n onEventToHandler,\n exportLetToProps,\n removeDollarRestProps,\n stateEffectSyncToDerived,\n dollarPropsClass,\n legacyReactiveToRunes,\n];\n\nexport type CodemodChange = { rel: string; after: string };\n\nexport async function planGotchaCodemods(cwd: string): Promise<CodemodChange[]> {\n const changes: CodemodChange[] = [];\n const relPaths = await glob(SVELTE_GLOBS, { cwd, ignore: IGNORE, absolute: false });\n for (const rel of relPaths) {\n const path = join(cwd, rel);\n const before = await readFile(path, \"utf-8\");\n const after = CODEMODS.reduce((s, fn) => fn(s), before);\n if (after !== before) changes.push({ rel, after });\n }\n return changes;\n}\n\nexport async function applyGotchaCodemods(cwd: string): Promise<{ filesChanged: number }> {\n const changes = await planGotchaCodemods(cwd);\n for (const c of changes) {\n await writeFile(join(cwd, c.rel), c.after, \"utf-8\");\n }\n return { filesChanged: changes.length };\n}\n","const SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst SIMPLE_ON_EVENT = /\\bon:([a-z]+)(?=\\s*=)/g;\nconst MODIFIER_EVENT = /\\bon:[a-z]+\\|[a-zA-Z]+(?=\\s*=)/g;\n\n/** Svelte 5 removed event modifier syntax (`on:click|preventDefault={fn}`).\n * The rewrite is non-trivial — the modifier behavior must be inlined into\n * the handler body — so this codemod doesn't attempt it automatically.\n * Instead it inserts a `@migration-task` marker immediately above each\n * offending element so the user gets a visible audit trail rather than\n * a silent build error from the Svelte 5 compiler. */\nfunction flagEventModifiers(source: string): string {\n const insertions: Array<{ tagStart: number; indent: string; modifier: string }> = [];\n let m: RegExpExecArray | null;\n MODIFIER_EVENT.lastIndex = 0;\n while ((m = MODIFIER_EVENT.exec(source)) !== null) {\n const tagStart = source.lastIndexOf(\"<\", m.index);\n if (tagStart === -1) continue;\n\n // Idempotency: if the line immediately above the tag already carries an\n // @migration-task marker for this site, don't double-insert on re-run.\n const prevLineEnd = tagStart - 1;\n if (prevLineEnd >= 0) {\n const prevLineStart = source.lastIndexOf(\"\\n\", prevLineEnd - 1) + 1;\n const prevLine = source.slice(prevLineStart, prevLineEnd + 1);\n if (/<!--\\s*@migration-task/.test(prevLine)) continue;\n }\n\n const lineStart = source.lastIndexOf(\"\\n\", tagStart - 1) + 1;\n const indent = source.slice(lineStart, tagStart);\n const safeIndent = /^[ \\t]*$/.test(indent) ? indent : \"\";\n insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });\n }\n\n // Apply back-to-front so earlier insertion offsets stay valid.\n let out = source;\n for (let i = insertions.length - 1; i >= 0; i--) {\n const { tagStart, indent, modifier } = insertions[i]!;\n const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\\`${modifier}\\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->\\n${indent}`;\n out = out.slice(0, tagStart) + comment + out.slice(tagStart);\n }\n return out;\n}\n\nexport function onEventToHandler(source: string): string {\n const masked: string[] = [];\n const placeholder = (i: number): string => ` SCRIPT_${i} `;\n const intermediate = source.replace(SCRIPT_BLOCK, (match) => {\n masked.push(match);\n return placeholder(masked.length - 1);\n });\n\n let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name: string) => `on${name}`);\n processed = flagEventModifiers(processed);\n\n let out = processed;\n masked.forEach((blk, i) => {\n out = out.replace(placeholder(i), blk);\n });\n\n return out;\n}\n","const SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst EXPORT_LET = /^\\s*export\\s+let\\s+(\\w+)\\s*(?::\\s*([^=;\\n]+))?\\s*(?:=\\s*([^;\\n]+))?;?\\s*$/gm;\n\ntype Prop = { name: string; type?: string | undefined; defaultExpr?: string | undefined };\n\nfunction transformScript(scriptBody: string, isTs: boolean): { body: string; changed: boolean } {\n const props: Prop[] = [];\n const cleaned = scriptBody.replace(\n EXPORT_LET,\n (_full, name: string, type?: string, defaultExpr?: string) => {\n props.push({\n name,\n type: type?.trim(),\n defaultExpr: defaultExpr?.trim(),\n });\n return \"\";\n },\n );\n if (props.length === 0) return { body: scriptBody, changed: false };\n\n const destructured = props\n .map((p) => (p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name))\n .join(\", \");\n\n let decl: string;\n if (isTs) {\n const typeSig = props\n .map((p) => {\n const optional = p.defaultExpr ? \"?\" : \"\";\n return `${p.name}${optional}: ${p.type ?? \"unknown\"}`;\n })\n .join(\"; \");\n decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;\n } else {\n decl = ` let { ${destructured} } = $props();`;\n }\n\n const next = cleaned.replace(/^(\\s*)/, (m) => `${m}${decl}\\n`);\n return { body: next, changed: true };\n}\n\nexport function exportLetToProps(source: string): string {\n const match = source.match(SCRIPT_BLOCK);\n if (!match) return source;\n const attrs = match[1] ?? \"\";\n const inner = match[2] ?? \"\";\n const isTs = /\\blang=[\"']ts[\"']/.test(attrs);\n const { body, changed } = transformScript(inner, isTs);\n if (!changed) return source;\n return source.replace(SCRIPT_BLOCK, (full) => full.replace(inner, body));\n}\n","/** Find the index of the closing quote for a string literal that opens at\n * `openIdx`. Handles backslash escapes. Returns -1 if the string is\n * unterminated.\n *\n * Treats backtick template literals the same as `'…'` / `\"…\"` — the\n * closing backtick terminates. Callers needing precise `${…}` interpolation\n * handling will need a real parser; this helper is intentionally simple\n * and good enough for the codemod-grade string masking we do today. */\nexport function findStringEnd(source: string, openIdx: number): number {\n const quote = source[openIdx];\n let i = openIdx + 1;\n while (i < source.length) {\n const ch = source[i];\n if (ch === \"\\\\\") {\n i += 2;\n continue;\n }\n if (ch === quote) return i;\n i++;\n }\n return -1;\n}\n","/** Locate `interface $$Props {` declarations and remove them, including\n * the matching closing `}` even if the body has nested braces or spans\n * multiple lines. Regex alone can't do balanced-brace matching, so we\n * walk the string manually. */\nfunction removeInterfaceBlock(source: string): string {\n const re = /^\\s*interface\\s+\\$\\$Props\\s*\\{/m;\n let out = source;\n while (true) {\n const match = re.exec(out);\n if (!match) return out;\n\n const openBraceIdx = match.index + match[0].length - 1;\n let depth = 1;\n let i = openBraceIdx + 1;\n while (i < out.length && depth > 0) {\n const ch = out[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") depth--;\n i++;\n }\n if (depth !== 0) return out; // unbalanced; bail rather than corrupt\n\n // Consume trailing whitespace through end-of-line.\n let endIdx = i;\n while (endIdx < out.length && /[ \\t]/.test(out[endIdx] ?? \"\")) endIdx++;\n if (out[endIdx] === \"\\n\") endIdx++;\n\n out = out.slice(0, match.index) + out.slice(endIdx);\n }\n}\n\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\n/** Mask every `'…'`, `\"…\"`, and template literal in `source` with a placeholder\n * so subsequent regex passes can rewrite identifiers without corrupting string\n * contents. Returns the masked body and a function to restore originals. */\nfunction maskStringLiterals(source: string): {\n masked: string;\n restore: (s: string) => string;\n} {\n const strings: string[] = [];\n let out = \"\";\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeIdx = findStringEnd(source, i);\n if (closeIdx === -1) {\n out += source.slice(i);\n break;\n }\n const literal = source.slice(i, closeIdx + 1);\n out += `__RDMNT_STR_${strings.length}__`;\n strings.push(literal);\n i = closeIdx + 1;\n } else {\n out += ch;\n i++;\n }\n }\n return {\n masked: out,\n restore: (s) => s.replace(/__RDMNT_STR_(\\d+)__/g, (_full, idx) => strings[Number(idx)] ?? \"\"),\n };\n}\n\nconst PROPS_DECL = /let\\s*\\{([^}]*)\\}\\s*(?::\\s*\\{([^}]*)\\})?\\s*=\\s*\\$props\\(\\)\\s*;?/;\n\n/** If the script declares `let { … } = $props();` (with or without an inline\n * type annotation) and doesn't already collect `...rest`, inject it. For TS,\n * widen the inline type with an `[key: string]: unknown` index signature so\n * the rest binding actually captures excess attributes (without the widening,\n * TS infers `rest` as `{}` and the spread forwards nothing). */\nfunction injectRestIntoProps(scriptBody: string): string {\n const match = scriptBody.match(PROPS_DECL);\n if (!match) return scriptBody;\n const destructured = match[1] ?? \"\";\n if (/\\.\\.\\.\\s*\\w+/.test(destructured)) return scriptBody; // already has rest\n\n // Strip any trailing comma left over from a multi-line destructuring shape\n // (e.g. `{ foo, bar, }`). Without this, the template literal below emits\n // `bar,, ...rest` — invalid syntax that the codemod was happily committing\n // (regression: caltex's Accordian.svelte, 2026-05-27).\n const trimmed = destructured.trim().replace(/,\\s*$/, \"\");\n const newDestructured = trimmed === \"\" ? \" ...rest \" : ` ${trimmed}, ...rest `;\n\n let replacement: string;\n if (match[2] !== undefined) {\n const typeBody = match[2];\n const hasIndexSig = /\\[\\s*key\\s*:\\s*string\\s*\\]\\s*:/.test(typeBody);\n const newTypeBody = hasIndexSig\n ? typeBody\n : `${typeBody.trimEnd().replace(/;?\\s*$/, \"\")}; [key: string]: unknown `;\n replacement = `let {${newDestructured}}: {${newTypeBody}} = $props();`;\n } else {\n replacement = `let {${newDestructured}} = $props();`;\n }\n return scriptBody.replace(PROPS_DECL, replacement);\n}\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst HAS_PROPS_CALL = /\\$props\\(\\s*\\)/;\n\nexport function removeDollarRestProps(source: string): string {\n const next = removeInterfaceBlock(source);\n\n const scriptMatch = next.match(SCRIPT_BLOCK);\n if (!scriptMatch) return next;\n if (!HAS_PROPS_CALL.test(scriptMatch[2] ?? \"\")) {\n // No $props() in this script — refuse to rewrite $$restProps anywhere, since\n // doing so would emit references to an undeclared identifier. The user sees\n // the original $$restProps and a clear Svelte 5 build error to migrate by hand.\n return next;\n }\n\n const scriptInner = scriptMatch[2] ?? \"\";\n const { masked, restore } = maskStringLiterals(scriptInner);\n let processed = injectRestIntoProps(masked);\n processed = processed.replace(/\\$\\$restProps/g, \"rest\");\n const restoredInner = restore(processed);\n\n // Use a function callback so `$$` in the restored script body isn't\n // interpreted as the `$` substitution pattern by String.prototype.replace.\n const newScriptBlock = scriptMatch[0].replace(scriptInner, () => restoredInner);\n const before = next.slice(0, scriptMatch.index!);\n const after = next.slice(scriptMatch.index! + scriptMatch[0].length);\n\n // Template (outside script) gets a plain swap. Template attribute strings\n // containing the literal text \"$$restProps\" are vanishingly rare in practice;\n // accept the limitation rather than parse the whole template.\n return (\n before.replace(/\\$\\$restProps/g, \"rest\") +\n newScriptBlock +\n after.replace(/\\$\\$restProps/g, \"rest\")\n );\n}\n","/**\n * Collapses the \"manual sync state with prop\" anti-pattern into `$derived`.\n *\n * Input:\n * let content = $state(data.page.data);\n * $effect(() => { data; content = data.page.data });\n *\n * Output:\n * let content = $derived(data.page.data);\n *\n * Only transforms when the `$state(...)` initializer expression matches the\n * effect's right-hand assignment exactly (after trim). Intervening statements\n * between the `let` and the `$effect` block prevent the match — keeps the\n * codemod conservative.\n *\n * Triggered by Svelte 5's `state_referenced_locally` warning, which fires\n * whenever a local `let X = $state(prop.expr)` captures a prop reference\n * only at init time.\n */\n// `;?` before the closing `}` so the multi-line effect form matches:\n// $effect(() => {\n// data;\n// content = data.page.data;\n// });\n// as well as the single-line form: $effect(() => { data; content = data.page.data })\nconst PATTERN =\n /let\\s+(\\w+)\\s*=\\s*\\$state\\(\\s*([^)]+?)\\s*\\)\\s*;[ \\t\\r\\n]*\\$effect\\(\\s*\\(\\s*\\)\\s*=>\\s*\\{\\s*\\w+\\s*;\\s*\\1\\s*=\\s*([^;}]+?)\\s*;?\\s*\\}\\s*\\)\\s*;?/g;\n\nexport function stateEffectSyncToDerived(source: string): string {\n return source.replace(PATTERN, (full, name: string, initExpr: string, effectExpr: string) => {\n if (initExpr.trim() !== effectExpr.trim()) return full;\n return `let ${name} = $derived(${initExpr.trim()});`;\n });\n}\n","/**\n * Converts the legacy `$$props.class` pattern (passing extra HTML class from\n * a parent component) to a Svelte 5 named-prop destructuring.\n *\n * Input:\n * <script lang=\"ts\">\n * let { foo }: { foo?: string } = $props();\n * </script>\n * <div class=\"other {$$props.class || ''}\">x</div>\n *\n * Output:\n * <script lang=\"ts\">\n * let { foo, class: className = \"\" }: { foo?: string; class?: string } = $props();\n * </script>\n * <div class=\"other {className || ''}\">x</div>\n *\n * Triggered by Svelte 5 build errors:\n * \"Cannot use `$$props` in runes mode\" (svelte.dev/e/legacy_props_invalid)\n *\n * The original svelte-migrate tool flagged this with a `@migration-task`\n * comment because it couldn't safely combine `$$props` with already-named\n * props. We can: `class` is the dominant case across the reddoor fleet,\n * so we destructure it as `class: className = \"\"` (renamed because `class`\n * is a JS reserved word as a bare binding) and rewrite template references.\n *\n * Conservative: only transforms files that have BOTH a template\n * `$$props.class` reference AND an existing `$props()` destructuring.\n * Files using `$$props.class` without a `$props()` declaration are left\n * for the `exportLetToProps` codemod to handle in a prior pass.\n */\n// Note: lazy `[\\s\\S]*?` (not `[^}]*`) so default values containing braces\n// — `() => {}`, `{ foo: 1 }`, etc. — don't truncate the match early.\nconst PROPS_DESTRUCTURE = /let\\s*\\{([\\s\\S]*?)\\}(\\s*:\\s*\\{([\\s\\S]*?)\\})?\\s*=\\s*\\$props\\(\\)/;\n// Two regexes: a stateless one for \"does this string contain $$props.class?\"\n// existence checks, and the /g one for the iterating template rewrite. Mixing\n// .test() and .replace() on the same /g regex makes lastIndex management\n// fragile — easy to forget the reset on a future edit.\nconst HAS_DOLLAR_PROPS_CLASS = /\\$\\$props\\.class\\b/;\nconst DOLLAR_PROPS_CLASS_GLOBAL = /\\$\\$props\\.class\\b/g;\nconst DOLLAR_PROPS_ANY = /\\$\\$props\\b/;\nconst SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst MIGRATION_TASK = /^<!--\\s*@migration-task[\\s\\S]*?-->\\s*\\n?/gm;\nconst IDENT = \"className\";\n\nfunction maskScripts(source: string): { masked: string; blocks: string[] } {\n const blocks: string[] = [];\n const masked = source.replace(SCRIPT_BLOCK, (m) => {\n blocks.push(m);\n return `__SCRIPT_${blocks.length - 1}__`;\n });\n return { masked, blocks };\n}\n\nfunction restoreScripts(masked: string, blocks: string[]): string {\n let out = masked;\n blocks.forEach((blk, i) => {\n out = out.replace(`__SCRIPT_${i}__`, blk);\n });\n return out;\n}\n\nexport function dollarPropsClass(source: string): string {\n // Bail early if the template doesn't reference $$props.class\n const { masked } = maskScripts(source);\n if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;\n\n // Bail if there's no $props() destructuring to extend\n if (!PROPS_DESTRUCTURE.test(source)) return source;\n\n let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {\n // Already migrated (someone added class manually)\n if (/\\bclass\\s*:/.test(body as string)) return full;\n\n const cleanBody = (body as string).trim().replace(/,\\s*$/, \"\").trim();\n const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = \"\"` : `class: ${IDENT} = \"\"`;\n\n if (typeAnno) {\n const cleanType = ((typeBody as string) ?? \"\").trim().replace(/;\\s*$/, \"\").trim();\n const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;\n return `let { ${newBody} }: { ${newType} } = $props()`;\n }\n return `let { ${newBody} } = $props()`;\n });\n\n // Replace $$props.class in template only (re-mask after destructuring update)\n const reMasked = maskScripts(updated);\n const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);\n updated = restoreScripts(templateRewritten, reMasked.blocks);\n\n // Strip @migration-task comments if no $$props references remain anywhere\n // EXCEPT inside those very comments. Strip-then-check, restore if still dirty.\n const stripped = updated.replace(MIGRATION_TASK, \"\");\n if (!DOLLAR_PROPS_ANY.test(stripped)) {\n updated = stripped;\n }\n\n return updated;\n}\n","/**\n * Converts Svelte 4 `$:` reactive statements to Svelte 5 runes.\n *\n * - `$: var = expr;` → `let var = $derived(expr);`\n * - `$: { body }` → `$effect(() => { body });`\n *\n * Triggered by:\n * \"`$:` is not allowed in runes mode, use `$derived` or `$effect` instead\"\n * (svelte.dev/e/legacy_reactive_statement_invalid)\n *\n * Block patterns become `$effect` rather than per-variable `$derived` calls\n * because the block typically mutates multiple already-declared `let`\n * variables with conditional logic — too contextual for a safe automatic\n * decomposition into discrete derived values. The user can refine each\n * `$effect` into idiomatic `$derived` calls afterward if desired.\n *\n * Scoped to `<script>` content only — `$:` in template/style text is left\n * alone (it would only ever appear there as literal text anyway).\n */\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/g;\nconst SIMPLE_REACTIVE = /^([ \\t]*)\\$:\\s*(\\w+)\\s*=\\s*([^;\\n]+);?[ \\t]*$/gm;\nconst BLOCK_REACTIVE_HEAD = /(^|\\n)([ \\t]*)\\$:\\s*\\{/g;\n\nfunction findMatchingClose(source: string, openIdx: number): number {\n let depth = 0;\n let i = openIdx;\n while (i < source.length) {\n const ch = source[i];\n // Skip over string literals so braces inside strings don't fool the counter.\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeStr = findStringEnd(source, i);\n if (closeStr === -1) return -1;\n i = closeStr + 1;\n continue;\n }\n // Skip over comments so braces inside `// }` or `/* } */` don't fool the\n // counter. Regression: the old version silently corrupted source (depth\n // went off, real closing brace mis-matched) on inputs like `$: { // } ... }`.\n // The corrupted output still compiles in Svelte 5 — no parser to scream.\n if (ch === \"/\") {\n const next = source[i + 1];\n if (next === \"/\") {\n const eol = source.indexOf(\"\\n\", i + 2);\n i = eol === -1 ? source.length : eol; // step onto newline; outer loop handles it\n continue;\n }\n if (next === \"*\") {\n const end = source.indexOf(\"*/\", i + 2);\n if (end === -1) return -1; // unterminated block comment — bail rather than corrupt\n i = end + 2;\n continue;\n }\n }\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) return i;\n }\n i++;\n }\n return -1;\n}\n\n/** Flag each converted `$effect` block for manual review. The conversion is\n * syntactically safe (compiles), but if any of the locals the block mutates\n * was declared as plain `let` (not `$state`), the `$effect` runs once on\n * mount and never again — code silently loses its reactivity. We can't\n * detect that automatically (it would require scope analysis on the\n * declaration sites), so we leave a breadcrumb for the human reviewer. */\nconst MIGRATION_MARKER =\n \"// @migration-task: $effect won't trigger UI updates on plain `let` bindings — refine mutated locals to $state or split into per-variable $derived.\";\n\nfunction transformBlocks(body: string): string {\n const out: string[] = [];\n let last = 0;\n BLOCK_REACTIVE_HEAD.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = BLOCK_REACTIVE_HEAD.exec(body)) !== null) {\n const leadingNewline = m[1] ?? \"\";\n const indent = m[2] ?? \"\";\n const headEnd = m.index + m[0].length; // position just after `{`\n const openBraceIdx = headEnd - 1;\n const closeBraceIdx = findMatchingClose(body, openBraceIdx);\n if (closeBraceIdx === -1) continue;\n out.push(body.slice(last, m.index));\n out.push(leadingNewline);\n const blockBody = body.slice(openBraceIdx + 1, closeBraceIdx);\n out.push(`${indent}${MIGRATION_MARKER}\\n`);\n out.push(`${indent}$effect(() => {${blockBody}});`);\n last = closeBraceIdx + 1;\n BLOCK_REACTIVE_HEAD.lastIndex = last;\n }\n out.push(body.slice(last));\n return out.join(\"\");\n}\n\nfunction transformSimple(body: string): string {\n return body.replace(SIMPLE_REACTIVE, (_full, indent: string, name: string, expr: string) => {\n return `${indent}let ${name} = $derived(${expr.trim()});`;\n });\n}\n\nexport function legacyReactiveToRunes(source: string): string {\n return source.replace(SCRIPT_BLOCK, (full, _attrs: string, body: string) => {\n // Blocks first so an outer `$: { ... }` containing nothing matchable\n // for the simple pass still gets wrapped. Order doesn't matter for the\n // patterns currently in the fleet but keeps the codemod robust to future\n // shapes.\n let next = transformBlocks(body);\n next = transformSimple(next);\n if (next === body) return full;\n return full.replace(body, next);\n });\n}\n","import { defaultSpawn, type SpawnFn, type SpawnResult } from \"../../audits/util/spawn.js\";\n\nexport type VerifyResult = {\n install: SpawnResult | { skipped: true };\n check: SpawnResult | { skipped: true };\n};\n\nexport async function verifyMigration(\n cwd: string,\n spawn: SpawnFn = defaultSpawn,\n): Promise<VerifyResult> {\n let install: VerifyResult[\"install\"];\n try {\n install = await spawn(\"pnpm\", [\"install\"], { cwd, timeoutMs: 10 * 60_000 });\n } catch {\n install = { skipped: true };\n }\n\n let check: VerifyResult[\"check\"];\n try {\n check = await spawn(\"pnpm\", [\"run\", \"check\"], { cwd, timeoutMs: 5 * 60_000 });\n } catch {\n check = { skipped: true };\n }\n\n return { install, check };\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type SummaryInput = {\n cwd: string;\n filesChangedByCodemods: number;\n svelteMigrateRan: boolean;\n tailwindUpgraded: boolean;\n};\n\nexport async function writeMigrationSummary(input: SummaryInput): Promise<string> {\n const lines = [\n `# Svelte 4 → 5 migration summary`,\n ``,\n `Generated by @reddoorla/maintenance.`,\n ``,\n `- svelte-migrate run: ${input.svelteMigrateRan ? \"yes\" : \"no\"}`,\n `- @tailwindcss/upgrade run: ${input.tailwindUpgraded ? \"yes\" : \"no\"}`,\n `- .svelte files touched by gotcha codemods: ${input.filesChangedByCodemods}`,\n ``,\n `Next steps:`,\n `- Run \\`pnpm run check\\` and resolve any remaining warnings.`,\n `- Spot-check rune migrations in components that use \\`reactive\\` statements.`,\n `- Verify Playwright a11y tests still pass.`,\n ];\n const content = lines.join(\"\\n\") + \"\\n\";\n const path = join(input.cwd, \"MIGRATION_SVELTE_5.md\");\n await writeFile(path, content, \"utf-8\");\n return path;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { planGotchaCodemods } from \"./svelte-5/step-gotchas.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\ntype Change = { rel: string; after: string };\n\n/**\n * Standalone codemod pass for sites already on Svelte 5.\n *\n * Applies the same gotcha codemods the full `svelte-4-to-5` migration runs,\n * but skips the version checks and migration steps — useful when Svelte 5\n * surfaces new strictness warnings post-upgrade (e.g. `state_referenced_locally`)\n * and the fleet needs a clean re-application.\n *\n * Plans changes in memory first; only creates the branch + writes + commits\n * when there is something to apply. Re-runs against a clean tree are noop.\n */\nexport async function svelteCodemods(site: Site): Promise<RecipeResult> {\n return withRecipe<Change[]>({\n name: \"svelte-codemods\",\n site,\n plan: async () => {\n const changes = await planGotchaCodemods(site.path);\n if (changes.length === 0) {\n return { kind: \"noop\", notes: \"no codemod targets matched\" };\n }\n return { kind: \"apply\", plan: changes };\n },\n apply: async (changes, { commit, cwd }) => {\n for (const c of changes) {\n await writeFile(join(cwd, c.rel), c.after, \"utf-8\");\n }\n await commit(`refactor(svelte5): apply codemods (${changes.length} files)`);\n return { kind: \"ok\" };\n },\n });\n}\n","import { rm, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { readPackageJson, writePackageJson, type PackageJsonLike } from \"../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { rewriteScriptsForPnpm } from \"./convert-to-pnpm/script-rewrites.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type ConvertToPnpmOptions = {\n spawn?: SpawnFn;\n /** Version string written into package.json's `packageManager` field.\n * Defaults to the version baked into this package's own pnpm setup. */\n pnpmVersion?: string;\n};\n\n/** Pinned default — matches the `packageManager` field of this package\n * (kept in sync with package.json). Sites can override per-recipe. */\nconst DEFAULT_PNPM_VERSION = \"10.33.1\";\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\ntype Plan = { hasNpmLock: boolean; hasYarnLock: boolean };\n\nexport async function convertToPnpm(\n site: Site,\n opts: ConvertToPnpmOptions = {},\n): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;\n\n const pnpmLockPath = join(site.path, \"pnpm-lock.yaml\");\n const npmLockPath = join(site.path, \"package-lock.json\");\n const yarnLockPath = join(site.path, \"yarn.lock\");\n\n return withRecipe<Plan>({\n name: \"convert-to-pnpm\",\n site,\n plan: async () => {\n if (await exists(pnpmLockPath)) {\n return { kind: \"noop\", notes: \"site already has pnpm-lock.yaml\" };\n }\n const hasNpmLock = await exists(npmLockPath);\n const hasYarnLock = await exists(yarnLockPath);\n if (!hasNpmLock && !hasYarnLock) {\n return {\n kind: \"noop\",\n notes: \"no convertible lockfile (package-lock.json or yarn.lock) at site root\",\n };\n }\n return { kind: \"apply\", plan: { hasNpmLock, hasYarnLock } };\n },\n apply: async ({ hasNpmLock, hasYarnLock }, { commit, cwd }) => {\n // Step 1: remove the npm/yarn lockfile(s).\n if (hasNpmLock) await rm(npmLockPath, { force: true });\n if (hasYarnLock) await rm(yarnLockPath, { force: true });\n const sourceLock = hasNpmLock ? \"package-lock.json\" : \"yarn.lock\";\n await commit(`chore(pnpm): remove ${sourceLock}`);\n\n // Step 2: pin packageManager + rewrite scripts (single commit — they\n // both touch package.json).\n const pkgPath = join(cwd, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n const next: PackageJsonLike = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };\n\n if (pkg.scripts && typeof pkg.scripts === \"object\") {\n const { scripts: rewritten, changedCount } = rewriteScriptsForPnpm(\n pkg.scripts as Record<string, string>,\n );\n if (changedCount > 0) {\n next.scripts = rewritten;\n }\n }\n\n await writePackageJson(pkgPath, next);\n await commit(\"chore(pnpm): pin packageManager + rewrite npm scripts\");\n\n // Step 3: remove any existing flat node_modules from a prior npm/yarn run\n // before pnpm installs. Sharing a node_modules across package managers\n // produces phantom-dep resolution issues (pnpm's nested layout disagrees\n // with what's already on disk). node_modules is gitignored on every\n // reddoor site so this doesn't dirty the tree.\n await rm(join(cwd, \"node_modules\"), { recursive: true, force: true });\n\n // Step 4: run pnpm install to materialize pnpm-lock.yaml.\n const installResult = await spawn(\"pnpm\", [\"install\"], { cwd, streaming: true });\n if (installResult.code !== 0) {\n return { kind: \"failed\", notes: `pnpm install failed (exit ${installResult.code})` };\n }\n\n await commit(\"chore(pnpm): add pnpm-lock.yaml\");\n return { kind: \"ok\" };\n },\n });\n}\n","/**\n * Rewrite a single package.json script value to use pnpm equivalents\n * where the substitution is safe. Conservative on purpose: we only touch\n * patterns whose semantics are identical under pnpm.\n *\n * - `npm run <token>` → `pnpm run <token>` (identical behavior)\n * - `npx <token>` → `pnpm dlx <token>` (identical behavior in pnpm 7+)\n *\n * Intentionally NOT rewritten:\n * - `npm install`, `npm install <pkg>`, `npm install --save-dev <pkg>` —\n * subtle flag mapping (e.g. `--save-dev` → `-D`) and edge cases like\n * `--save-exact` / `--save-optional`. Better to leave for an operator\n * eyeball than to silently mis-translate.\n * - Hyphenated identifiers like `npm-check-updates` (word-boundary protected).\n * - `concurrently \"npm:scriptName\"` shorthand syntax — it isn't actually\n * running npm; it's a concurrently-specific script reference.\n */\nexport function rewriteScriptForPnpm(script: string): string {\n let out = script;\n // `npm run <name>` → `pnpm run <name>`. \\b before npm prevents\n // matching inside hyphenated identifiers. Lookahead `(?=\\s)` after run\n // ensures we don't match `runner`.\n out = out.replace(/\\bnpm run(?=\\s)/g, \"pnpm run\");\n // `npx ` → `pnpm dlx `. \\b before npx prevents matching `npx-something`.\n out = out.replace(/\\bnpx(?=\\s)/g, \"pnpm dlx\");\n return out;\n}\n\n/**\n * Rewrite every entry in a package.json `scripts` map. Returns the new\n * map alongside a count of scripts that were actually changed.\n */\nexport function rewriteScriptsForPnpm(scripts: Record<string, string>): {\n scripts: Record<string, string>;\n changedCount: number;\n} {\n const next: Record<string, string> = {};\n let changedCount = 0;\n for (const [name, value] of Object.entries(scripts)) {\n const rewritten = rewriteScriptForPnpm(value);\n next[name] = rewritten;\n if (rewritten !== value) changedCount++;\n }\n return { scripts: next, changedCount };\n}\n","import { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../types.js\";\nimport { readPackageJson, writePackageJson, bumpDep, type PackageJsonLike } from \"../util/pkg.js\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport { selfCaretRange } from \"../util/self-version.js\";\nimport { baselineVersions } from \"../configs/baseline-versions.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type OnboardAudit = \"lighthouse\" | \"a11y\";\n\nexport type OnboardOptions = {\n spawn?: SpawnFn;\n /** Which audit-related deps to ensure. Defaults to all known audits. */\n audits?: OnboardAudit[];\n /** Version range to pin for @reddoorla/maintenance. Defaults to a caret\n * range against this package's own version at runtime — no manual\n * syncing required at each minor bump. */\n packageVersion?: string;\n};\n\nconst PACKAGE_NAME = \"@reddoorla/maintenance\";\n\nconst AUDIT_DEP_NAMES: Record<OnboardAudit, string[]> = {\n lighthouse: [\"@lhci/cli\"],\n a11y: [\"@playwright/test\", \"@axe-core/playwright\"],\n};\n\n/** Framework deps onboard ensures for every site, independent of which audits\n * are requested. The sync-configs svelte.config.js template does\n * `import adapter from \"@sveltejs/adapter-netlify\"`, so a site that lacks the\n * adapter declared can't build once configs are synced — onboard closes that\n * gap at the same time it adds the maintenance package. */\nconst FRAMEWORK_DEP_NAMES = [\"@sveltejs/adapter-netlify\"];\n\n/** Resolve framework dep versions from baselineVersions at module load so they\n * can't drift from the single source of truth — mirrors AUDIT_DEPS. Throws at\n * import time if a name is missing there (a programming error). */\nexport const FRAMEWORK_DEPS: Array<{ name: string; version: string }> = FRAMEWORK_DEP_NAMES.map(\n (name) => {\n const version = baselineVersions[name];\n if (!version) {\n throw new Error(\n `baseline-versions is missing framework dep \"${name}\" — add it to src/configs/baseline-versions.ts`,\n );\n }\n return { name, version };\n },\n);\n\n/** Look up each audit dep's version in baselineVersions at module load so\n * AUDIT_DEPS can't drift from the single source of truth across releases.\n * Throws at import time if baseline-versions is missing an audit dep —\n * which would be a programming error (every audit dep name above must\n * appear in baselineVersions). */\nexport const AUDIT_DEPS: Record<\n OnboardAudit,\n Array<{ name: string; version: string }>\n> = Object.fromEntries(\n (Object.entries(AUDIT_DEP_NAMES) as Array<[OnboardAudit, string[]]>).map(([audit, names]) => [\n audit,\n names.map((name) => {\n const version = baselineVersions[name];\n if (!version) {\n throw new Error(\n `baseline-versions is missing audit dep \"${name}\" — add it to src/configs/baseline-versions.ts`,\n );\n }\n return { name, version };\n }),\n ]),\n) as Record<OnboardAudit, Array<{ name: string; version: string }>>;\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isDeclared(pkg: PackageJsonLike, name: string): boolean {\n return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);\n}\n\ntype Plan = {\n pkg: PackageJsonLike;\n toAdd: Array<{ name: string; version: string }>;\n};\n\nexport async function onboard(site: Site, opts: OnboardOptions = {}): Promise<RecipeResult> {\n const spawn = opts.spawn ?? defaultSpawn;\n const audits = opts.audits ?? ([\"lighthouse\", \"a11y\"] as OnboardAudit[]);\n const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);\n\n return withRecipe<Plan>({\n name: \"onboard\",\n site,\n plan: async () => {\n // Pre-flight: site must already be on pnpm. We don't auto-convert here;\n // that's the convert-to-pnpm recipe's job, and combining them would\n // hide the package-manager transition inside a bigger PR.\n if (!(await exists(join(site.path, \"pnpm-lock.yaml\")))) {\n return {\n kind: \"failed\",\n notes: \"no pnpm-lock.yaml at site root — run convert-to-pnpm first\",\n };\n }\n\n const pkgPath = join(site.path, \"package.json\");\n const pkg = await readPackageJson(pkgPath);\n\n // Determine what's missing. Anything already declared (even at a wildly\n // different version) is left alone — onboard never downgrades.\n const toAdd: Array<{ name: string; version: string }> = [];\n if (!isDeclared(pkg, PACKAGE_NAME)) {\n toAdd.push({ name: PACKAGE_NAME, version: packageVersion });\n }\n for (const dep of FRAMEWORK_DEPS) {\n if (!isDeclared(pkg, dep.name)) toAdd.push(dep);\n }\n for (const audit of audits) {\n for (const dep of AUDIT_DEPS[audit]) {\n if (!isDeclared(pkg, dep.name)) toAdd.push(dep);\n }\n }\n\n if (toAdd.length === 0) {\n return {\n kind: \"noop\",\n notes: `site already has ${PACKAGE_NAME}, framework deps, and audit deps (${audits.join(\"+\")})`,\n };\n }\n return { kind: \"apply\", plan: { pkg, toAdd } };\n },\n apply: async ({ pkg, toAdd }, { commit, cwd }) => {\n const pkgPath = join(cwd, \"package.json\");\n let next: PackageJsonLike = pkg;\n for (const dep of toAdd) {\n next = bumpDep(next, dep.name, dep.version);\n }\n await writePackageJson(pkgPath, next);\n\n // Run pnpm install so the lockfile reflects the new deps before we commit.\n // Stream output — install on a real site can take 30s+.\n const installResult = await spawn(\"pnpm\", [\"install\"], { cwd, streaming: true });\n if (installResult.code !== 0) {\n return {\n kind: \"failed\",\n notes: `pnpm install failed (exit ${installResult.code})`,\n };\n }\n\n await commit(`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`);\n return {\n kind: \"ok\",\n notes: `Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(\", \")}`,\n };\n },\n });\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Read this package's own version at runtime so recipe defaults don't go\n * stale at each minor bump.\n *\n * Pass `import.meta.url` from the calling file. Walks UP from the caller\n * looking for the first `package.json` whose `name` matches this package\n * (`@reddoorla/maintenance`). The older \"two levels up\" shortcut held for\n * `src/X/Y.ts` and `dist/cli/bin.js` (both happen to be 2 dirs deep) but\n * broke for `dist/index.js` (only 1 dir deep) — silently returned \"0.0.0\"\n * and pinned consumers to `^0.0.0`. Same bug class as the 0.10.1 bundled-\n * assets ENOENT (2026-05-27). Walk-up is robust regardless of bundling\n * layout.\n *\n * Returns \"0.0.0\" if no matching package.json is reachable (defensive\n * fallback; callers should treat that as a signal to either override\n * explicitly or fail loudly).\n */\nexport function selfPackageVersion(callerImportMetaUrl: string): string {\n try {\n let dir = dirname(fileURLToPath(callerImportMetaUrl));\n while (true) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n const raw = readFileSync(candidate, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n // Only accept OUR package.json — keep walking past random ancestor\n // package.jsons (the consumer's own, anything in node_modules) that\n // happen to sit above the bundle.\n if (pkg.name === \"@reddoorla/maintenance\") {\n return pkg.version ?? \"0.0.0\";\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return \"0.0.0\";\n dir = parent;\n }\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Caret-pinned range against this package's own version: e.g. \"^0.6.2\". */\nexport function selfCaretRange(callerImportMetaUrl: string): string {\n return `^${selfPackageVersion(callerImportMetaUrl)}`;\n}\n","import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { A11Y_FIXTURES_PAGE_RELATIVE, A11Y_FIXTURES_PAGE_TEMPLATE } from \"./template.js\";\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route\n * doesn't already exist. The hardcoded URL in `src/configs/lighthouse.ts` +\n * `src/configs/playwright-a11y.ts` targets this path — newly-onboarded sites\n * need the route to exist for either audit to pass. Operator edits to an\n * existing page are never clobbered (noop on existing file).\n */\nexport async function a11yFixturesPage(site: Site): Promise<RecipeResult> {\n const target = join(site.path, A11Y_FIXTURES_PAGE_RELATIVE);\n return withRecipe<{ target: string }>({\n name: \"a11y-fixtures-page\",\n site,\n plan: async () => {\n if (await fileExists(target)) {\n return { kind: \"noop\", notes: `${A11Y_FIXTURES_PAGE_RELATIVE} already exists` };\n }\n return { kind: \"apply\", plan: { target } };\n },\n apply: async (planned, { commit }) => {\n await mkdir(dirname(planned.target), { recursive: true });\n await writeFile(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, \"utf-8\");\n await commit(\"feat: add /dev/a11y-fixtures starter route\");\n return { kind: \"ok\" };\n },\n });\n}\n","/** Relative path inside a site where the a11y fixtures route lives. The\n * hardcoded URL in `src/configs/lighthouse.ts` + `src/configs/playwright-a11y.ts`\n * is `/dev/a11y-fixtures`, so a SvelteKit `+page.svelte` here resolves. */\nexport const A11Y_FIXTURES_PAGE_RELATIVE = \"src/routes/dev/a11y-fixtures/+page.svelte\";\n\n/** Stub `+page.svelte` for newly-onboarded sites. Generic on purpose —\n * landmarks, heading hierarchy, and a relative link cover the axe-core +\n * lhci defaults without committing the operator to any specific fixture\n * shape. Replace with site-specific patterns over time. */\nexport const A11Y_FIXTURES_PAGE_TEMPLATE = `<svelte:head>\n <title>a11y fixtures — Reddoor</title>\n <meta\n name=\"description\"\n content=\"Reddoor accessibility fixtures — semantic landmarks, heading hierarchy, and a stable target for @lhci/cli and Playwright + axe-core coverage. Not linked from the public site.\"\n />\n</svelte:head>\n\n<main>\n <header>\n <h1>Accessibility fixtures</h1>\n <p>\n This page exists so <code>@lhci/cli</code> and Playwright + axe-core have a\n stable target with predictable a11y characteristics. It is not linked from\n the public site.\n </p>\n </header>\n\n <section aria-labelledby=\"landmarks-heading\">\n <h2 id=\"landmarks-heading\">Landmarks</h2>\n <p>\n A single <code>main</code> wraps the page; sections each declare\n <code>aria-labelledby</code> matched to their heading id so screen readers\n and axe both see a clean outline.\n </p>\n </section>\n\n <section aria-labelledby=\"links-heading\">\n <h2 id=\"links-heading\">Links</h2>\n <p>\n <a href=\"/\">Back to home</a> — relative link with descriptive visible text,\n so no <code>aria-label</code> override is needed.\n </p>\n </section>\n</main>\n`;\n","import type { AuditResult, RecipeResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { convertToPnpm } from \"./convert-to-pnpm.js\";\nimport { onboard } from \"./onboard.js\";\nimport { syncConfigs } from \"./sync-configs.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport { runAudits } from \"../audits/index.js\";\n\nexport type InitStepResult =\n | { kind: \"recipe\"; result: RecipeResult }\n | { kind: \"audit\"; results: AuditResult[] }\n | { kind: \"error\"; message: string };\n\nexport type InitStep = {\n name: string;\n run: (site: Site) => Promise<InitStepResult>;\n};\n\nexport type InitResult = {\n site: string;\n steps: Array<{ name: string; result: InitStepResult }>;\n /** True if every step ran; false if an `error` or `failed` recipe result\n * short-circuited the chain. `noop` recipes do not break completeness. */\n complete: boolean;\n};\n\nexport type InitOptions = {\n /** Override the default step list. Tests inject mocked steps; production\n * code relies on the default. */\n steps?: InitStep[];\n};\n\nfunction recipeStep(name: string, fn: (site: Site) => Promise<RecipeResult>): InitStep {\n return {\n name,\n run: async (site) => ({ kind: \"recipe\", result: await fn(site) }),\n };\n}\n\n/** convert-to-pnpm → onboard → sync-configs → svelte-codemods →\n * a11y-fixtures-page → audit. Order is deliberate — every step depends on\n * the prior one's output (pnpm before onboard's installs, onboard's deps\n * before sync-configs writes lighthouserc, fixtures page before audit\n * actually has a route to hit). */\nexport const DEFAULT_INIT_STEPS: InitStep[] = [\n recipeStep(\"convert-to-pnpm\", convertToPnpm),\n recipeStep(\"onboard\", onboard),\n recipeStep(\"sync-configs\", syncConfigs),\n recipeStep(\"svelte-codemods\", svelteCodemods),\n recipeStep(\"a11y-fixtures-page\", a11yFixturesPage),\n {\n name: \"audit\",\n run: async (site) => ({ kind: \"audit\", results: await runAudits(site) }),\n },\n];\n\n/**\n * One-shot guided onboarding. Runs the default step sequence against a\n * site, collecting per-step results into an InitResult. Each underlying\n * recipe still creates its own branch — init is a thin orchestrator, not\n * a branch-collapser; the operator ends up with one stack of branches per\n * mutated step (recipes that noop don't branch).\n *\n * Stops the chain on the first uncaught error or `failed` recipe result.\n * `noop` results are expected (e.g. running init twice) and continue the\n * chain. The final audit pass runs if no prior step errored.\n */\nexport async function init(site: Site, opts: InitOptions = {}): Promise<InitResult> {\n const steps = opts.steps ?? DEFAULT_INIT_STEPS;\n const out: Array<{ name: string; result: InitStepResult }> = [];\n\n for (const step of steps) {\n let result: InitStepResult;\n try {\n result = await step.run(site);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n out.push({ name: step.name, result: { kind: \"error\", message } });\n return { site: siteLabel(site), steps: out, complete: false };\n }\n out.push({ name: step.name, result });\n if (result.kind === \"recipe\" && result.result.status === \"failed\") {\n return { site: siteLabel(site), steps: out, complete: false };\n }\n }\n\n return { site: siteLabel(site), steps: out, complete: true };\n}\n","import type { RecipeName } from \"../types.js\";\nimport { syncConfigs, type SyncConfigsOptions } from \"./sync-configs.js\";\nimport { bumpDeps, type BumpDepsOptions } from \"./bump-deps.js\";\nimport { upgradeSvelte4to5, type UpgradeSvelte4to5Options } from \"./svelte-5/index.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { convertToPnpm, type ConvertToPnpmOptions } from \"./convert-to-pnpm.js\";\nimport { onboard, type OnboardOptions, type OnboardAudit } from \"./onboard.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport {\n init,\n DEFAULT_INIT_STEPS,\n type InitOptions,\n type InitResult,\n type InitStep,\n type InitStepResult,\n} from \"./init.js\";\n\nexport {\n syncConfigs,\n bumpDeps,\n upgradeSvelte4to5,\n svelteCodemods,\n convertToPnpm,\n onboard,\n a11yFixturesPage,\n init,\n DEFAULT_INIT_STEPS,\n};\nexport type {\n SyncConfigsOptions,\n BumpDepsOptions,\n UpgradeSvelte4to5Options,\n ConvertToPnpmOptions,\n OnboardOptions,\n OnboardAudit,\n InitOptions,\n InitResult,\n InitStep,\n InitStepResult,\n};\n\nexport const ALL_RECIPE_NAMES: RecipeName[] = [\n \"sync-configs\",\n \"bump-deps\",\n \"svelte-4-to-5\",\n \"svelte-codemods\",\n \"convert-to-pnpm\",\n \"onboard\",\n \"a11y-fixtures-page\",\n \"self-updating\",\n \"init\",\n];\n\nexport function isRecipeName(value: string): value is RecipeName {\n return (ALL_RECIPE_NAMES as string[]).includes(value);\n}\n","import { basename } from \"node:path\";\nimport type { InventoryProvider, Site } from \"../types.js\";\n\nexport type LocalPathOptions = {\n name?: string;\n};\n\nexport function localPath(path: string, opts: LocalPathOptions = {}): InventoryProvider {\n const site: Site = { path, name: opts.name ?? basename(path) };\n return async () => [site];\n}\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute } from \"node:path\";\nimport type { InventoryProvider, Site } from \"../types.js\";\n\nfunction validate(raw: unknown): Site[] {\n if (!Array.isArray(raw)) {\n throw new Error(\"inventory JSON must be an array of sites\");\n }\n return raw.map((entry, i) => {\n if (!entry || typeof entry !== \"object\") {\n throw new Error(`inventory entry ${i} is not an object`);\n }\n const e = entry as Record<string, unknown>;\n if (typeof e.path !== \"string\" || e.path.length === 0) {\n throw new Error(`inventory entry ${i} is missing required field: path`);\n }\n if (!isAbsolute(e.path)) {\n throw new Error(\n `inventory entry ${i}: path must be absolute (got \"${e.path}\"). ` +\n `Relative paths are rejected so cwd at invocation can't change which site is targeted.`,\n );\n }\n const site: Site = { path: e.path };\n if (typeof e.name === \"string\") site.name = e.name;\n if (typeof e.repoUrl === \"string\") site.repoUrl = e.repoUrl;\n if (typeof e.meta === \"object\" && e.meta !== null) {\n site.meta = e.meta as Record<string, unknown>;\n }\n return site;\n });\n}\n\nexport function fromJsonFile(path: string): InventoryProvider {\n return async () => {\n const raw = JSON.parse(await readFile(path, \"utf-8\")) as unknown;\n return validate(raw);\n };\n}\n","import type { FieldSet } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport type { LighthouseScores } from \"../types.js\";\n\nexport const WEBSITES_TABLE = \"Websites\";\n\nexport type Frequency = \"None\" | \"Monthly\" | \"Quarterly\" | \"Yearly\";\n\nexport type Status =\n | \"in development\"\n | \"launch period\"\n | \"maintenance\"\n | \"hosting\"\n | \"probably not our problem\"\n | \"deprecated\";\n\nexport type WebsiteRow = {\n id: string;\n name: string;\n url: string;\n status: Status | null;\n pointOfContact: string | null;\n maintenanceFreq: Frequency;\n testingFreq: Frequency;\n /** Last manually-recorded maintenance day (used as fallback when no Reports row exists). */\n maintenanceDay: string | null;\n testingDay: string | null;\n ga4PropertyId: string | null;\n /** Operator-supplied query for the Google search-presence check (e.g. the business name).\n * Null = no query set → the check is skipped for this site. */\n searchQuery: string | null;\n /** Explicit Search Console property for this site (`sc-domain:...` or `https://.../`).\n * Null = auto-resolve from the SA's visible properties by host. */\n searchConsoleProperty: string | null;\n /** GitHub repo identity as `owner/repo`. Null = no git wiring → self-update ops skip\n * (or, for local runs, fall back to the checkout's origin remote). */\n gitRepo: string | null;\n reportRecipientsTo: string | null;\n reportRecipientsCc: string | null;\n /** First attachment in the Header image field (Airtable's signed URL — fetch before expiry). */\n headerImage: { url: string; filename: string; type: string } | null;\n /** Lighthouse \"current state\" snapshot, kept fresh by `audit lighthouse --write-airtable`. */\n pScore: number | null;\n rScore: number | null;\n bpScore: number | null;\n seoScore: number | null;\n /** ISO timestamp set by `audit lighthouse --write-airtable` when scores were last refreshed. */\n lastLighthouseAuditAt: string | null;\n /** Last-known counts from non-lighthouse audits, written by\n * `audit --write-airtable`. `null` = never audited (or this audit\n * type was skipped on the last run). 0 = audited, clean. */\n a11yViolations: number | null;\n depsDrifted: number | null;\n depsMajorBehind: number | null;\n securityVulnsCritical: number | null;\n securityVulnsHigh: number | null;\n securityVulnsModerate: number | null;\n securityVulnsLow: number | null;\n /** Shared-link gate for the per-site dashboard at /s/<slug>?t=<token>.\n * Operator generates and pastes into the \"Dashboard Token\" Airtable field;\n * rotated by replacing the value. `null` means the site has no dashboard\n * link yet — the function returns 403 with a clear setup message. */\n dashboardToken: string | null;\n};\n\nexport function siteSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nexport function mapRow(rec: { id: string; fields: Record<string, unknown> }): WebsiteRow {\n const f = rec.fields;\n const attachments =\n (f[\"Header image\"] as Array<{ url: string; filename: string; type: string }> | undefined) ?? [];\n const header = attachments[0] ?? null;\n return {\n id: rec.id,\n name: String(f[\"Name\"] ?? \"\"),\n url: String(f[\"url\"] ?? \"\"),\n status: (f[\"Status\"] as Status | undefined) ?? null,\n pointOfContact: (f[\"point of contact\"] as string | undefined) ?? null,\n maintenanceFreq: ((f[\"maintenence freq\"] as string | undefined) ?? \"None\") as Frequency,\n testingFreq: ((f[\"testing freq\"] as string | undefined) ?? \"None\") as Frequency,\n maintenanceDay: (f[\"maintenance day\"] as string | undefined) ?? null,\n testingDay: (f[\"testing day\"] as string | undefined) ?? null,\n ga4PropertyId: (f[\"GA4 property ID\"] as string | undefined) ?? null,\n searchQuery: (f[\"Search query\"] as string | undefined) ?? null,\n searchConsoleProperty: (f[\"Search Console property\"] as string | undefined) ?? null,\n gitRepo: (f[\"Git repo\"] as string | undefined) ?? null,\n reportRecipientsTo: (f[\"Report recipients (To)\"] as string | undefined) ?? null,\n reportRecipientsCc: (f[\"Report recipients (CC)\"] as string | undefined) ?? null,\n headerImage: header,\n pScore: (f[\"pScore\"] as number | undefined) ?? null,\n rScore: (f[\"rScore\"] as number | undefined) ?? null,\n bpScore: (f[\"bpScore\"] as number | undefined) ?? null,\n seoScore: (f[\"seoScore\"] as number | undefined) ?? null,\n lastLighthouseAuditAt: (f[\"Last lighthouse audit at\"] as string | undefined) ?? null,\n a11yViolations: (f[\"A11y Violations\"] as number | undefined) ?? null,\n depsDrifted: (f[\"Deps Drifted\"] as number | undefined) ?? null,\n depsMajorBehind: (f[\"Deps Major Behind\"] as number | undefined) ?? null,\n securityVulnsCritical: (f[\"Security Vulns Critical\"] as number | undefined) ?? null,\n securityVulnsHigh: (f[\"Security Vulns High\"] as number | undefined) ?? null,\n securityVulnsModerate: (f[\"Security Vulns Moderate\"] as number | undefined) ?? null,\n securityVulnsLow: (f[\"Security Vulns Low\"] as number | undefined) ?? null,\n dashboardToken: (() => {\n const raw = f[\"Dashboard Token\"];\n if (typeof raw !== \"string\") return null;\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n })(),\n };\n}\n\nexport async function listWebsites(base: AirtableBase): Promise<WebsiteRow[]> {\n const out: WebsiteRow[] = [];\n await base(WEBSITES_TABLE)\n .select({ pageSize: 100 })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\nexport async function getWebsiteBySlug(\n base: AirtableBase,\n slug: string,\n): Promise<WebsiteRow | null> {\n const all = await listWebsites(base);\n return all.find((w) => siteSlug(w.name) === slug) ?? null;\n}\n\n/**\n * Write the four Lighthouse scores + a refreshed-at timestamp onto a Websites row.\n * Called by `audit lighthouse --write-airtable` after a successful audit run, so\n * the operator never has to paste numbers manually before drafting a report.\n */\nexport async function updateScores(\n base: AirtableBase,\n recordId: string,\n scores: LighthouseScores,\n): Promise<void> {\n const fields: FieldSet = {\n pScore: scores.performance,\n rScore: scores.accessibility,\n bpScore: scores.bestPractices,\n seoScore: scores.seo,\n \"Last lighthouse audit at\": new Date().toISOString(),\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist a11y violation count. */\nexport async function updateA11yCounts(\n base: AirtableBase,\n recordId: string,\n counts: { violations: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"A11y Violations\": counts.violations,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist deps drift counts. */\nexport async function updateDepsCounts(\n base: AirtableBase,\n recordId: string,\n counts: { drifted: number; majorBehind: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"Deps Drifted\": counts.drifted,\n \"Deps Major Behind\": counts.majorBehind,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n\n/** Persist security vulnerability counts by severity. */\nexport async function updateSecurityCounts(\n base: AirtableBase,\n recordId: string,\n counts: { critical: number; high: number; moderate: number; low: number },\n): Promise<void> {\n const fields: FieldSet = {\n \"Security Vulns Critical\": counts.critical,\n \"Security Vulns High\": counts.high,\n \"Security Vulns Moderate\": counts.moderate,\n \"Security Vulns Low\": counts.low,\n };\n await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);\n}\n","import type { Site, InventoryProvider } from \"../types.js\";\nimport type { AirtableBase } from \"../reports/airtable/client.js\";\nimport { listWebsites, siteSlug } from \"../reports/airtable/websites.js\";\n\nexport type AirtableInventoryOptions = {\n /**\n * Local workdir to compute each site's path as `{workdir}/{slug}`.\n * Defaults to REDDOOR_FLEET_WORKDIR env var if not provided.\n * Airtable doesn't store local checkout paths, so this is required.\n */\n workdir?: string;\n};\n\n/**\n * Read sites from the Airtable Websites table as an InventoryProvider.\n * Each row becomes one Site; `path` is computed as `{workdir}/{slug}`.\n * Sites where BOTH maintenance freq AND testing freq are \"None\" are excluded\n * (they're inactive — no scheduled audits or reports).\n *\n * Note: `repoUrl` is set to the production URL (Websites.url). For sites\n * cloned via `--workdir` semantics this is wrong — the convention should be\n * tightened (e.g. add a `repo` field to Websites) when fleet-clone-from-\n * airtable becomes a real flow. For local audits where the site is already\n * checked out at `path`, the `repoUrl` is unused.\n */\nexport function fromAirtableBase(\n base: AirtableBase,\n opts: AirtableInventoryOptions = {},\n): InventoryProvider {\n return async (): Promise<Site[]> => {\n const workdir = opts.workdir ?? process.env.REDDOOR_FLEET_WORKDIR;\n if (!workdir) {\n throw new Error(\n \"fromAirtableBase requires `workdir` option or REDDOOR_FLEET_WORKDIR env (sites need a local path)\",\n );\n }\n const websites = await listWebsites(base);\n return websites\n .filter((w) => w.maintenanceFreq !== \"None\" || w.testingFreq !== \"None\")\n .map((w) => {\n const slug = siteSlug(w.name);\n const site: Site = {\n path: `${workdir}/${slug}`,\n name: slug,\n meta: { airtableRowId: w.id, displayName: w.name },\n };\n if (w.url) site.repoUrl = w.url;\n if (w.gitRepo) site.gitRepo = w.gitRepo;\n return site;\n });\n };\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { ReportType, LighthouseScores } from \"./types.js\";\nimport { renderReportHtml } from \"./render.js\";\nimport { siteSlug } from \"./airtable/websites.js\";\nimport type { WebsiteRow } from \"./airtable/websites.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport { createDraft, setDraftReady, listReportsForSite } from \"./airtable/reports.js\";\nimport { uploadAttachment } from \"./airtable/attachments.js\";\nimport type { AirtableBase } from \"./airtable/client.js\";\nimport { readGaConfig } from \"./ga/config.js\";\nimport { fetchPeriodUsers } from \"./ga/client.js\";\nimport { fetchSearchPresence } from \"./search/client.js\";\nimport type { SearchPresence } from \"./search/client.js\";\n\nexport type DraftOptions = {\n /** Where to write the local preview HTML when `previewOnly`. Defaults to `reports/<slug>/draft.html`. */\n previewPath?: string;\n /** If true: render locally only, never touch Airtable. */\n previewOnly?: boolean;\n};\n\nexport type DraftResult = {\n /** null when previewOnly. */\n reportRow: ReportRow | null;\n /** Path to the local preview file (only set when previewOnly). */\n htmlPath: string | null;\n /** Always present — the rendered HTML string. */\n html: string;\n};\n\nfunction scoresFromWebsite(siteRow: WebsiteRow): LighthouseScores {\n const { pScore, rScore, bpScore, seoScore } = siteRow;\n if (pScore === null || rScore === null || bpScore === null || seoScore === null) {\n throw new Error(\n `Site '${siteRow.name}' is missing one or more Lighthouse scores on the Websites row (pScore, rScore, bpScore, seoScore). ` +\n `Run 'reddoor-maint audit lighthouse' from the site's checkout and paste the four numbers into Airtable, then retry.`,\n );\n }\n return { performance: pScore, accessibility: rScore, bestPractices: bpScore, seo: seoScore };\n}\n\nfunction daysAgo(today: Date, n: number): Date {\n // UTC accessors to stay TZ-consistent with `due.ts` (and avoid landing\n // Airtable's `Period start` on a different calendar day than the operator\n // expects on late-night runs near a month boundary). See morning brief\n // 2026-05-29 (M1) for context.\n const out = new Date(today);\n out.setUTCDate(out.getUTCDate() - n);\n return out;\n}\n\nexport async function draftReportForSite(\n base: AirtableBase | null,\n siteRow: WebsiteRow,\n reportType: ReportType,\n options: DraftOptions = {},\n): Promise<DraftResult> {\n const scores = scoresFromWebsite(siteRow);\n\n const today = new Date();\n const slug = siteSlug(siteRow.name);\n\n const periodStart =\n base !== null ? await derivePeriodStart(base, siteRow, reportType, today) : daysAgo(today, 30);\n\n const periodEnd = today;\n const completedOn = today;\n const lastTestedDate =\n reportType === \"Maintenance\" && siteRow.testingDay ? new Date(siteRow.testingDay) : null;\n\n // GA enrichment (real path only). Soft-fail: any GA problem leaves the numbers null so\n // the draft still proceeds (operator fills them manually) — GA is an enhancement, not a\n // gate. Rendered with the fetched numbers so the review HTML matches the Airtable fields.\n const gaUsers = base !== null ? await fetchGaUsers(siteRow, periodStart, periodEnd) : null;\n const search = base !== null ? await fetchSearch(siteRow, periodStart, periodEnd) : null;\n\n const cidName = `${slug}-header`;\n const { html } = await renderReportHtml({\n siteName: siteRow.name,\n siteUrl: siteRow.url,\n reportType,\n completedOn,\n lighthouse: scores,\n gaUsersCurrent: gaUsers?.current,\n gaUsersPrevious: gaUsers?.previous,\n searchPosition: search?.foundOnPage1 ? (search.position ?? undefined) : undefined,\n lastTestedDate,\n commentary: null,\n headerImageCid: cidName,\n });\n\n if (options.previewOnly) {\n const path = options.previewPath ?? `reports/${slug}/draft.html`;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, html, \"utf-8\");\n return { reportRow: null, htmlPath: path, html };\n }\n\n if (base === null) throw new Error(\"base required when previewOnly=false\");\n\n const reportId = `${siteRow.name} — ${reportType} — ${periodEnd.toISOString().slice(0, 10)}`;\n const created = await createDraft(base, {\n reportId,\n siteId: siteRow.id,\n reportType,\n periodStart,\n periodEnd,\n completedOn,\n lighthouse: scores,\n lastTestedDate,\n ...(gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {}),\n ...(search ? { searchFoundPage1: search.foundOnPage1 } : {}),\n ...(search?.foundOnPage1 && search.position !== null\n ? { searchPosition: search.position }\n : {}),\n });\n\n const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;\n await uploadAttachment(created.id, \"Rendered HTML\", html, htmlFilename, \"text/html\");\n await setDraftReady(base, created.id, true);\n\n return { reportRow: created, htmlPath: null, html };\n}\n\n/**\n * Fetch GA \"Users\" for the period, soft-failing to null. Returns null (no enrichment) when\n * GA isn't configured (`GA_SUBJECT` unset), the site has no GA4 property ID, or the GA API\n * errors — logging a one-line warning in the error case. Never throws, so a GA problem can\n * never block a draft; the operator can always enter the numbers by hand.\n */\nasync function fetchGaUsers(\n siteRow: WebsiteRow,\n periodStart: Date,\n periodEnd: Date,\n): Promise<{ current: number; previous: number } | null> {\n const cfg = readGaConfig();\n if (!cfg || !siteRow.ga4PropertyId) return null;\n try {\n return await fetchPeriodUsers(\n { propertyId: siteRow.ga4PropertyId, subject: cfg.subject, keyPath: cfg.keyPath },\n periodStart,\n periodEnd,\n );\n } catch (e) {\n console.warn(`⚠ GA skipped for ${siteRow.name}: ${(e as Error).message}`);\n return null;\n }\n}\n\n/**\n * Fetch the site's Google search presence for the period, soft-failing to null. Returns null\n * when GA/SA isn't configured (`readGaConfig()` null — search shares the SA credentials), the\n * site has no `searchQuery`, or the Search Console API errors (logging a one-line warning).\n * Never throws, so a search problem can never block a draft.\n */\nasync function fetchSearch(\n siteRow: WebsiteRow,\n periodStart: Date,\n periodEnd: Date,\n): Promise<SearchPresence | null> {\n const cfg = readGaConfig();\n if (!cfg || !siteRow.searchQuery) return null;\n try {\n return await fetchSearchPresence(\n {\n keyPath: cfg.keyPath,\n subject: cfg.subject,\n property: siteRow.searchConsoleProperty ?? undefined,\n host: siteRow.url,\n query: siteRow.searchQuery,\n },\n periodStart,\n periodEnd,\n );\n } catch (e) {\n console.warn(`⚠ Search presence skipped for ${siteRow.name}: ${(e as Error).message}`);\n return null;\n }\n}\n\nasync function derivePeriodStart(\n base: AirtableBase,\n siteRow: WebsiteRow,\n reportType: ReportType,\n today: Date,\n): Promise<Date> {\n const prior = await listReportsForSite(base, siteRow.id);\n const sameType = prior\n .filter((r) => r.reportType === reportType && r.periodEnd)\n .map((r) => r.periodEnd!)\n .sort();\n const latest = sameType[sameType.length - 1];\n return latest ? new Date(latest) : daysAgo(today, 30);\n}\n","import mjml2html from \"mjml\";\nimport type { ReportData } from \"./types.js\";\nimport { buildMjml } from \"./maintenance-email/template.js\";\n\nexport type RenderResult = {\n html: string;\n warnings: Array<{ line: number; message: string }>;\n};\n\nexport async function renderReportHtml(data: ReportData): Promise<RenderResult> {\n const mjml = buildMjml(data);\n const out = await mjml2html(mjml, { validationLevel: \"strict\" });\n return { html: out.html, warnings: out.errors ?? [] };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const CHECK_CID = \"rd-check-png\";\nexport const BLURRED_CID = \"rd-blurred-tests-jpg\";\n\nexport type BundledImage = {\n bytes: Uint8Array;\n contentType: string;\n cid: string;\n filename: string;\n};\n\n// Walk up from the current module's URL looking for the assets dir in either\n// the dev layout (src/reports/maintenance-email/assets/) or the published\n// layout (dist/reports/maintenance-email/assets/). REQUIRED because tsup\n// inlines this module into dist/cli/bin.js — so `import.meta.url`-based\n// sibling resolution looks in dist/cli/ for the PNGs and fails with ENOENT.\n// Regression that shipped in 0.10.0–0.10.1; tests passed in dev because\n// vitest evaluates the source file where import.meta.url is already correct.\nlet cachedAssetsDir: string | null = null;\nfunction resolveAssetsDir(): string {\n if (cachedAssetsDir) return cachedAssetsDir;\n let dir = dirname(fileURLToPath(import.meta.url));\n while (true) {\n // Source layout preferred — single source of truth in the workspace\n // and the only one present in dev/test environments.\n const srcCandidate = join(dir, \"src\", \"reports\", \"maintenance-email\", \"assets\", \"check.png\");\n if (existsSync(srcCandidate)) {\n cachedAssetsDir = dirname(srcCandidate);\n return cachedAssetsDir;\n }\n // Published layout — only `dist/` ships per package.json#files, so\n // consumers fall through to here.\n const distCandidate = join(dir, \"dist\", \"reports\", \"maintenance-email\", \"assets\", \"check.png\");\n if (existsSync(distCandidate)) {\n cachedAssetsDir = dirname(distCandidate);\n return cachedAssetsDir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(\n `loadBundledImages: could not locate maintenance-email assets dir by walking up from ${fileURLToPath(import.meta.url)}. Checked both src/ and dist/ layouts.`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Read the bundled image bytes from disk. Both Maintenance and Testing\n * variants reference `check.png`; only the Maintenance variant references\n * `blurredTests.jpg`.\n */\nexport async function loadBundledImages(): Promise<{\n check: BundledImage;\n blurred: BundledImage;\n}> {\n const assetsDir = resolveAssetsDir();\n const [check, blurred] = await Promise.all([\n readFile(join(assetsDir, \"check.png\")),\n readFile(join(assetsDir, \"blurredTests.jpg\")),\n ]);\n return {\n check: {\n bytes: new Uint8Array(check),\n contentType: \"image/png\",\n cid: CHECK_CID,\n filename: \"check.png\",\n },\n blurred: {\n bytes: new Uint8Array(blurred),\n contentType: \"image/jpeg\",\n cid: BLURRED_CID,\n filename: \"blurredTests.jpg\",\n },\n };\n}\n","import type { ReportData } from \"../types.js\";\nimport { CHECK_CID, BLURRED_CID } from \"./assets/index.js\";\n\n// Bundled images: shipped in dist/ via tsup onSuccess copy, attached inline via\n// CID by orchestrate.ts at send time. No external CDN dependency.\nconst CHECK_PNG = `cid:${CHECK_CID}`;\nconst BLURRED_TESTS = `cid:${BLURRED_CID}`;\n\nfunction fmtDate(d: Date | null): string {\n if (!d) return \"\";\n // Airtable date fields are wall-clock YYYY-MM-DD strings parsed as UTC midnight.\n // Use UTC accessors so the rendered date matches what the operator entered.\n // US format: MM.DD.YYYY (Reddoor is Texas-based, clients are US).\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const yyyy = d.getUTCFullYear();\n return `${mm}.${dd}.${yyyy}`;\n}\n\nfunction fmtUsers(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n/**\n * Escape operator/site-controlled strings before interpolating into the MJML markup.\n * MJML parses as XML with `validationLevel: \"strict\"`, so a raw `&`, `<`, `>`, or `\"`\n * in a site name (e.g. \"Brown & Co\"), URL, or commentary throws at render time and blocks\n * the send. Apply to every interpolation of siteName / siteUrl / commentary.\n */\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nconst TREND_UP = \"#2E7D32\"; // positive green — growth reads as good\nconst TREND_NEUTRAL = \"#757575\"; // muted grey — dips/flat aren't failures (and brand red is reserved)\n\nfunction trendText(color: string, text: string): string {\n return `<mj-text color=\"${color}\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">${text}</mj-text>`;\n}\n\n/**\n * The line under \"{N} Users\": a directional trend vs the previous period when both numbers\n * are real, else a graceful fallback. `undefined` means GA was unavailable (distinct from a\n * real 0). Up = green; down/flat = muted grey (a traffic dip isn't a failure).\n */\nfunction analyticsTrendLine(cur: number | undefined, prev: number | undefined): string {\n if (cur === undefined || prev === undefined) {\n // GA unavailable for one/both — show the prior count if we have it, else an em dash.\n return trendText(TREND_NEUTRAL, `Last Period: ${prev !== undefined ? fmtUsers(prev) : \"—\"}`);\n }\n if (prev === 0) {\n return cur > 0\n ? trendText(TREND_UP, \"▲ New this period (0 last period)\")\n : trendText(TREND_NEUTRAL, \"Last Period: 0\");\n }\n const pct = Math.round(((cur - prev) / prev) * 100);\n const range = `(${fmtUsers(prev)} → ${fmtUsers(cur)})`;\n if (pct > 0) return trendText(TREND_UP, `▲ ${pct}% vs last period ${range}`);\n if (pct < 0) return trendText(TREND_NEUTRAL, `▼ ${Math.abs(pct)}% vs last period ${range}`);\n return trendText(TREND_NEUTRAL, `No change vs last period (${fmtUsers(prev)})`);\n}\n\nfunction maintenanceChecksSection(searchPosition?: number): string {\n const googleLabel =\n searchPosition !== undefined ? `Page 1 Google Result (#${searchPosition})` : \"Google Indexed\";\n const rows = [\n \"Reviewed Logs\",\n \"CMS Checked\",\n \"DNS Checked\",\n googleLabel,\n \"Reviewed Certificate\",\n \"Security Updates\",\n ];\n return rows\n .map(\n (label, i) => `\n <mj-section background-color=\"white\" padding=\"0px\"${i === rows.length - 1 ? ' padding-bottom=\"36px\"' : \"\"}>\n <mj-group>\n <mj-column padding-left=\"0px\" width=\"90%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"}>\n <mj-text height=\"25px\" padding-left=\"0px\" color=\"#757575\" padding-top=\"20px\" padding-bottom=\"7.5px\" font-size=\"16px\">${label}</mj-text>\n </mj-column>\n <mj-column width=\"10%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"} padding-top=\"15px\">\n <mj-image align=\"right\" padding-right=\"0px\" width=\"20px\" height=\"20px\" padding-top=\"2.5px\" padding-bottom=\"15px\" src=\"${CHECK_PNG}\" />\n </mj-column>\n </mj-group>\n </mj-section>`,\n )\n .join(\"\");\n}\n\nfunction testingChecklistSection(): string {\n const rows = [\n \"Desktop Browsers\",\n \"Mobile Browsers\",\n \"Package Updates\",\n \"Bottlenecks\",\n \"Form Functionality\",\n \"Animation Functionality\",\n ];\n return rows\n .map(\n (label, i) => `\n <mj-section background-color=\"#F4F4F4\" padding=\"0px\"${i === rows.length - 1 ? ' padding-bottom=\"60px\"' : \"\"}>\n <mj-group>\n <mj-column width=\"90%\" padding-left=\"0px\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"}>\n <mj-text height=\"25px\" padding-left=\"0px\" color=\"#757575\" padding-top=\"20px\" padding-bottom=\"7.5px\" font-size=\"16px\">${label}</mj-text>\n </mj-column>\n <mj-column width=\"10%\"${i < rows.length - 1 ? ' border-bottom=\"solid #CCCCCC 1px\"' : \"\"} padding-top=\"15px\">\n <mj-image align=\"right\" padding-right=\"0px\" width=\"20px\" height=\"20px\" padding-top=\"2.5px\" padding-bottom=\"15px\" src=\"${CHECK_PNG}\" />\n </mj-column>\n </mj-group>\n </mj-section>`,\n )\n .join(\"\");\n}\n\nfunction maintenanceTestingPlaceholder(lastTested: Date | null): string {\n return `\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-image href=\"mailto:info@reddoorla.com\" src=\"${BLURRED_TESTS}\" />\n </mj-column>\n </mj-section>\n <mj-section background-color=\"#F4F4F4\" padding-top=\"0px\">\n <mj-column>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Last Tested: ${fmtDate(lastTested)}</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction testingIntroSection(): string {\n return `\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">TESTING</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Testing includes checks similar to those at launch: testing on common browsers and operating systems, at different screen sizes, and checking every function, and updating all packages for performance rather than just those needed for security.</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction commentarySection(text: string): string {\n return `\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"55px\">NOTES</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">${escapeXml(text).replace(/\\n/g, \"<br/>\")}</mj-text>\n </mj-column>\n </mj-section>`;\n}\n\nfunction hasHeaderDims(\n data: ReportData,\n): data is ReportData & { headerWidth: number; headerHeight: number; headerBgColor: string } {\n return Boolean(data.headerWidth && data.headerHeight && data.headerBgColor);\n}\n\nfunction headerImageTag(data: ReportData): string {\n const src = `cid:${data.headerImageCid}`;\n const alt = `${escapeXml(data.siteName)} maintenance report`;\n const href = escapeXml(data.siteUrl);\n // Reserve the box and show a matched placeholder while the image loads / if blocked.\n // Critically, we do NOT set an mj-image `height` — MJML would emit `height:<px>` while\n // keeping `width:100%`, locking the height while the width scales and distorting the\n // image at any rendered width != the design width (mobile, narrow panes). Instead the\n // image stays `height:auto` (proportional) and the box is reserved via `aspect-ratio`\n // in the head <mj-style> below (see headerStyleBlock). `container-background-color` is\n // the placeholder; the bare fallback (no dims, e.g. local preview) keeps today's behavior.\n if (hasHeaderDims(data)) {\n return `<mj-image href=\"${href}\" src=\"${src}\" alt=\"${alt}\" width=\"${data.headerWidth}px\" css-class=\"rd-header\" container-background-color=\"${data.headerBgColor}\" />`;\n }\n return `<mj-image href=\"${href}\" src=\"${src}\" alt=\"${alt}\" />`;\n}\n\nfunction headerStyleBlock(data: ReportData): string {\n if (!hasHeaderDims(data)) return \"\";\n // Reserve the header's vertical space by aspect ratio so it scales proportionally with\n // its fluid (width:100%) width — no fixed pixel height, so it never squishes.\n // `height:auto !important` defends against any client honoring MJML's inline height.\n return `<mj-style>.rd-header img { height: auto !important; aspect-ratio: ${data.headerWidth} / ${data.headerHeight}; }</mj-style>`;\n}\n\nexport function buildMjml(data: ReportData): string {\n const isTesting = data.reportType === \"Testing\";\n const previewText = `Checked up on ${escapeXml(data.siteName)}`;\n\n return `<mjml>\n <mj-head>\n <mj-attributes>\n <mj-text font-family=\"helvetica, sans-serif\" padding-left=\"5px\" padding-right=\"5px\" />\n <mj-section padding-left=\"11%\" padding-right=\"11%\"/>\n <mj-image padding=\"0px\" />\n </mj-attributes>\n <mj-preview>${previewText}</mj-preview>\n ${headerStyleBlock(data)}\n </mj-head>\n <mj-body background-color=\"white\">\n <mj-section background-color=\"#F4F4F4\" padding-top=\"0px\" padding-bottom=\"0px\" padding-left=\"0px\" padding-right=\"0px\">\n <mj-column>\n ${headerImageTag(data)}\n </mj-column>\n </mj-section>\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">COMPLETED ON</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\">${fmtDate(data.completedOn)}</mj-text>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">MAINTENANCE CHECKS</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"16px\" font-weight=\"300\" line-height=\"24px\">Includes checking the hosting, DNS, Content Management System (CMS, if applicable), search indexing and security of the site for major flaws and updating as necessary.</mj-text>\n </mj-column>\n </mj-section>\n ${maintenanceChecksSection(data.searchPosition)}\n <mj-section background-color=\"#F4F4F4\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"55px\">LIGHTHOUSE SCORES*</mj-text>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Performance</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.performance}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 50–89 // Ideal 90–100</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Readability</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.accessibility}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 80–99 // Ideal 100</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Best Practices</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.bestPractices}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 60–79 // Ideal 80–92</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"300\" padding-top=\"25px\">Site Structure</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\" padding-top=\"0px\">${data.lighthouse.seo}</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"0px\" padding-bottom=\"36px\">Acceptable 50–89 // Ideal 90–100</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" padding-bottom=\"36px\" line-height=\"20px\">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>\n </mj-column>\n </mj-section>\n <mj-section background-color=\"white\">\n <mj-column>\n <mj-text color=\"#C00\" font-size=\"20px\" font-weight=\"700\" padding-top=\"75px\">ANALYTICS</mj-text>\n <mj-text color=\"#C00\" font-size=\"44px\" font-weight=\"400\">${data.gaUsersCurrent !== undefined ? fmtUsers(data.gaUsersCurrent) : \"—\"} Users</mj-text>\n ${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" padding-bottom=\"36px\" line-height=\"20px\">Contact us if you are interested in more in-depth data or have questions about SEO.</mj-text>\n </mj-column>\n </mj-section>\n ${isTesting ? testingIntroSection() + testingChecklistSection() : maintenanceTestingPlaceholder(data.lastTestedDate)}\n ${data.commentary ? commentarySection(data.commentary) : \"\"}\n <mj-section background-color=\"white\">\n <mj-column padding-top=\"36px\">\n <mj-text color=\"#C00\" font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"700\" padding-top=\"36px\" line-height=\"36px\">Any questions, concerns or requests?</mj-text>\n <mj-text font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"300\" line-height=\"30px\">Just hit reply.</mj-text>\n <mj-text font-family=\"helvetica, sans-serif\" font-size=\"24px\" font-weight=\"300\" padding-top=\"0px\" line-height=\"30px\" padding-bottom=\"36px\">We're here to help in any way we can.</mj-text>\n <mj-divider border-width=\"1px\" border-style=\"solid\" border-color=\"#CCCCCC\" padding=\"0\" />\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" padding-top=\"24px\" line-height=\"20px\" font-style=\"italic\">Copyright ${new Date().getFullYear()} Reddoor Creative, LLC. All rights reserved.</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"700\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Our mailing address is:</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Reddoor Creative, LLC</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">29027 Dapper Dan</mj-text>\n <mj-text color=\"#757575\" font-family=\"helvetica, sans-serif\" font-size=\"12px\" font-weight=\"300\" line-height=\"16px\" padding-top=\"0\" padding-bottom=\"0px\">Fair Oaks Ranch, TX 78015</mj-text>\n </mj-column>\n </mj-section>\n </mj-body>\n</mjml>`;\n}\n","import type { FieldSet, Records } from \"airtable\";\nimport type { AirtableBase } from \"./client.js\";\nimport type { ReportType, LighthouseScores } from \"../types.js\";\n\nexport const REPORTS_TABLE = \"Reports\";\n\nexport type DeliveryStatus = \"pending\" | \"delivered\" | \"bounced\" | \"complained\";\n\nexport type ReportRow = {\n id: string;\n reportId: string;\n siteId: string;\n reportType: ReportType;\n periodStart: string | null;\n periodEnd: string | null;\n completedOn: string | null;\n lighthouse: LighthouseScores | null;\n gaUsersCurrent: number | null;\n gaUsersPrevious: number | null;\n searchFoundPage1: boolean | null;\n searchPosition: number | null;\n lastTestedDate: string | null;\n commentary: string | null;\n subjectOverride: string | null;\n draftReady: boolean;\n approvedToSend: boolean;\n sentAt: string | null;\n deliveryStatus: DeliveryStatus;\n renderedHtmlAttachment: { url: string; filename: string } | null;\n /** Read out of the Resend response and stored in a hidden field; needed for webhook reconciliation. */\n resendMessageId: string | null;\n};\n\nfunction mapRow(rec: { id: string; fields: Record<string, unknown> }): ReportRow {\n const f = rec.fields;\n const linkSites = (f[\"Site\"] as string[] | undefined) ?? [];\n const html =\n ((f[\"Rendered HTML\"] as Array<{ url: string; filename: string }> | undefined) ?? [])[0] ?? null;\n return {\n id: rec.id,\n reportId: String(f[\"Report ID\"] ?? \"\"),\n siteId: linkSites[0] ?? \"\",\n reportType: ((f[\"Report type\"] as string | undefined) ?? \"Maintenance\") as ReportType,\n periodStart: (f[\"Period start\"] as string | undefined) ?? null,\n periodEnd: (f[\"Period end\"] as string | undefined) ?? null,\n completedOn: (f[\"Completed on\"] as string | undefined) ?? null,\n lighthouse: lighthouseFromFields(f),\n gaUsersCurrent: (f[\"GA users (period)\"] as number | undefined) ?? null,\n gaUsersPrevious: (f[\"GA users (prev period)\"] as number | undefined) ?? null,\n searchFoundPage1:\n typeof f[\"Search found page 1\"] === \"boolean\" ? (f[\"Search found page 1\"] as boolean) : null,\n searchPosition: (f[\"Search position\"] as number | undefined) ?? null,\n lastTestedDate: (f[\"Last tested date\"] as string | undefined) ?? null,\n commentary: (f[\"Commentary\"] as string | undefined) ?? null,\n subjectOverride: (f[\"Subject override\"] as string | undefined) ?? null,\n draftReady: Boolean(f[\"Draft ready\"]),\n approvedToSend: Boolean(f[\"Approved to send\"]),\n sentAt: (f[\"Sent at\"] as string | undefined) ?? null,\n deliveryStatus: ((f[\"Delivery status\"] as string | undefined) ?? \"pending\") as DeliveryStatus,\n renderedHtmlAttachment: html,\n resendMessageId: (f[\"Resend message ID\"] as string | undefined) ?? null,\n };\n}\n\nfunction lighthouseFromFields(f: Record<string, unknown>): LighthouseScores | null {\n const p = f[\"Lighthouse — Performance\"];\n const a = f[\"Lighthouse — Accessibility\"];\n const b = f[\"Lighthouse — Best Practices\"];\n const s = f[\"Lighthouse — SEO\"];\n if (\n typeof p !== \"number\" ||\n typeof a !== \"number\" ||\n typeof b !== \"number\" ||\n typeof s !== \"number\"\n )\n return null;\n return { performance: p, accessibility: a, bestPractices: b, seo: s };\n}\n\nexport type DraftInput = {\n reportId: string;\n siteId: string;\n reportType: ReportType;\n periodStart: Date;\n periodEnd: Date;\n completedOn: Date;\n lighthouse: LighthouseScores;\n lastTestedDate: Date | null;\n /** GA \"Users\" for the period / previous period. Omitted when GA is not configured\n * for the site or the fetch failed — the operator fills the fields manually. */\n gaUsersCurrent?: number;\n gaUsersPrevious?: number;\n /** Search-presence result. `searchFoundPage1` is written whenever the check ran (true or\n * false — false is the operator-only negative signal). `searchPosition` only when found. */\n searchFoundPage1?: boolean;\n searchPosition?: number;\n};\n\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Escape a string for safe interpolation into an Airtable filterByFormula.\n * Airtable formulas use SQL-like string literals; we escape backslash and\n * double quote. Used wherever an externally-supplied string flows into a\n * formula (e.g. Resend message ids on the webhook path).\n */\nexport function escapeFormulaString(s: string): string {\n return s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nexport async function createDraft(base: AirtableBase, input: DraftInput): Promise<ReportRow> {\n // Set Delivery status to \"pending\" at creation time, NOT at send time. This\n // matters for H4: if stampSent wrote \"pending\" after the webhook had already\n // written \"delivered\" (race), the operator would see a regressed status.\n const fields: FieldSet = {\n \"Report ID\": input.reportId,\n Site: [input.siteId],\n \"Report type\": input.reportType,\n \"Period start\": ymd(input.periodStart),\n \"Period end\": ymd(input.periodEnd),\n \"Completed on\": ymd(input.completedOn),\n \"Lighthouse — Performance\": input.lighthouse.performance,\n \"Lighthouse — Accessibility\": input.lighthouse.accessibility,\n \"Lighthouse — Best Practices\": input.lighthouse.bestPractices,\n \"Lighthouse — SEO\": input.lighthouse.seo,\n \"Delivery status\": \"pending\",\n };\n if (input.lastTestedDate) fields[\"Last tested date\"] = ymd(input.lastTestedDate);\n // GA fields are written only when supplied (GA configured + fetch succeeded). When\n // omitted the row keeps them blank for manual entry — the pre-GA behavior.\n if (input.gaUsersCurrent !== undefined) fields[\"GA users (period)\"] = input.gaUsersCurrent;\n if (input.gaUsersPrevious !== undefined) fields[\"GA users (prev period)\"] = input.gaUsersPrevious;\n if (input.searchFoundPage1 !== undefined) fields[\"Search found page 1\"] = input.searchFoundPage1;\n if (input.searchPosition !== undefined) fields[\"Search position\"] = input.searchPosition;\n const created = (await base(REPORTS_TABLE).create([{ fields }])) as Records<FieldSet>;\n const rec = created[0];\n if (!rec) throw new Error(\"Airtable create returned no records\");\n return mapRow({ id: rec.id, fields: rec.fields });\n}\n\nexport async function setDraftReady(\n base: AirtableBase,\n recordId: string,\n ready: boolean,\n): Promise<void> {\n await base(REPORTS_TABLE).update([{ id: recordId, fields: { \"Draft ready\": ready } }]);\n}\n\nexport async function listSendableReports(base: AirtableBase): Promise<ReportRow[]> {\n const out: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula:\n \"AND({Draft ready} = TRUE(), {Approved to send} = TRUE(), {Sent at} = BLANK())\",\n pageSize: 100,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\nexport async function listReportsForSite(base: AirtableBase, siteId: string): Promise<ReportRow[]> {\n // Anchor with commas so a prefix collision (record id A is a substring of\n // record id B) can't pull in another site's reports. ARRAYJOIN({Site}, \",\")\n // produces \"rec1,rec2,rec3\"; wrap both sides with sentinels for safety.\n const safeId = escapeFormulaString(siteId);\n const out: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula: `FIND(\",${safeId},\", \",\" & ARRAYJOIN({Site}, \",\") & \",\") > 0`,\n pageSize: 100,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return out;\n}\n\n/**\n * Mark a row as sent: write `Sent at` and `Resend message ID` only. Crucially\n * does NOT touch `Delivery status` — that's set to \"pending\" in createDraft\n * and updated by the webhook from there. If we wrote \"pending\" here we could\n * clobber a \"delivered\" that the webhook raced ahead and wrote first (H4).\n */\nexport async function stampSent(\n base: AirtableBase,\n recordId: string,\n sentAt: Date,\n messageId: string,\n): Promise<void> {\n await base(REPORTS_TABLE).update([\n {\n id: recordId,\n fields: {\n \"Sent at\": sentAt.toISOString(),\n \"Resend message ID\": messageId,\n },\n },\n ]);\n}\n\nexport async function setDeliveryStatus(\n base: AirtableBase,\n recordId: string,\n status: DeliveryStatus,\n): Promise<void> {\n await base(REPORTS_TABLE).update([{ id: recordId, fields: { \"Delivery status\": status } }]);\n}\n\nexport async function findReportByMessageId(\n base: AirtableBase,\n messageId: string,\n): Promise<ReportRow | null> {\n const rows: ReportRow[] = [];\n await base(REPORTS_TABLE)\n .select({\n filterByFormula: `{Resend message ID} = \"${escapeFormulaString(messageId)}\"`,\n maxRecords: 1,\n })\n .eachPage((records, fetchNextPage) => {\n for (const rec of records) rows.push(mapRow({ id: rec.id, fields: rec.fields }));\n fetchNextPage();\n });\n return rows[0] ?? null;\n}\n","export async function fetchAttachmentBytes(\n url: string,\n): Promise<{ bytes: Uint8Array; contentType: string }> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch Airtable attachment ${res.status} ${res.statusText} (url=${url})`,\n );\n }\n const contentType = res.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const ab = await res.arrayBuffer();\n return { bytes: new Uint8Array(ab), contentType };\n}\n\n/**\n * Upload bytes (or a string) as an attachment to a specific record + field.\n * Uses Airtable's content.airtable.com upload endpoint (base64 body) because\n * the standard SDK only accepts public URLs for attachments, and we don't\n * host the generated content anywhere public.\n *\n * Docs: https://airtable.com/developers/web/api/upload-attachment\n *\n * Requires AIRTABLE_PAT + AIRTABLE_BASE_ID in env (same as the rest of the\n * reports module). The fieldName is URL-encoded for the request path.\n */\nexport async function uploadAttachment(\n recordId: string,\n fieldName: string,\n body: Uint8Array | string,\n filename: string,\n contentType: string,\n): Promise<void> {\n const apiKey = process.env.AIRTABLE_PAT;\n const baseId = process.env.AIRTABLE_BASE_ID;\n if (!apiKey || !baseId) {\n throw new Error(\"AIRTABLE_PAT and AIRTABLE_BASE_ID must be set\");\n }\n const base64 =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf-8\").toString(\"base64\")\n : Buffer.from(body).toString(\"base64\");\n const payload = { contentType, file: base64, filename };\n const url = `https://content.airtable.com/v0/${baseId}/${recordId}/${encodeURIComponent(fieldName)}/uploadAttachment`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n throw new Error(`Airtable upload failed: ${res.status} ${res.statusText} ${await res.text()}`);\n }\n}\n","import { dirname, join } from \"node:path\";\nimport { defaultCredentialsPath } from \"../../util/credentials.js\";\n\nexport type GaConfig = {\n /** Workspace user the service account impersonates (domain-wide delegation). */\n subject: string;\n /** Absolute path to the service-account JSON key file. */\n keyPath: string;\n};\n\n/**\n * Read GA configuration from the environment (credentials.env is already loaded into\n * process.env by the CLI entrypoint). Returns null when `GA_SUBJECT` is unset — the\n * signal that GA enrichment is simply not configured, so drafting skips it silently.\n *\n * `GA_SA_KEY_PATH` is optional; it defaults to `ga-service-account.json` alongside the\n * credentials file (e.g. ~/.config/reddoor-maint/), keeping the key out of the repo.\n */\nexport function readGaConfig(): GaConfig | null {\n const subject = process.env.GA_SUBJECT?.trim();\n if (!subject) return null;\n const keyPath =\n process.env.GA_SA_KEY_PATH?.trim() ||\n join(dirname(defaultCredentialsPath()), \"ga-service-account.json\");\n return { subject, keyPath };\n}\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/** Resolve the canonical credentials file path. Respects $XDG_CONFIG_HOME\n * (Linux/macOS convention) and falls back to ~/.config/reddoor-maint/. */\nexport function defaultCredentialsPath(): string {\n const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\");\n return join(base, \"reddoor-maint\", \"credentials.env\");\n}\n\n/** Parse a tiny subset of dotenv: `KEY=value` per line, `# comments`,\n * blank lines. Quoted values strip the surrounding quotes. Unknown\n * shapes (no `=`, leading whitespace before `=`, etc.) are skipped\n * silently — this is a credentials file, not a config language. */\nexport function parseEnvFile(contents: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of contents.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n\n/** Load credentials from `path` (default: canonical file) into `process.env`.\n * `process.env` values win — file-defined keys are only applied when the\n * env var is currently undefined. Missing/unreadable file is a silent\n * no-op; commands that need the credentials will fail downstream with\n * their own clear error. Returns the keys actually applied (diagnostics). */\nexport function loadCredentialsIntoEnv(path: string = defaultCredentialsPath()): string[] {\n let contents: string;\n try {\n contents = readFileSync(path, \"utf-8\");\n } catch {\n return [];\n }\n const parsed = parseEnvFile(contents);\n const applied: string[] = [];\n for (const [k, v] of Object.entries(parsed)) {\n if (process.env[k] === undefined) {\n process.env[k] = v;\n applied.push(k);\n }\n }\n return applied;\n}\n","import { readFileSync } from \"node:fs\";\nimport { JWT } from \"google-auth-library\";\nimport { BetaAnalyticsDataClient } from \"@google-analytics/data\";\n\nconst ANALYTICS_READONLY = \"https://www.googleapis.com/auth/analytics.readonly\";\nconst MS_PER_DAY = 86_400_000;\n\nexport type GaQuery = {\n /** GA4 numeric property ID (e.g. \"471880366\"). */\n propertyId: string;\n /** Workspace user to impersonate via domain-wide delegation. */\n subject: string;\n /** Path to the service-account JSON key. */\n keyPath: string;\n};\n\n/** UTC YYYY-MM-DD — matches the rest of the reports pipeline's date handling. */\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Fetch GA4 `activeUsers` (\"Users\") for a report period and the equal-length window\n * immediately before it, via a domain-wide-delegation service account that impersonates\n * `subject`. Throws on any auth/API error — the caller (draftReportForSite) soft-fails.\n *\n * Previous window: same length as the current period, ending the day before `periodStart`.\n */\nexport async function fetchPeriodUsers(\n query: GaQuery,\n periodStart: Date,\n periodEnd: Date,\n): Promise<{ current: number; previous: number }> {\n const key = JSON.parse(readFileSync(query.keyPath, \"utf8\")) as {\n client_email: string;\n private_key: string;\n };\n const authClient = new JWT({\n email: key.client_email,\n key: key.private_key,\n scopes: [ANALYTICS_READONLY],\n subject: query.subject,\n });\n const client = new BetaAnalyticsDataClient({ authClient });\n\n const lengthDays = Math.round((periodEnd.getTime() - periodStart.getTime()) / MS_PER_DAY);\n const prevEnd = new Date(periodStart.getTime() - MS_PER_DAY);\n const prevStart = new Date(prevEnd.getTime() - lengthDays * MS_PER_DAY);\n\n const property = `properties/${query.propertyId}`;\n const run = async (start: Date, end: Date): Promise<number> => {\n const [resp] = await client.runReport({\n property,\n dateRanges: [{ startDate: ymd(start), endDate: ymd(end) }],\n metrics: [{ name: \"activeUsers\" }],\n });\n const raw = resp.rows?.[0]?.metricValues?.[0]?.value ?? \"0\";\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n };\n\n const current = await run(periodStart, periodEnd);\n const previous = await run(prevStart, prevEnd);\n return { current, previous };\n}\n","import { readFileSync } from \"node:fs\";\nimport { JWT } from \"google-auth-library\";\n\nconst WEBMASTERS_READONLY = \"https://www.googleapis.com/auth/webmasters.readonly\";\nconst SC_BASE = \"https://searchconsole.googleapis.com/webmasters/v3\";\n/** Average-position threshold for \"on page 1\" (10 organic results per page). */\nconst PAGE_1_MAX_POSITION = 10;\n\nexport type SearchPresenceQuery = {\n /** Path to the service-account JSON key (same one GA uses). */\n keyPath: string;\n /** Workspace user to impersonate via domain-wide delegation. */\n subject: string;\n /** Explicit Search Console property (`sc-domain:...` or `https://.../`). Overrides auto-resolution. */\n property?: string | undefined;\n /** Site host, used to auto-resolve the property from `sites.list` when `property` is absent. */\n host: string;\n /** Operator-supplied query string (e.g. the business name). */\n query: string;\n};\n\nexport type SearchPresence = {\n /** True when the average position for the query is on page 1 (<= 10). */\n foundOnPage1: boolean;\n /** Rounded average position, or null when not found / no data. */\n position: number | null;\n};\n\ntype SiteEntry = { siteUrl: string };\n\n/** Reduce any property string or URL to a bare host: no `sc-domain:`, scheme, `www.`, path, lowercased. */\nexport function bareHost(s: string): string {\n return s\n .trim()\n .replace(/^sc-domain:/i, \"\")\n .replace(/^https?:\\/\\//i, \"\")\n .split(\"/\")[0]!\n .replace(/^www\\./i, \"\")\n .toLowerCase();\n}\n\n/**\n * All Search Console properties matching `host`, ordered for query fallback: Domain\n * (`sc-domain:`) forms first (broadest coverage), then URL-prefix forms. A site can be verified\n * as both; a freshly-created Domain property has no backfilled history, so its data can be empty\n * even while a long-lived URL-prefix property has data — hence we return every match and let the\n * caller try them in order until one returns data. Empty list = nothing matches.\n */\nexport function resolvePropertyCandidates(entries: SiteEntry[], host: string): string[] {\n const target = bareHost(host);\n const matches = entries.filter((e) => bareHost(e.siteUrl) === target).map((e) => e.siteUrl);\n const domains = matches.filter((s) => s.toLowerCase().startsWith(\"sc-domain:\"));\n const prefixes = matches.filter((s) => !s.toLowerCase().startsWith(\"sc-domain:\"));\n return [...domains, ...prefixes];\n}\n\n/** UTC YYYY-MM-DD — matches the rest of the reports pipeline. */\nfunction ymd(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Query Google Search Console for the average position of `query` on the site over the report\n * period, via a domain-wide-delegation service account impersonating `subject`. Uses `property`\n * verbatim when given (operator's choice is final — no fallback); otherwise auto-discovers all\n * matching properties via `sites.list` and tries them in order (Domain first) until one returns\n * data. Throws on any auth/API error — the caller (draftReportForSite) soft-fails.\n */\nexport async function fetchSearchPresence(\n q: SearchPresenceQuery,\n periodStart: Date,\n periodEnd: Date,\n): Promise<SearchPresence> {\n const key = JSON.parse(readFileSync(q.keyPath, \"utf8\")) as {\n client_email: string;\n private_key: string;\n };\n const jwt = new JWT({\n email: key.client_email,\n key: key.private_key,\n scopes: [WEBMASTERS_READONLY],\n subject: q.subject,\n });\n\n const explicit = q.property?.trim();\n let candidates: string[];\n if (explicit) {\n candidates = [explicit];\n } else {\n const list = await jwt.request<{ siteEntry?: SiteEntry[] }>({\n url: `${SC_BASE}/sites`,\n method: \"GET\",\n });\n candidates = resolvePropertyCandidates(list.data.siteEntry ?? [], q.host);\n if (candidates.length === 0) return { foundOnPage1: false, position: null };\n }\n\n for (const property of candidates) {\n const res = await jwt.request<{ rows?: Array<{ position?: number }> }>({\n url: `${SC_BASE}/sites/${encodeURIComponent(property)}/searchAnalytics/query`,\n method: \"POST\",\n data: {\n startDate: ymd(periodStart),\n endDate: ymd(periodEnd),\n dimensions: [\"query\"],\n dimensionFilterGroups: [\n {\n filters: [\n { dimension: \"query\", operator: \"equals\", expression: q.query.toLowerCase() },\n ],\n },\n ],\n rowLimit: 1,\n },\n });\n const pos = res.data.rows?.[0]?.position;\n if (typeof pos === \"number\") {\n return { foundOnPage1: pos <= PAGE_1_MAX_POSITION, position: Math.round(pos) };\n }\n }\n return { foundOnPage1: false, position: null };\n}\n","import Airtable from \"airtable\";\nimport { defaultCredentialsPath } from \"../../util/credentials.js\";\n\nexport type AirtableConfig = {\n apiKey: string;\n baseId: string;\n};\n\nfunction missing(name: string): Error {\n return Object.assign(\n new Error(\n `${name} not set. Export it in your shell or put it in ${defaultCredentialsPath()} as ${name}=...`,\n ),\n { exitCode: 2 },\n );\n}\n\nexport function readAirtableConfig(): AirtableConfig {\n const apiKey = process.env.AIRTABLE_PAT;\n const baseId = process.env.AIRTABLE_BASE_ID;\n if (!apiKey) throw missing(\"AIRTABLE_PAT\");\n if (!baseId) throw missing(\"AIRTABLE_BASE_ID\");\n return { apiKey, baseId };\n}\n\nexport type AirtableBase = ReturnType<typeof openBase>;\n\nexport function openBase(cfg: AirtableConfig) {\n return new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);\n}\n","import sharp from \"sharp\";\n\nexport type PreparedHeaderImage = {\n /** Resized JPEG bytes to attach inline (CID) in place of the Airtable original. */\n bytes: Uint8Array;\n /** Always \"image/jpeg\" — we re-encode for predictable size and a flat white background. */\n contentType: string;\n /** CSS display width in px (≤ requested, never wider than the source has pixels for). */\n displayWidth: number;\n /** CSS display height in px, source aspect ratio preserved (no distortion). */\n displayHeight: number;\n /** Dominant-color hex (e.g. \"#cfc3a8\"), used as the loading/blocked placeholder box. */\n placeholderColor: string;\n};\n\nexport type PrepareHeaderImageOptions = {\n /** Intended CSS display width. The email body is 600px, so that's the default. */\n displayWidth?: number;\n};\n\nconst DEFAULT_DISPLAY_WIDTH = 600;\n/** Encode the source at 2× display width so it stays crisp on retina screens. */\nconst RETINA_SCALE = 2;\n/** Quality is for *resized* pixels — at 1200px the texture/text read as sharp; bytes are tiny. */\nconst JPEG_QUALITY = 82;\n\nfunction channelToHex(value: number): string {\n return Math.max(0, Math.min(255, Math.round(value)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/**\n * Downscale an oversized header image for email: 2× the display width (retina) at most,\n * never upscaled, re-encoded as JPEG on a flat white background. Also reports the display\n * dimensions (so the template can reserve the box and stop reflow) and a dominant color\n * (so the reserved box shows a matched placeholder while the image loads).\n *\n * Root cause this addresses: Airtable headers can be multi-MB / 2400px+ while the email\n * renders them at ~600px — shipping ~16× more pixels than the display can use.\n */\nexport async function prepareHeaderImage(\n bytes: Uint8Array,\n options: PrepareHeaderImageOptions = {},\n): Promise<PreparedHeaderImage> {\n const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;\n const input = Buffer.from(bytes);\n\n const meta = await sharp(input).metadata();\n const origWidth = meta.width;\n const origHeight = meta.height;\n if (!origWidth || !origHeight) {\n throw new Error(\"prepareHeaderImage: could not read source image dimensions\");\n }\n\n // Never claim a wider display than the source can fill at 1×.\n const displayWidth = Math.min(requestedDisplayWidth, origWidth);\n const displayHeight = Math.round((displayWidth * origHeight) / origWidth);\n\n // Encode at 2× display for retina, but never enlarge a smaller original.\n const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);\n\n const out = await sharp(input)\n .resize({ width: targetSourceWidth, withoutEnlargement: true })\n .flatten({ background: \"#ffffff\" })\n .jpeg({ quality: JPEG_QUALITY })\n .toBuffer();\n\n const { dominant } = await sharp(out).stats();\n const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;\n\n return {\n bytes: new Uint8Array(out),\n contentType: \"image/jpeg\",\n displayWidth,\n displayHeight,\n placeholderColor,\n };\n}\n","import { Resend } from \"resend\";\n\nexport type ResendSendInput = {\n from: string;\n to: string[];\n cc?: string[];\n replyTo?: string;\n subject: string;\n html: string;\n attachments?: Array<{\n filename: string;\n content: string; // base64\n contentType?: string;\n /** Setting this attaches the file as inline; reference it from HTML as `src=\"cid:<id>\"`. */\n inlineContentId?: string;\n }>;\n /**\n * Stable key forwarded as the `Idempotency-Key` header. Resend dedupes calls\n * with the same key for 24 hours, returning the original message id. Use a\n * key that's stable across retries of the same logical send (e.g. the\n * Reports row id), so a network blip during stamping doesn't cause a\n * duplicate email to the client.\n */\n idempotencyKey?: string;\n};\n\nexport type ResendSendResult = {\n messageId: string;\n};\n\nexport type ResendClient = {\n send: (input: ResendSendInput) => Promise<ResendSendResult>;\n};\n\nexport function defaultResendClient(): ResendClient {\n const key = process.env.RESEND_API_KEY;\n if (!key) throw Object.assign(new Error(\"RESEND_API_KEY not set\"), { exitCode: 2 });\n const resend = new Resend(key);\n return {\n async send(input) {\n const payload: Parameters<typeof resend.emails.send>[0] = {\n from: input.from,\n to: input.to,\n subject: input.subject,\n html: input.html,\n };\n if (input.cc) payload.cc = input.cc;\n if (input.replyTo) payload.replyTo = input.replyTo;\n if (input.attachments) payload.attachments = input.attachments;\n const options: Parameters<typeof resend.emails.send>[1] = {};\n if (input.idempotencyKey) options.idempotencyKey = input.idempotencyKey;\n const { data, error } = await resend.emails.send(payload, options);\n if (error) throw new Error(`Resend error: ${error.message}`);\n if (!data?.id) throw new Error(\"Resend returned no message id\");\n return { messageId: data.id };\n },\n };\n}\n","import { openBase, readAirtableConfig } from \"../airtable/client.js\";\nimport { listSendableReports, stampSent } from \"../airtable/reports.js\";\nimport { listWebsites, siteSlug } from \"../airtable/websites.js\";\nimport type { WebsiteRow } from \"../airtable/websites.js\";\nimport type { ReportRow } from \"../airtable/reports.js\";\nimport { fetchAttachmentBytes } from \"../airtable/attachments.js\";\nimport { renderReportHtml } from \"../render.js\";\nimport { loadBundledImages } from \"../maintenance-email/assets/index.js\";\nimport { prepareHeaderImage } from \"../maintenance-email/header-image.js\";\nimport { defaultResendClient, type ResendClient } from \"./resend.js\";\n\nconst FROM_ADDRESS = \"Reddoor Reports <reports@reddoorla.com>\";\nconst REPLY_TO = \"info@reddoorla.com\";\n\nconst MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** \"May 2026\" — UTC month/year, consistent with the rest of the reports pipeline's dates. */\nfunction monthYear(d: Date): string {\n return `${MONTHS[d.getUTCMonth()]} ${d.getUTCFullYear()}`;\n}\n\nexport type OrchestrateOptions = {\n resend?: ResendClient;\n};\n\nexport async function sendApprovedReports(\n options: OrchestrateOptions = {},\n): Promise<{ output: string; code: number }> {\n const base = openBase(readAirtableConfig());\n const client = options.resend ?? defaultResendClient();\n\n const sendable = await listSendableReports(base);\n if (sendable.length === 0) return { output: \"No reports ready to send.\", code: 0 };\n\n const websites = await listWebsites(base);\n const sites = new Map(websites.map((w) => [w.id, w]));\n\n const lines: string[] = [];\n let anyFailed = false;\n for (const report of sendable) {\n const site = sites.get(report.siteId);\n if (!site) {\n lines.push(`✗ ${report.reportId} — Site row not found for id=${report.siteId}`);\n anyFailed = true;\n continue;\n }\n try {\n const messageId = await sendOne(client, base, site, report);\n lines.push(`✓ sent: ${report.reportId} (${messageId})`);\n } catch (e) {\n lines.push(`✗ ${report.reportId} — ${(e as Error).message}`);\n anyFailed = true;\n }\n }\n return { output: lines.join(\"\\n\"), code: anyFailed ? 1 : 0 };\n}\n\nasync function sendOne(\n client: ResendClient,\n base: ReturnType<typeof openBase>,\n site: WebsiteRow,\n report: ReportRow,\n): Promise<string> {\n if (!site.headerImage) {\n throw new Error(`Site '${site.name}' has no Header image set on the Websites row`);\n }\n if (!report.lighthouse) {\n throw new Error(`Report ${report.reportId} has no Lighthouse scores`);\n }\n\n const original = await fetchAttachmentBytes(site.headerImage.url);\n // Downscale the (often multi-MB / 2400px+) Airtable header to email display size, and get\n // back display dims + a placeholder color so the template can reserve the box.\n const header = await prepareHeaderImage(original.bytes);\n const bundled = await loadBundledImages();\n\n const slug = siteSlug(site.name);\n const cidName = `${slug}-header`;\n const { html } = await renderReportHtml({\n siteName: site.name,\n siteUrl: site.url,\n reportType: report.reportType,\n completedOn: report.completedOn ? new Date(report.completedOn) : new Date(),\n lighthouse: report.lighthouse,\n gaUsersCurrent: report.gaUsersCurrent ?? undefined,\n gaUsersPrevious: report.gaUsersPrevious ?? undefined,\n searchPosition:\n report.searchFoundPage1 && report.searchPosition !== null ? report.searchPosition : undefined,\n lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,\n commentary: report.commentary,\n headerImageCid: cidName,\n headerWidth: header.displayWidth,\n headerHeight: header.displayHeight,\n headerBgColor: header.placeholderColor,\n });\n\n const reportDate = report.completedOn ? new Date(report.completedOn) : new Date();\n const subject =\n report.subjectOverride ?? `${site.name} — ${monthYear(reportDate)} ${report.reportType} Report`;\n const explicitTo = parseAddresses(site.reportRecipientsTo);\n // Run pointOfContact through the parser too — operators sometimes paste\n // \"a@x, b@y\" into that single-line field.\n const fallbackTo = parseAddresses(site.pointOfContact);\n const to = explicitTo ?? fallbackTo ?? [];\n if (to.length === 0) {\n throw new Error(\n `Site '${site.name}' has no recipients (Report recipients (To) AND point of contact are both empty)`,\n );\n }\n for (const addr of to) {\n if (!isProbablyEmail(addr)) {\n throw new Error(\n `Site '${site.name}' recipient is malformed: ${addr} — fix Report recipients (To) or point of contact in Airtable`,\n );\n }\n }\n const cc = parseAddresses(site.reportRecipientsCc);\n if (cc) {\n for (const addr of cc) {\n if (!isProbablyEmail(addr)) {\n throw new Error(\n `Site '${site.name}' CC is malformed: ${addr} — fix Report recipients (CC) in Airtable`,\n );\n }\n }\n }\n\n const payload: Parameters<ResendClient[\"send\"]>[0] = {\n from: FROM_ADDRESS,\n to,\n replyTo: REPLY_TO,\n subject,\n html,\n attachments: [\n {\n filename: `${cidName}.jpg`,\n content: Buffer.from(header.bytes).toString(\"base64\"),\n contentType: header.contentType,\n inlineContentId: cidName,\n },\n // Bundled images referenced via cid:rd-check-png / cid:rd-blurred-tests-jpg\n // in the template. Attached inline so the email is self-contained — no\n // external CDN dependency, no image-blocked broken icons in webmail.\n {\n filename: bundled.check.filename,\n content: Buffer.from(bundled.check.bytes).toString(\"base64\"),\n contentType: bundled.check.contentType,\n inlineContentId: bundled.check.cid,\n },\n {\n filename: bundled.blurred.filename,\n content: Buffer.from(bundled.blurred.bytes).toString(\"base64\"),\n contentType: bundled.blurred.contentType,\n inlineContentId: bundled.blurred.cid,\n },\n ],\n // Stable across retries of the same row — if Airtable stamping fails after a\n // successful Resend, the next --send-ready replays with the same key and\n // Resend returns the original message id rather than sending a duplicate.\n idempotencyKey: `report:${report.id}`,\n };\n if (cc) payload.cc = cc;\n\n const result = await client.send(payload);\n await stampSent(base, report.id, new Date(), result.messageId);\n return result.messageId;\n}\n\n/**\n * Split a comma/newline-separated address field into a clean array.\n * Lowercases (case-insensitive dedupe) and removes empty entries. Returns\n * null if nothing survives. Does NOT understand `Display Name <email>` —\n * operators should put a bare address in the Airtable field, or use multiple\n * lines if needing multiple recipients.\n */\nexport function parseAddresses(field: string | null): string[] | null {\n if (!field) return null;\n const seen = new Set<string>();\n const list: string[] = [];\n for (const raw of field.split(/[,\\n]/)) {\n const trimmed = raw.trim().toLowerCase();\n if (!trimmed) continue;\n if (seen.has(trimmed)) continue;\n seen.add(trimmed);\n list.push(trimmed);\n }\n return list.length > 0 ? list : null;\n}\n\n/**\n * Cheap email shape check — must contain exactly one @, with non-empty\n * local and domain parts and at least one dot in the domain. We're not\n * trying to be a full RFC validator; we're trying to catch operator\n * mistakes like \"ops at acme dot com\" or a missing @ before they 422\n * at Resend.\n */\nexport function isProbablyEmail(s: string): boolean {\n const at = s.indexOf(\"@\");\n if (at < 1 || at !== s.lastIndexOf(\"@\")) return false;\n const local = s.slice(0, at);\n const domain = s.slice(at + 1);\n if (!local || !domain) return false;\n if (!domain.includes(\".\")) return false;\n if (/\\s/.test(s)) return false;\n return true;\n}\n","import type { WebsiteRow, Frequency, Status } from \"./airtable/websites.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport type { ReportType } from \"./types.js\";\n\n/** Statuses where reports are appropriate. Drops \"deprecated\" and\n * \"probably not our problem\" — even if the operator left a freq set, we don't\n * want to surface those sites in --due output. Sites with status=null pass\n * through (existing data is partial; better to surface than silently skip). */\nconst ELIGIBLE_STATUSES: ReadonlySet<Status> = new Set<Status>([\n \"in development\",\n \"launch period\",\n \"maintenance\",\n \"hosting\",\n]);\n\nexport type DueItem = {\n site: WebsiteRow;\n reportType: ReportType;\n /** Inclusive: the day the next report became due. */\n dueDate: Date;\n /** ISO date of the last `Sent at` for this (site, type), or null if there's never been one. */\n lastSent: string | null;\n};\n\nconst MONTHS: Record<Exclude<Frequency, \"None\">, number> = {\n Monthly: 1,\n Quarterly: 3,\n Yearly: 12,\n};\n\n/**\n * Add `n` calendar months in UTC, clamped to the last day of the target month.\n * Jan 31 + 1 month = Feb 28 (not Mar 3, which is what naive setMonth produces).\n * All-UTC accessors mean the result is timezone-independent.\n */\nfunction addMonths(d: Date, n: number): Date {\n const out = new Date(d);\n const day = out.getUTCDate();\n out.setUTCDate(1);\n out.setUTCMonth(out.getUTCMonth() + n);\n const lastDayOfTargetMonth = new Date(\n Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0),\n ).getUTCDate();\n out.setUTCDate(Math.min(day, lastDayOfTargetMonth));\n return out;\n}\n\n/** Truncate to UTC midnight. Avoids local-TZ skew when comparing Airtable date-only fields. */\nfunction startOfDay(d: Date): Date {\n const out = new Date(d);\n out.setUTCHours(0, 0, 0, 0);\n return out;\n}\n\nfunction lastSentForType(reports: ReportRow[], siteId: string, type: ReportType): string | null {\n const candidates = reports\n .filter((r) => r.siteId === siteId && r.reportType === type && r.sentAt !== null)\n .map((r) => r.sentAt!)\n .sort();\n return candidates[candidates.length - 1] ?? null;\n}\n\n/**\n * Computes which (site, type) pairs are due as of `today`.\n *\n * Algorithm per (site, type):\n * 1. If freq === \"None\", skip.\n * 2. baseDate = max(last Sent at for this type, site's `maintenance/testing day` fallback).\n * 3. If no baseDate exists at all, the site is due now.\n * 4. dueDate = baseDate + frequency months.\n * 5. Due iff startOfDay(today) >= startOfDay(dueDate).\n */\nexport function findDueReports(\n websites: WebsiteRow[],\n reports: ReportRow[],\n today: Date,\n): DueItem[] {\n const out: DueItem[] = [];\n const todayStart = startOfDay(today);\n\n for (const site of websites) {\n // Skip explicitly-non-active statuses (deprecated, \"probably not our problem\").\n // Null status is treated as active for backwards compat with rows that pre-date\n // the Status convention.\n if (site.status !== null && !ELIGIBLE_STATUSES.has(site.status)) continue;\n\n for (const type of [\"Maintenance\", \"Testing\"] as const) {\n const freq = type === \"Maintenance\" ? site.maintenanceFreq : site.testingFreq;\n if (freq === \"None\") continue;\n\n const lastSent = lastSentForType(reports, site.id, type);\n const fallback = type === \"Maintenance\" ? site.maintenanceDay : site.testingDay;\n const baseIso = lastSent ?? fallback;\n\n if (!baseIso) {\n out.push({ site, reportType: type, dueDate: todayStart, lastSent });\n continue;\n }\n\n const dueDate = addMonths(new Date(baseIso), MONTHS[freq]);\n if (todayStart.getTime() >= startOfDay(dueDate).getTime()) {\n out.push({ site, reportType: type, dueDate, lastSent });\n }\n }\n }\n\n return out;\n}\n","/** Render an absolute timestamp as a coarse \"Xd ago\" relative string for the\n * fleet card. Takes an explicit `now` for testability; defaults to wall clock\n * for callers (the Netlify function). Returns \"—\" for null / unparseable. */\nexport function relativeTimeFromNow(iso: string | null, now: Date = new Date()): string {\n if (!iso) return \"—\";\n const t = Date.parse(iso);\n if (Number.isNaN(t)) return \"—\";\n\n const seconds = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n if (seconds < 60) return \"just now\";\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) return `${weeks}w ago`;\n\n const months = Math.floor(days / 30);\n return `${months}mo ago`;\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport type { ReportRow } from \"../reports/airtable/reports.js\";\nimport { relativeTimeFromNow } from \"./relative-time.js\";\n\n/** Minimal HTML-escape; not for XML/attribute-edge cases, just for text + safe\n * attribute interpolation here. */\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\n/** Allow only http(s) URLs in href context; everything else collapses to \"#\". */\nfunction safeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (u.protocol === \"http:\" || u.protocol === \"https:\") return raw;\n } catch {\n // fall through\n }\n return \"#\";\n}\n\nfunction scoreTile(label: string, value: number | null): string {\n const display = value === null ? \"—\" : String(value);\n return `<div class=\"tile\"><div class=\"tile-value\">${escapeHtml(display)}</div><div class=\"tile-label\">${escapeHtml(label)}</div></div>`;\n}\n\nfunction healthTile(label: string, value: number | null, sub: string | null): string {\n const display = value === null ? \"—\" : String(value);\n const subLine = sub ? `<div class=\"tile-sub\">${escapeHtml(sub)}</div>` : \"\";\n return `<div class=\"tile\"><div class=\"tile-value\">${escapeHtml(display)}</div><div class=\"tile-label\">${escapeHtml(label)}</div>${subLine}</div>`;\n}\n\nfunction depsSub(majorBehind: number | null): string | null {\n if (majorBehind === null || majorBehind === 0) return null;\n return `${majorBehind} major behind`;\n}\n\nfunction securityTotal(site: WebsiteRow): number | null {\n const parts = [\n site.securityVulnsCritical,\n site.securityVulnsHigh,\n site.securityVulnsModerate,\n site.securityVulnsLow,\n ];\n if (parts.every((p) => p === null)) return null;\n return parts.reduce<number>((sum, p) => sum + (p ?? 0), 0);\n}\n\nfunction securitySub(site: WebsiteRow): string | null {\n const total = securityTotal(site);\n if (total === null || total === 0) return null;\n const c = site.securityVulnsCritical ?? 0;\n const h = site.securityVulnsHigh ?? 0;\n const m = site.securityVulnsModerate ?? 0;\n const l = site.securityVulnsLow ?? 0;\n return `${c}C / ${h}H / ${m}M / ${l}L`;\n}\n\nfunction reportRow(r: ReportRow): string {\n const date = r.completedOn ? escapeHtml(r.completedOn) : \"—\";\n const type = escapeHtml(r.reportType);\n const id = escapeHtml(r.reportId);\n const link = r.renderedHtmlAttachment\n ? `<a href=\"${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}\">view</a>`\n : `<span class=\"muted\">no attachment</span>`;\n return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${link}</td></tr>`;\n}\n\nconst STYLES = `\n:root { color-scheme: light dark; }\nbody { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 860px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }\n@media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }\nh1 { margin: 0 0 0.25rem; font-size: 1.75rem; }\n.meta { color: #666; margin-bottom: 2rem; }\n.meta a { color: inherit; }\n.audited { color: #999; font-size: 0.85rem; margin-bottom: 1.5rem; }\n.section { margin: 2rem 0; }\n.section h2 { font-size: 1.1rem; margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; }\n.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }\n.tile { padding: 1rem; border: 1px solid #ddd; border-radius: 6px; text-align: center; }\n@media (prefers-color-scheme: dark) { .tile { border-color: #333; } }\n.tile-value { font-size: 2rem; font-weight: 600; }\n.tile-label { font-size: 0.85rem; color: #666; margin-top: 0.25rem; }\n.tile-sub { font-size: 0.75rem; color: #999; margin-top: 0.15rem; }\ntable { width: 100%; border-collapse: collapse; }\nth, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }\n@media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }\n.muted { color: #999; }\n.empty { color: #999; padding: 1rem; border: 1px dashed #ccc; border-radius: 6px; text-align: center; }\n`;\n\n/**\n * Render the per-site dashboard as a single HTML document. Pure function:\n * no Airtable access, no env reads, no I/O. The Netlify function handler\n * fetches data, then hands it here. Easier to unit-test, easier to render\n * a static preview from CLI later.\n */\nexport function renderSiteDashboardHtml(site: WebsiteRow, reports: ReportRow[]): string {\n const name = escapeHtml(site.name);\n const urlSafe = safeUrl(site.url);\n const allScoresNull =\n site.pScore === null && site.rScore === null && site.bpScore === null && site.seoScore === null;\n\n const scoresSection = allScoresNull\n ? `<div class=\"empty\">No lighthouse data yet — run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>`\n : `<div class=\"tiles\">\n ${scoreTile(\"Performance\", site.pScore)}\n ${scoreTile(\"Accessibility\", site.rScore)}\n ${scoreTile(\"Best Practices\", site.bpScore)}\n ${scoreTile(\"SEO\", site.seoScore)}\n </div>`;\n\n const secTotal = securityTotal(site);\n const allHealthNull =\n site.a11yViolations === null && site.depsDrifted === null && secTotal === null;\n const healthSection = allHealthNull\n ? `<div class=\"empty\">No health data yet — run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>`\n : `<div class=\"tiles\">\n ${healthTile(\"Accessibility issues\", site.a11yViolations, null)}\n ${healthTile(\"Dependency updates\", site.depsDrifted, depsSub(site.depsMajorBehind))}\n ${healthTile(\"Security alerts\", secTotal, securitySub(site))}\n </div>`;\n\n const auditedLine = site.lastLighthouseAuditAt\n ? `<div class=\"audited\">Last audited ${escapeHtml(relativeTimeFromNow(site.lastLighthouseAuditAt))}</div>`\n : \"\";\n\n const reportsSection =\n reports.length === 0\n ? `<div class=\"empty\">No reports yet.</div>`\n : `<table>\n <thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>Report</th></tr></thead>\n <tbody>${reports.map(reportRow).join(\"\")}</tbody>\n </table>`;\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${name} — Reddoor maintenance</title>\n <style>${STYLES}</style>\n</head>\n<body>\n <h1>${name}</h1>\n <div class=\"meta\"><a href=\"${escapeHtml(urlSafe)}\">${escapeHtml(site.url)}</a></div>\n ${auditedLine}\n\n <div class=\"section\">\n <h2>Lighthouse</h2>\n ${scoresSection}\n </div>\n\n <div class=\"section\">\n <h2>Site Health</h2>\n ${healthSection}\n </div>\n\n <div class=\"section\">\n <h2>Reports</h2>\n ${reportsSection}\n </div>\n</body>\n</html>`;\n}\n","import { timingSafeEqual } from \"node:crypto\";\n\n/**\n * Constant-time comparison of a request-provided token against the token\n * stored on the Websites row in Airtable. Used by the per-site dashboard\n * Netlify function to gate /s/<slug>?t=<token>.\n *\n * Returns false for any of:\n * - provided token missing / empty\n * - expected token missing (the site has no Dashboard Token configured)\n * - lengths differ (constant-time path skipped because `timingSafeEqual`\n * throws on length mismatch — the length difference itself doesn't leak\n * anything secret since the expected token's length is fixed per site)\n *\n * Treats null/undefined/empty-string from the request as a single\n * \"no token\" state — keeps the handler's branching simple.\n */\nexport function verifyDashboardToken(\n provided: string | null | undefined,\n expected: string | null,\n): boolean {\n if (!provided || !expected) return false;\n if (provided.length !== expected.length) return false;\n return timingSafeEqual(Buffer.from(provided, \"utf-8\"), Buffer.from(expected, \"utf-8\"));\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\n\nexport type OnboardingStatus = {\n score: number;\n total: 4;\n checks: {\n firstAudit: boolean;\n recipients: boolean;\n schedule: boolean;\n poc: boolean;\n };\n};\n\nfunction isNonEmpty(s: string | null | undefined): boolean {\n return typeof s === \"string\" && s.trim().length > 0;\n}\n\n/** Four-point onboarding signal for the fleet card. A site is \"fully onboarded\"\n * when it has been audited at least once, has a To-recipient for monthly\n * reports, has a maintenance schedule that isn't \"None\", and has a named POC. */\nexport function onboardingStatus(row: WebsiteRow): OnboardingStatus {\n const checks = {\n firstAudit: isNonEmpty(row.lastLighthouseAuditAt),\n recipients: isNonEmpty(row.reportRecipientsTo),\n schedule: row.maintenanceFreq !== \"None\",\n poc: isNonEmpty(row.pointOfContact),\n };\n const score = Object.values(checks).filter(Boolean).length;\n return { score, total: 4, checks };\n}\n","import type { WebsiteRow } from \"../reports/airtable/websites.js\";\nimport { siteSlug } from \"../reports/airtable/websites.js\";\nimport { onboardingStatus } from \"./onboarding.js\";\nimport { relativeTimeFromNow } from \"./relative-time.js\";\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction safeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (u.protocol === \"http:\" || u.protocol === \"https:\") return raw;\n } catch {\n // fall through\n }\n return \"#\";\n}\n\nconst DASH = \"—\";\n\nfunction scoreSpan(category: \"perf\" | \"a11y-lh\" | \"bp\" | \"seo\", value: number | null): string {\n const display = value === null ? DASH : String(value);\n return `<span class=\"score ${category}\">${escapeHtml(display)}</span>`;\n}\n\nfunction a11ySpan(value: number | null): string {\n const display = value === null ? DASH : String(value);\n return `<span class=\"metric a11y\">${escapeHtml(display)}</span>`;\n}\n\nfunction depsSpan(drifted: number | null, majorBehind: number | null): string {\n if (drifted === null || majorBehind === null) {\n return `<span class=\"metric deps\">${DASH}</span>`;\n }\n const display = drifted === 0 ? \"0\" : `${drifted} drifted (${majorBehind} major)`;\n return `<span class=\"metric deps\">${escapeHtml(display)}</span>`;\n}\n\nfunction securitySpan(\n critical: number | null,\n high: number | null,\n moderate: number | null,\n low: number | null,\n): string {\n if (critical === null || high === null || moderate === null || low === null) {\n return `<span class=\"metric sec\">${DASH}</span>`;\n }\n const total = critical + high + moderate + low;\n const display = total === 0 ? \"0\" : `${critical}C/${high}H/${moderate}M/${low}L`;\n return `<span class=\"metric sec\">${escapeHtml(display)}</span>`;\n}\n\nfunction card(site: WebsiteRow): string {\n const name = escapeHtml(site.name);\n // Caller is responsible for filtering — render assumes every site has a\n // dashboardToken. The `?? \"\"` is a defensive nudge if a misuse ever slips through.\n const token = site.dashboardToken ?? \"\";\n const href = `/s/${escapeHtml(siteSlug(site.name))}?t=${escapeHtml(token)}`;\n const onboarding = onboardingStatus(site);\n const audited = relativeTimeFromNow(site.lastLighthouseAuditAt);\n const safeSiteUrl = escapeHtml(safeUrl(site.url));\n const visibleUrl = escapeHtml(site.url);\n\n return `<article class=\"card\">\n <header class=\"card-head\">\n <a class=\"site\" href=\"${href}\">${name}</a>\n <a class=\"url\" href=\"${safeSiteUrl}\" target=\"_blank\" rel=\"noopener\">${visibleUrl}</a>\n <span class=\"setup\">Setup: <strong>${onboarding.score}/${onboarding.total}</strong></span>\n <span class=\"audited\">Audited: <strong>${escapeHtml(audited)}</strong></span>\n </header>\n <div class=\"card-metrics\">\n <span class=\"cluster lighthouse\">\n ${scoreSpan(\"perf\", site.pScore)}\n ${scoreSpan(\"a11y-lh\", site.rScore)}\n ${scoreSpan(\"bp\", site.bpScore)}\n ${scoreSpan(\"seo\", site.seoScore)}\n </span>\n <span class=\"cluster health\">\n <span class=\"metric-label\">a11y</span> ${a11ySpan(site.a11yViolations)}\n <span class=\"metric-label\">deps</span> ${depsSpan(site.depsDrifted, site.depsMajorBehind)}\n <span class=\"metric-label\">sec</span> ${securitySpan(\n site.securityVulnsCritical,\n site.securityVulnsHigh,\n site.securityVulnsModerate,\n site.securityVulnsLow,\n )}\n </span>\n </div>\n </article>`;\n}\n\nconst STYLES = `\n:root { color-scheme: light dark; }\nbody { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }\n@media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }\nh1 { margin: 0 0 0.25rem; font-size: 1.75rem; }\n.meta { color: #666; margin-bottom: 1.5rem; }\n.empty { color: #999; padding: 2rem; text-align: center; border: 1px dashed #ccc; border-radius: 6px; }\n.cards { display: flex; flex-direction: column; gap: 0.75rem; }\n.card { border: 1px solid #e5e5e5; border-radius: 8px; padding: 0.9rem 1.1rem; }\n@media (prefers-color-scheme: dark) { .card { border-color: #2a2a2a; background: #181818; } }\n.card-head { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: baseline; }\n.card-head .site { font-weight: 600; font-size: 1.05rem; }\n.card-head .url { color: #666; font-size: 0.85rem; }\n.card-head .setup, .card-head .audited { color: #666; font-size: 0.85rem; }\n.card-head .setup { margin-left: auto; }\n.card-metrics { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin-top: 0.5rem; font-variant-numeric: tabular-nums; }\n.cluster { display: inline-flex; gap: 0.5rem; align-items: baseline; }\n.cluster.lighthouse .score { display: inline-block; min-width: 2.25rem; text-align: right; }\n.metric-label { color: #999; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }\n.metric { font-feature-settings: \"tnum\"; }\n`;\n\n/**\n * Render the fleet homepage as a single HTML document. Pure function:\n * no Airtable access, no env reads, no I/O. The Netlify function handler\n * filters Websites rows (drops anything without a dashboardToken), sorts,\n * and hands here. One <article class=\"card\"> per site, with a header row\n * (name · url · setup · audited) and a metrics row (lighthouse · a11y · deps · sec).\n */\nexport function renderFleetHomeHtml(sites: WebsiteRow[]): string {\n const body =\n sites.length === 0\n ? `<div class=\"empty\">No sites to display.</div>`\n : `<div class=\"cards\">${sites.map(card).join(\"\")}</div>`;\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Reddoor maintenance — fleet</title>\n <style>${STYLES}</style>\n</head>\n<body>\n <h1>Reddoor fleet</h1>\n <div class=\"meta\">${sites.length} site${sites.length === 1 ? \"\" : \"s\"} on the Reddoor stack.</div>\n ${body}\n</body>\n</html>`;\n}\n","import { timingSafeEqual } from \"node:crypto\";\n\n/**\n * Verify an `Authorization: Basic <base64>` header against the configured\n * dashboard password. Username is intentionally ignored — operators may\n * type anything when the browser prompts; only the password gates entry.\n *\n * Returns false for any of:\n * - missing/empty Authorization header\n * - non-Basic auth scheme\n * - malformed base64 or payload (no colon to split user:password)\n * - wrong password\n * - expected password missing (DASHBOARD_PASSWORD not configured)\n *\n * Wrong-password compare is constant-time; lengths are checked first\n * (timingSafeEqual throws on mismatch, and the length itself doesn't\n * leak — operator's password length is fixed per deploy).\n */\nexport function verifyBasicAuth(\n authHeader: string | null | undefined,\n expectedPassword: string | null,\n): boolean {\n if (!authHeader || !expectedPassword) return false;\n // RFC 7235: scheme is case-insensitive.\n const match = /^basic\\s+(.+)$/i.exec(authHeader.trim());\n if (!match) return false;\n let decoded: string;\n try {\n decoded = Buffer.from(match[1]!, \"base64\").toString(\"utf-8\");\n } catch {\n return false;\n }\n // Base64-decoding never throws in Node, but a payload of garbage may\n // produce a string with no colon. user:password form is required.\n const colonIdx = decoded.indexOf(\":\");\n if (colonIdx === -1) return false;\n const provided = decoded.slice(colonIdx + 1);\n if (provided.length !== expectedPassword.length) return false;\n return timingSafeEqual(Buffer.from(provided, \"utf-8\"), Buffer.from(expectedPassword, \"utf-8\"));\n}\n"],"mappings":"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","names":["readFile","join","join","eslint","readFile","spawn","readFile","join","readFile","join","readFile","join","spawn","readFile","writeFile","mkdtemp","rm","join","readJsonMaybe","readFile","spawn","mkdtemp","join","writeFile","rm","spawn","readFile","writeFile","join","readFile","join","writeFile","commit","join","spawn","join","commit","join","readFile","writeFile","join","join","readFile","writeFile","join","spawn","join","spawn","join","readFile","writeFile","join","glob","SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK","IGNORE","glob","join","readFile","writeFile","spawn","writeFile","join","join","spawn","commit","writeFile","join","commit","writeFile","join","rm","stat","join","exists","stat","spawn","join","commit","rm","stat","join","existsSync","dirname","join","exists","stat","spawn","join","commit","mkdir","writeFile","dirname","join","join","commit","mkdir","dirname","writeFile","readFile","mkdir","writeFile","dirname","readFile","existsSync","dirname","join","fileURLToPath","mapRow","dirname","join","readFileSync","join","join","dirname","readFileSync","ymd","readFileSync","JWT","ymd","mkdir","dirname","writeFile","MONTHS","escapeHtml","safeUrl","STYLES","timingSafeEqual"]}