// Скриншот-цикл: собрать, поднять preview, снять PNG, положить в .shots/. // Смотреть на снимок — обязательная часть цикла: без неё код валиден, а вид случаен. // // node scripts/shot.mjs все маршруты, 1440x900 // node scripts/shot.mjs /showcase один маршрут // node scripts/shot.mjs --size 1280x764 вьюпорт референса — для прямого наложения // // Маршруты дублируют src/routes.tsx: два списка вместо загрузчика TS в Node — сознательный // выбор в пользу простоты, пополнять оба. import { mkdir } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const shotsDir = resolve(root, '.shots'); // Chromium и шрифт CJK лежат локально, без sudo и без прав на систему (FRONTEND_PLAN.md §4). const toolingRoot = resolve(root, '.tooling/root'); if (existsSync(toolingRoot)) { const libs = resolve(toolingRoot, 'usr/lib/x86_64-linux-gnu'); process.env.LD_LIBRARY_PATH = [libs, process.env.LD_LIBRARY_PATH].filter(Boolean).join(':'); process.env.XDG_DATA_HOME = resolve(toolingRoot, 'usr/share'); } const { chromium } = await import('playwright'); const { build, preview } = await import('vite'); const args = process.argv.slice(2); const sizeIndex = args.indexOf('--size'); const [width, height] = (sizeIndex === -1 ? '1440x900' : (args[sizeIndex + 1] ?? '')) .split('x') .map(Number); if (!width || !height) throw new Error('Ожидается --size ШИРИНАxВЫСОТА, например --size 1440x900'); const requested = args.filter((arg, index) => arg.startsWith('/') && index !== sizeIndex + 1); const routes = requested.length > 0 ? requested : ['/showcase']; await mkdir(shotsDir, { recursive: true }); await build({ logLevel: 'warn' }); const server = await preview({ preview: { open: false } }); const origin = server.resolvedUrls?.local[0]?.replace(/\/$/, ''); if (!origin) throw new Error('vite preview не отдал локальный адрес'); const browser = await chromium.launch(); // deviceScaleFactor 2 — референс снят на macOS при 2x, иначе снимки несравнимы по детализации. const page = await browser.newPage({ viewport: { width, height }, deviceScaleFactor: 2 }); for (const route of routes) { await page.goto(`${origin}${route}`, { waitUntil: 'networkidle' }); // Без этого в кадр попадает фолбэк-шрифт: метрики и плотность будут не те. await page.evaluate(() => document.fonts.ready); const file = resolve(shotsDir, `${route.replace(/^\//, '').replace(/\//g, '-') || 'index'}.png`); await page.screenshot({ path: file }); console.log(`${route} → ${file} (${width}x${height} @2x)`); } await browser.close(); await server.close();