import { createServer } from 'node:http'; import { readFile, stat } from 'node:fs/promises'; import path from 'node:path'; import { chromium } from 'playwright'; const dist = path.resolve('dist'); const routes = [ '/', '/articles/', '/articles/greatai-ai-deployment-api/', '/writing/', '/writing/greatai-ai-deployment-api/', '/projects/', '/about/', ]; const widths = [320, 390, 430]; function contentType(file) { if (file.endsWith('.html')) return 'text/html; charset=utf-8'; if (file.endsWith('.css')) return 'text/css; charset=utf-8'; if (file.endsWith('.js')) return 'text/javascript; charset=utf-8'; if (file.endsWith('.svg')) return 'image/svg+xml'; if (file.endsWith('.png')) return 'image/png'; if (file.endsWith('.jpg') || file.endsWith('.jpeg')) return 'image/jpeg'; if (file.endsWith('.webp')) return 'image/webp'; if (file.endsWith('.woff2')) return 'font/woff2'; return 'application/octet-stream'; } async function resolveFile(url) { const parsed = new URL(url, 'http://localhost'); const safePath = path .normalize(decodeURIComponent(parsed.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) { try { const fileStat = await stat(file); if (fileStat.isFile()) return file; } catch { // Try the next candidate. } } return path.join(dist, '404.html'); } const server = createServer(async (req, res) => { try { const file = await resolveFile(req.url ?? '/'); const body = await readFile(file); res.writeHead(200, { 'content-type': contentType(file) }); res.end(body); } catch (error) { res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' }); res.end(String(error)); } }); await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); const { port } = server.address(); const browser = await chromium.launch({ headless: true }); const failures = []; async function measureViewport(page) { for (let attempt = 0; attempt < 3; attempt += 1) { try { await page.waitForLoadState('load'); return await page.evaluate(() => ({ scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, })); } catch (error) { const message = error instanceof Error ? error.message : String(error); if (attempt === 2 || !/Execution context was destroyed|navigation/i.test(message)) { throw error; } await page.waitForLoadState('load').catch(() => {}); } } } try { for (const width of widths) { const page = await browser.newPage({ viewport: { width, height: 900 }, javaScriptEnabled: false, }); for (const route of routes) { await page.goto(`http://127.0.0.1:${port}${route}`, { waitUntil: 'load' }); if (route.startsWith('/writing/')) { await page .waitForURL((url) => url.pathname.startsWith('/articles/'), { timeout: 1000 }) .catch(() => {}); } const result = await measureViewport(page); if (result.scrollWidth > result.clientWidth + 1) { failures.push( `${route} overflows at ${width}px: ${result.scrollWidth}px > ${result.clientWidth}px` ); } } await page.close(); } } finally { await browser.close(); server.close(); } if (failures.length > 0) { console.error(failures.join('\n')); process.exit(1); } console.log('No horizontal overflow detected at 320px, 390px, or 430px.');