#!/usr/bin/env node // Renders every preview page from a running dev server and compares the HTML // against a stored baseline, so internal refactors can prove they don't change // the rendered output at all. Volatile content (the "Generated " footer) // is normalized away before comparing. // Run: pnpm run html-diff:baseline — capture baseline snapshots (before refactoring) // pnpm run html-diff — re-render and diff against the baseline // The dev server defaults to http://localhost:3000; override with HTML_DIFF_BASE. import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs' import { join, dirname, relative } from 'node:path' import { fileURLToPath } from 'node:url' import { sync } from 'glob' const __dirname = dirname(fileURLToPath(import.meta.url)) const repoRoot = join(__dirname, '..') const pagesDir = join(repoRoot, 'preview', 'pages') const snapshotDir = join(repoRoot, '.cache', 'html-diff') const baseUrl = process.env.HTML_DIFF_BASE ?? 'http://localhost:3000' const mode = process.argv[2] === 'baseline' ? 'baseline' : 'check' // --------------------------------------------------------------------------- // Route table: every preview page url, keyed by its snapshot file name. // --------------------------------------------------------------------------- const routes = new Map() for (const file of sync(join(pagesDir, '**', '*.astro'))) { if (file.includes('[')) continue // dynamic routes have no single url const url = '/' + relative(pagesDir, file) .replace(/\/?index\.astro$/, '') .replace(/\.astro$/, '') routes.set((url === '/' ? 'index' : url.slice(1)).replaceAll('/', '_') + '.html', url) } // The footer stamps the render time into every page, and the dev server injects // its own client script — neither says anything about the rendered markup. const normalize = (html: string) => html.replace(/Generated \d{4}[^<]*/g, 'Generated').replace(/[ \t]*