#!/usr/bin/env node /** * run-test-json (Level B) — прогон одного тест-файла vitest с json-репортером и разбор итога. * Анти-вакуум (SE-LB-1/11): «прошёл» = allGreen И numPassed>0 (0 собранных тестов НЕ зелёный). * loadError — модуль не импортировался / json не разобран (ошибка сборки мутанта): НЕ ассерт-провал, * mutate-runner трактует как inconclusive (не «убит»). Чистый parseVitestJson + I/O runVitestJson. */ import { execFileSync } from 'node:child_process'; import { resolveVitestConfig } from './produce-verify-receipt.mjs'; /** Разобрать stdout vitest --reporter json. Битый/пустой → loadError. */ export function parseVitestJson(jsonText) { let data; try { data = JSON.parse(String(jsonText)); } catch { return { allGreen: false, numTests: 0, numPassed: 0, numFailed: 0, loadError: true }; } if (!data || typeof data !== 'object') { return { allGreen: false, numTests: 0, numPassed: 0, numFailed: 0, loadError: true }; } const numTests = Number(data.numTotalTests || 0); const numPassed = Number(data.numPassedTests || 0); const numFailed = Number(data.numFailedTests || 0); const allGreen = numFailed === 0 && numPassed > 0; return { allGreen, numTests, numPassed, numFailed, loadError: false }; } /** Аргументы `npx` для прогона одного тест-файла. Корень и конфиг сюиты — ОТ КОРНЯ репозитория * через общий resolveVitestConfig (есть app/ → app-режим, иначе корень); без хардкода app/. * `exists` инъектируется (для теста); по умолчанию resolveVitestConfig берёт existsSync. */ export function buildVitestJsonArgs(testFile, gitCwd, exists) { const { root, config } = resolveVitestConfig(gitCwd, exists); return ['vitest', 'run', testFile, '--root', root, '--config', config, '--reporter', 'json']; } /** I/O: прогнать testFile через vitest json. exit≠0 (упавшие тесты) — это нормально, парсим stdout. */ export function runVitestJson(testFile, gitCwd) { let stdout = ''; try { stdout = execFileSync('npx', buildVitestJsonArgs(testFile, gitCwd), { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch (e) { // Упавшие тесты → execFileSync бросает с ненулевым кодом; stdout всё равно несёт json. stdout = (e && e.stdout) ? String(e.stdout) : ''; } return parseVitestJson(stdout); }