import { mkdir, readFile, writeFile } from "node:fs/promises"; import path from "node:path"; const repoRoot = process.cwd(); const packageName = process.argv[2] || "userfront"; const lcovPath = path.join(repoRoot, packageName, "coverage", "lcov.info"); const reportsDir = path.join(repoRoot, "reports"); const excludedSourceFiles = new Set(["lib/main.dart", "lib/i18n_data.dart"]); function normalizeSourcePath(sourcePath) { const normalized = sourcePath.replace(/\\/g, "/"); const packagePrefix = `${packageName}/`; return normalized.startsWith(packagePrefix) ? normalized.slice(packagePrefix.length) : normalized; } function shouldExcludeSource(sourcePath) { const normalized = normalizeSourcePath(sourcePath); return ( excludedSourceFiles.has(normalized) || normalized.endsWith(".g.dart") || normalized.endsWith(".freezed.dart") ); } function parseLcov(raw) { const records = []; let current = null; for (const line of raw.split(/\r?\n/)) { if (line.startsWith("SF:")) { current = { sourceFile: normalizeSourcePath(line.slice(3)), lines: [] }; continue; } if (!current) continue; if (line.startsWith("DA:")) { const [, hitsValue] = line.slice(3).split(","); const hits = Number(hitsValue); current.lines.push(Number.isFinite(hits) ? hits : 0); continue; } if (line === "end_of_record") { records.push(current); current = null; } } if (current) { records.push(current); } return records; } function formatPct(value) { return `${value.toFixed(2)}%`; } function renderMarkdown(row) { return [ "# Userfront Flutter Coverage Summary", "", "| Package | Lines | Covered / Total | LCOV |", "| --- | ---: | ---: | --- |", `| ${row.package} | ${formatPct(row.lines)} | ${row.coveredLines} / ${row.totalLines} | ${row.lcovPath} |`, "", "Coverage excludes Flutter bootstrap/generated files: `lib/main.dart`, `lib/i18n_data.dart`, `*.g.dart`, `*.freezed.dart`.", "", ].join("\n"); } const lcov = await readFile(lcovPath, "utf8"); const includedRecords = parseLcov(lcov).filter( (record) => !shouldExcludeSource(record.sourceFile), ); const totalLines = includedRecords.reduce( (total, record) => total + record.lines.length, 0, ); const coveredLines = includedRecords.reduce( (total, record) => total + record.lines.filter((hits) => hits > 0).length, 0, ); const lineCoverage = totalLines === 0 ? 0 : (coveredLines / totalLines) * 100; const row = { package: packageName, statements: lineCoverage, branches: null, functions: null, lines: lineCoverage, coveredLines, totalLines, summaryPath: "reports/package-coverage-summary.json", htmlPath: null, lcovPath: `${packageName}/coverage/lcov.info`, }; await mkdir(reportsDir, { recursive: true }); await writeFile( path.join(reportsDir, "package-coverage-summary.json"), `${JSON.stringify({ packages: [row] }, null, 2)}\n`, ); await writeFile( path.join(reportsDir, `${packageName}-coverage-summary.md`), renderMarkdown(row), ); console.log(renderMarkdown(row));