@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
164 lines (133 loc) • 5.56 kB
JavaScript
const fs = require('fs');
const path = require('path');
function getCoverageStats() {
const coveragePath = path.join(process.cwd(), 'coverage/coverage-final.json');
if (!fs.existsSync(coveragePath)) {
return { overall: { statements: 0, branches: 0, functions: 0, lines: 0 }, bySuite: {} };
}
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
let totalStatements = 0, hitStatements = 0;
let totalBranches = 0, hitBranches = 0;
let totalFunctions = 0, hitFunctions = 0;
let totalLines = 0, hitLines = 0;
const bySuite = {};
for (const [file, data] of Object.entries(coverage)) {
// Determine suite from file path
const relativePath = path.relative(process.cwd(), file);
const suite = relativePath.split(path.sep)[0] || 'root';
if (!bySuite[suite]) {
bySuite[suite] = { statements: 0, branches: 0, functions: 0, lines: 0, files: 0 };
}
bySuite[suite].files++;
let fileStatements = 0, fileHitStatements = 0;
let fileBranches = 0, fileHitBranches = 0;
let fileFunctions = 0, fileHitFunctions = 0;
let fileLines = 0, fileHitLines = 0;
if (data.s) {
fileStatements = Object.keys(data.s).length;
fileHitStatements = Object.values(data.s).filter(x => x > 0).length;
totalStatements += fileStatements;
hitStatements += fileHitStatements;
}
if (data.b) {
for (const branches of Object.values(data.b)) {
fileBranches += branches.length;
fileHitBranches += branches.filter(x => x > 0).length;
}
totalBranches += fileBranches;
hitBranches += fileHitBranches;
}
if (data.f) {
fileFunctions = Object.keys(data.f).length;
fileHitFunctions = Object.values(data.f).filter(x => x > 0).length;
totalFunctions += fileFunctions;
hitFunctions += fileHitFunctions;
}
// Use statementMap for line coverage if l is not available
if (data.statementMap) {
const lineNumbers = new Set();
Object.values(data.statementMap).forEach(stmt => {
lineNumbers.add(stmt.start.line);
});
fileLines = lineNumbers.size;
totalLines += fileLines;
// Count hit lines by matching with statement hits
const hitLineNumbers = new Set();
Object.entries(data.s || {}).forEach(([index, hits]) => {
if (hits > 0 && data.statementMap[index]) {
hitLineNumbers.add(data.statementMap[index].start.line);
}
});
fileHitLines = hitLineNumbers.size;
hitLines += fileHitLines;
}
// Add to suite totals
bySuite[suite].statements += fileStatements > 0 ? Math.round((fileHitStatements / fileStatements) * 100) : 0;
bySuite[suite].branches += fileBranches > 0 ? Math.round((fileHitBranches / fileBranches) * 100) : 0;
bySuite[suite].functions += fileFunctions > 0 ? Math.round((fileHitFunctions / fileFunctions) * 100) : 0;
bySuite[suite].lines += fileLines > 0 ? Math.round((fileHitLines / fileLines) * 100) : 0;
}
// Average by suite
Object.keys(bySuite).forEach(suite => {
const s = bySuite[suite];
s.statements = Math.round(s.statements / s.files);
s.branches = Math.round(s.branches / s.files);
s.functions = Math.round(s.functions / s.files);
s.lines = Math.round(s.lines / s.files);
});
return {
overall: {
statements: Math.round((hitStatements / totalStatements) * 100) || 0,
branches: Math.round((hitBranches / totalBranches) * 100) || 0,
functions: Math.round((hitFunctions / totalFunctions) * 100) || 0,
lines: Math.round((hitLines / totalLines) * 100) || 0
},
bySuite
};
}
function generateCoverageBadges(stats) {
const badges = [];
for (const [type, percent] of Object.entries(stats)) {
const color = percent >= 90 ? 'brightgreen' : percent >= 80 ? 'yellow' : percent >= 70 ? 'orange' : 'red';
badges.push(``);
}
return badges.join(' ');
}
function injectCoverage() {
const readmePath = path.join(process.cwd(), 'README.md');
let readme = '';
if (fs.existsSync(readmePath)) {
readme = fs.readFileSync(readmePath, 'utf8');
}
const { overall, bySuite } = getCoverageStats();
const badges = generateCoverageBadges(overall);
let coverageSection = `# @wearesage/schema
> A TypeScript decorator-based schema definition system that works across multiple database adapters (PostgreSQL, MongoDB, Neo4j, Redis, SQLite). Define your entities once with decorators, then use them with any supported database through a unified repository interface.
## Coverage
${badges}
### Overall
| Metric | Percentage |
|--------|-----------|
| Statements | ${overall.statements}% |
| Branches | ${overall.branches}% |
| Functions | ${overall.functions}% |
| Lines | ${overall.lines}% |
### By Module
| Module | Statements | Branches | Functions | Lines |
|---------|-----------|----------|-----------|-------|
`;
// Add suite-specific coverage
Object.entries(bySuite).forEach(([suite, stats]) => {
coverageSection += `| ${suite} | ${stats.statements}% | ${stats.branches}% | ${stats.functions}% | ${stats.lines}% |\n`;
});
coverageSection += '\n';
// Replace entire README since we're including the title
readme = coverageSection;
fs.writeFileSync(readmePath, readme);
console.log('✅ Coverage injected into README.md');
}
if (require.main === module) {
injectCoverage();
}
module.exports = { injectCoverage, getCoverageStats };