#!/usr/bin/env node /** * Coverage Trend Script * * Reads Vitest coverage summary and appends to a JSON trend file. * Usage: node scripts/coverage-trend.mjs [coverage-summary.json] [trend-output.json] */ import { readFileSync, writeFileSync, existsSync } from 'fs'; const summaryPath = process.argv[2] || 'apps/web/coverage/coverage-summary.json'; const trendPath = process.argv[3] || 'coverage-trend.json'; function readTrend() { if (existsSync(trendPath)) { return JSON.parse(readFileSync(trendPath, 'utf8')); } return { entries: [] }; } function extractCoverage(summaryPath) { if (!existsSync(summaryPath)) { console.error(`Coverage summary not found: ${summaryPath}`); return null; } const summary = JSON.parse(readFileSync(summaryPath, 'utf8')); const total = summary.total || {}; return { date: new Date().toISOString().split('T')[0], commit: process.env.GITHUB_SHA?.slice(0, 7) || 'local', lines: total.lines?.pct ?? 0, branches: total.branches?.pct ?? 0, functions: total.functions?.pct ?? 0, statements: total.statements?.pct ?? 0, }; } const coverage = extractCoverage(summaryPath); if (coverage) { const trend = readTrend(); // Keep last 100 entries trend.entries.push(coverage); if (trend.entries.length > 100) { trend.entries = trend.entries.slice(-100); } writeFileSync(trendPath, JSON.stringify(trend, null, 2)); console.log(`Coverage trend updated: lines=${coverage.lines}%, branches=${coverage.branches}%`); // Check for regression (> 2% drop from last entry) if (trend.entries.length >= 2) { const prev = trend.entries[trend.entries.length - 2]; const linesDrop = prev.lines - coverage.lines; if (linesDrop > 2) { console.warn(`WARNING: Line coverage dropped ${linesDrop.toFixed(1)}% (${prev.lines}% -> ${coverage.lines}%)`); } } }