60 lines
3.1 KiB
JavaScript
60 lines
3.1 KiB
JavaScript
// Скриншот-цикл: собрать, поднять 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();
|