Files
stockfill/scripts/normalize-lcov.cjs
beatzaplenty 6192bd20eb modified: .gitignore
modified:   .vscode/launch.json
	new file:   e2e/playwright-collect-coverage.spec.ts
	modified:   package-lock.json
	modified:   package.json
	modified:   playwright.config.ts
	new file:   scripts/merge-lcov.cjs
	new file:   scripts/normalize-lcov.cjs
	new file:   scripts/playwright-collect-coverage.cjs
	new file:   scripts/remap-and-report.cjs
	modified:   vite.config.ts
	new file:   vitest.config.ts
2025-12-02 16:14:12 +10:00

135 lines
4.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
// scripts/normalize-lcov.cjs
// Usage: node scripts/normalize-lcov.cjs input.lcov [output.lcov]
const fs = require('fs');
const path = require('path');
const repoRoot = process.cwd();
const inFile = process.argv[2] || 'coverage-reports/merged/merged.lcov';
const outFile = process.argv[3] || inFile.replace(/\.lcov$/, '.normalized.lcov');
if (!fs.existsSync(inFile)) {
console.error('Input file not found:', inFile);
process.exit(2);
}
const content = fs.readFileSync(inFile, 'utf8');
const lines = content.split(/\r?\n/);
// split into records separated by "end_of_record"
const records = [];
let cur = [];
for (const L of lines) {
cur.push(L);
if (L.trim() === 'end_of_record') {
records.push(cur.join('\n'));
cur = [];
}
}
// if file didn't end with end_of_record, push remainder
if (cur.length) records.push(cur.join('\n'));
function exists(p) {
try { return fs.existsSync(p); } catch { return false; }
}
function findInSrc(basename) {
const srcRoot = path.join(repoRoot, 'src');
if (!fs.existsSync(srcRoot)) return null;
// recursive search (stop when found)
function walk(dir) {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const it of items) {
const full = path.join(dir, it.name);
if (it.isFile() && it.name === basename) return full;
if (it.isDirectory() && it.name !== 'node_modules' && it.name !== '.git') {
const found = walk(full);
if (found) return found;
}
}
return null;
}
return walk(srcRoot);
}
function normalizeSF(sfLine) {
// sfLine is the part after 'SF:'
let sf = sfLine.trim();
// Skip clearly virtual / query-string entries
if (sf.startsWith('@') || sf.includes('?v=') || sf === '@react-refresh') {
return null;
}
// If it's an absolute path that exists, make it repo-relative.
if (path.isAbsolute(sf)) {
if (exists(sf)) return path.relative(repoRoot, sf);
// Try chopping off any leading workspace prefix and look again
const repoName = path.basename(repoRoot);
const idx = sf.indexOf(`/${repoName}/`);
if (idx !== -1) {
const candidate = sf.slice(idx + repoName.length + 2); // after /repoName/
if (exists(path.join(repoRoot, candidate))) return candidate;
if (exists(path.join(repoRoot, 'src', candidate))) return path.posix.join('src', candidate);
}
}
// If it's already relative, check relative to repo root
if (!path.isAbsolute(sf)) {
if (exists(path.join(repoRoot, sf))) return sf;
// If it looks like "stockfill/..." or "<repoName>/..." try to strip the repo prefix
const repoName = path.basename(repoRoot);
if (sf.startsWith(`${repoName}/`) || sf.startsWith('stockfill/')) {
const candidate = sf.replace(new RegExp(`^(${repoName}|stockfill)/`), '');
if (exists(path.join(repoRoot, candidate))) return candidate;
if (exists(path.join(repoRoot, 'src', candidate))) return path.posix.join('src', candidate);
}
// If it is a bare filename "DBProvider.tsx" try src/
if (!sf.includes('/') && exists(path.join(repoRoot, 'src', sf))) {
return path.posix.join('src', sf);
}
}
// If it contains '/src/' somewhere, try to extract the src/... suffix
const srcIdx = sf.indexOf('/src/');
if (srcIdx !== -1) {
const candidate = sf.slice(srcIdx + 1); // remove leading slash
if (exists(path.join(repoRoot, candidate))) return candidate;
}
// Finally, try finding by basename under src
const base = path.basename(sf);
const found = findInSrc(base);
if (found) return path.relative(repoRoot, found);
// give up
return null;
}
let kept = 0, dropped = 0;
const outRecords = records.map(rec => {
// find SF line
const m = rec.match(/^SF:(.*)$/m);
if (!m) {
// no SF line? keep it (unlikely)
kept++;
return rec;
}
const sfRaw = m[1].trim();
const normalized = normalizeSF(sfRaw);
if (!normalized) {
dropped++;
return null; // drop entire record
}
// replace SF:... with normalized relative path using posix separators
const normalizedPosix = normalized.split(path.sep).join(path.posix.sep);
const newRec = rec.replace(/^SF:.*$/m, `SF:${normalizedPosix}`);
kept++;
return newRec;
}).filter(Boolean);
fs.writeFileSync(outFile, outRecords.join('\n') + '\n');
console.log(`Wrote ${outFile} (kept ${kept}, dropped ${dropped} records)`);