// Verifies the built frontpage before it is deployed: the page exists, the // PostHog snippet survived the build with the intended config (EU ingestion // host, cookieless persistence, exactly one init), the link-unfurl tags point // at a card image that actually shipped, and the screenshot gallery is the // current popup captures rather than an older copy of them. Plain node, no // dependencies beyond the repo's own render-icons and shots helpers. // // Usage: node scripts/verify-dist.mjs [distDir] import { createHash } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { pngSize } from "../../scripts/render-icons.mjs"; import { SHOTS, capturePath } from "../../scripts/shots.mjs"; const SITE = "https://substandard.blog"; const webRoot = resolve(fileURLToPath(import.meta.url), "../.."); const dist = resolve(process.argv[2] ?? join(webRoot, "dist")); const index = join(dist, "index.html"); if (!existsSync(index)) { console.error( `verify-dist: ${index} does not exist — run \`npm run build\` first`, ); process.exit(1); } const html = readFileSync(index, "utf8"); const problems = []; const inits = html.match(/posthog\.init\(/g)?.length ?? 0; if (inits !== 1) { problems.push(`expected exactly one posthog.init call, found ${inits}`); } if (!html.includes('"phc_yBvzGPADSPgFV7hBaHJWQxHa4ZniXUhdd6nkqaHgk2Mi"')) { problems.push("PostHog project API key missing from index.html"); } if (!html.includes("https://eu.i.posthog.com")) { problems.push("PostHog EU ingestion host missing — events would go nowhere"); } if (!/persistence:\s*"memory"/.test(html)) { problems.push('persistence: "memory" missing — tracking must stay cookieless'); } // The CSP served by CloudFront allows the inline PostHog snippet by hash, so // any change to that snippet — a PostHog config edit, a different Astro // inlining — silently blocks it in production unless infra/main.tf is updated // to match. Read the expected hash out of the terraform rather than keeping a // second copy here, so there is exactly one value to keep right. const mainTf = readFileSync(resolve(webRoot, "../infra/main.tf"), "utf8"); const expectedHash = mainTf.match(/posthog_script_hash\s*=\s*"([^"]+)"/)?.[1]; const inlineScript = html.match(/