textmachine/frontend/scripts/shot.mjs

60 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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