Files
andrey e889cf8e45 Фаза 2: инфраструктура разработки и тестирования
- tools/check.mjs: проверка синтаксиса JS из <script> в index.html через node --check
  (перевод номера строки в координаты index.html), exit-коды 0/1.
- tests/: каркас headless-суиты на puppeteer-core (ok/waitState/waitCond/sleep,
  viewport 390x844@2, контроль pageerror/console.error, ИТОГ N/N и exit code).
- index.html: минимальный скелет (canvas/DPR/resize, state=MENU, SAVE_KEY, 60x60)
  без игровой логики — она в фазе 3.
- Прогоны: check: OK; npm test — ИТОГ 12/12, exit 0.
- Ревью-гейт 2 (Oracle): PASS; minor/nit перенесены в фазу 3.
2026-10-11 08:47:11 +03:00

76 lines
3.5 KiB
JavaScript
Raw Permalink 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.
#!/usr/bin/env node
// Проверка синтаксиса JS, встроенного в index.html: вынимает содержимое <script>-блоков,
// прогоняет каждый через node --check во временном файле. Автоматизирует п.1 раздела
// «Проверка» из AGENTS.md. Чистый Node, без зависимостей.
// Запуск: node tools/check.mjs [путь-к-html] (по умолчанию index.html в корне репо)
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const htmlPath = process.argv[2] || join(root, 'index.html');
let html;
try {
html = readFileSync(htmlPath, 'utf8');
} catch (e) {
console.error(`check: не удалось прочитать ${htmlPath}: ${e.message}`);
process.exit(1);
}
// Допустимые типы скриптов с JS (у игры тип не указан); <script src=...> и не-JS блоки пропускаем.
const JS_TYPES = new Set(['', 'text/javascript', 'application/javascript', 'module']);
const re = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
const blocks = [];
for (const m of html.matchAll(re)) {
const attrs = m[1];
const code = m[2];
if (/\bsrc\s*=/i.test(attrs)) continue;
const type = (attrs.match(/\btype\s*=\s*["']?([^"'\s>]*)/i)?.[1] ?? '').toLowerCase();
if (!JS_TYPES.has(type)) continue;
if (!code.trim()) continue;
// 1-я строка index.html, с которой начинается код блока (сразу после ">").
const lineInHtml = html.slice(0, m.index + m[0].indexOf(code)).split('\n').length;
blocks.push({ code, lineInHtml, isModule: type === 'module' });
}
if (!blocks.length) {
console.error(`check: в ${htmlPath} не найдено <script>-блоков с JS`);
process.exit(1);
}
const tmp = mkdtempSync(join(tmpdir(), 'htmljs-check-'));
let failed = false;
try {
for (const [i, b] of blocks.entries()) {
const file = join(tmp, `block-${i + 1}.${b.isModule ? 'mjs' : 'js'}`);
writeFileSync(file, b.code);
const r = spawnSync(process.execPath, ['--check', file], { encoding: 'utf8' });
if (r.status !== 0) {
failed = true;
const out = (r.stderr || r.stdout || String(r.error)).trim();
// node --check пишет в первой строке stderr "<файл>:<строка>" —
// переводим номер строки в координаты index.html (строки, не столбцы).
const mm = out.match(new RegExp(`block-${i + 1}\\.(?:mjs|js):(\\d+)`));
if (mm) {
const htmlLine = Number(mm[1]) + b.lineInHtml - 1;
const rest = out.split('\n').slice(1).join('\n');
console.error(`check: ОШИБКА синтаксиса в ${htmlPath}:${htmlLine} (блок ${i + 1} из ${blocks.length})`);
if (rest) console.error(rest);
} else {
console.error(`check: ОШИБКА синтаксиса в блоке ${i + 1} из ${blocks.length} (${htmlPath})`);
console.error(out);
}
}
}
} finally {
rmSync(tmp, { recursive: true, force: true });
}
if (failed) process.exit(1);
const jsLines = blocks.reduce((s, b) => s + b.code.split('\n').length, 0);
console.log(`check: OK — ${htmlPath}: блоков ${blocks.length}, строк JS ${jsLines}, синтаксис корректен`);