import { readdir, readFile, stat } from 'node:fs/promises'; import path from 'node:path'; const dist = path.resolve('dist'); const allowedPreservedRoutes = new Set(['/fleeting/', '/reconcile/']); const failures = []; async function walk(dir) { const entries = await readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...(await walk(fullPath))); } else { files.push(fullPath); } } return files; } async function exists(file) { try { return (await stat(file)).isFile(); } catch { return false; } } async function targetExists(pathname) { if (allowedPreservedRoutes.has(pathname)) return true; const safePath = path .normalize(decodeURIComponent(pathname)) .replace(/^\/+/, '') .replace(/^(\.\.(\/|\\|$))+/, ''); const candidate = path.join(dist, safePath); const candidates = [ candidate, path.join(candidate, 'index.html'), path.join(dist, `${safePath}.html`), ]; for (const file of candidates) { if (await exists(file)) return true; } return false; } try { await stat(dist); } catch { throw new Error('dist/ does not exist. Run npm run build first.'); } const files = await walk(dist); const htmlAndXml = files.filter((file) => /\.(html|xml)$/.test(file)); function pagePathname(file) { const rel = path.relative(dist, file).replaceAll(path.sep, '/'); if (rel === 'index.html') return '/'; if (rel.endsWith('/index.html')) return `/${rel.slice(0, -'index.html'.length)}`; return `/${rel}`; } for (const file of htmlAndXml) { const body = await readFile(file, 'utf8'); const rel = path.relative(dist, file); const baseUrl = new URL(pagePathname(file), 'https://schmelczer.dev'); const matches = body.matchAll(/\b(?:href|src)=["']([^"'#?]+)(?:[?#][^"']*)?["']/g); for (const match of matches) { const raw = match[1]; if (/^(mailto:|tel:|data:)/i.test(raw)) continue; let parsed; try { parsed = new URL(raw, baseUrl); } catch { failures.push(`${rel}: invalid URL ${raw}`); continue; } if (parsed.origin !== 'https://schmelczer.dev') continue; if (!(await targetExists(parsed.pathname))) { failures.push(`${rel}: missing local target ${parsed.pathname}`); } } } if (failures.length > 0) { console.error(failures.join('\n')); process.exit(1); } console.log('No missing local href/src targets found in dist/.');