Skip to main content
Glama
generate-coverage-report.cjs7.34 kB
#!/usr/bin/env node /** * Generate comprehensive test coverage report * Works around Jest TypeScript coverage limitations */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); console.log('🔍 Generating comprehensive test coverage report...\n'); // Step 1: Run tests and collect coverage from built files console.log('📊 Running test suite with coverage collection...'); try { // Run tests with coverage on dist files (which have source maps) const testCommand = 'NODE_OPTIONS="--experimental-vm-modules" npx jest --config=jest.typescript.config.json --coverage --silent --maxWorkers=1'; const testOutput = execSync(testCommand, { encoding: 'utf8', cwd: process.cwd(), timeout: 120000 // 2 minute timeout }); console.log('✅ Test execution completed'); } catch (error) { console.warn('⚠️ Some tests may have failed, but continuing with coverage analysis'); // Continue even if some tests fail } // Step 2: Analyze coverage data let coverageData = {}; let coverageSummary = null; try { if (fs.existsSync('coverage/coverage-final.json')) { const rawData = fs.readFileSync('coverage/coverage-final.json', 'utf8'); coverageData = JSON.parse(rawData); console.log('✅ Coverage data found'); } else { console.warn('⚠️ No coverage-final.json found'); } } catch (error) { console.warn('⚠️ Could not parse coverage data:', error.message); } // Step 3: Generate manual coverage statistics console.log('\n📈 Analyzing test coverage...'); // Get test file counts const testFiles = [ 'tests/utils/', 'tests/config/', 'tests/server/', 'tests/managers/', 'tests/security/', 'tests/property/', 'tests/unit/', 'tests/cache/', 'tests/tools/' ].flatMap(dir => { try { if (!fs.existsSync(dir)) return []; return fs.readdirSync(dir) .filter(file => file.endsWith('.test.js')) .map(file => path.join(dir, file)); } catch (error) { return []; } }); // Get source file counts const sourceFiles = [ 'src/utils/', 'src/config/', 'src/client/', 'src/tools/', 'src/server/', 'src/cache/', 'src/performance/', 'src/security/' ].flatMap(dir => { try { if (!fs.existsSync(dir)) return []; return fs.readdirSync(dir, { recursive: true }) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.includes('.test.')) .map(file => path.join(dir, file)); } catch (error) { return []; } }); // Calculate test-to-source ratios const testCoverage = { testFiles: testFiles.length, sourceFiles: sourceFiles.length, testRatio: testFiles.length > 0 ? (testFiles.length / sourceFiles.length * 100).toFixed(1) : '0.0' }; console.log('\n📊 Test Coverage Analysis:'); console.log('============================'); console.log(`Total Source Files: ${testCoverage.sourceFiles}`); console.log(`Total Test Files: ${testCoverage.testFiles}`); console.log(`Test-to-Source Ratio: ${testCoverage.testRatio}%`); // Step 4: Analyze specific module coverage const moduleAnalysis = { 'utils/': { source: sourceFiles.filter(f => f.startsWith('src/utils/')).length, tests: testFiles.filter(f => f.startsWith('tests/utils/')).length }, 'config/': { source: sourceFiles.filter(f => f.startsWith('src/config/')).length, tests: testFiles.filter(f => f.startsWith('tests/config/')).length }, 'client/': { source: sourceFiles.filter(f => f.startsWith('src/client/')).length, tests: testFiles.filter(f => f.startsWith('tests/client/') || f.startsWith('tests/managers/')).length }, 'tools/': { source: sourceFiles.filter(f => f.startsWith('src/tools/')).length, tests: testFiles.filter(f => f.startsWith('tests/tools/')).length }, 'server/': { source: sourceFiles.filter(f => f.startsWith('src/server/')).length, tests: testFiles.filter(f => f.startsWith('tests/server/')).length } }; console.log('\n🎯 Module-Specific Coverage:'); console.log('=============================='); Object.entries(moduleAnalysis).forEach(([module, stats]) => { const ratio = stats.source > 0 ? (stats.tests / stats.source * 100).toFixed(1) : '0.0'; const status = parseFloat(ratio) >= 50 ? '✅' : parseFloat(ratio) >= 25 ? '⚠️' : '❌'; console.log(`${status} ${module}: ${stats.tests}/${stats.source} files (${ratio}%)`); }); // Step 5: Extract actual coverage from HTML report if available let actualCoverage = null; try { if (fs.existsSync('coverage/index.html')) { const htmlContent = fs.readFileSync('coverage/index.html', 'utf8'); // Try to extract coverage percentages from HTML const extractPercentage = (metric) => { const regex = new RegExp(`${metric}[^>]*>\\s*([0-9.]+)%`, 'i'); const match = htmlContent.match(regex); return match ? parseFloat(match[1]) : null; }; actualCoverage = { statements: extractPercentage('statements') || 0, branches: extractPercentage('branches') || 0, functions: extractPercentage('functions') || 0, lines: extractPercentage('lines') || 0 }; } } catch (error) { console.warn('⚠️ Could not extract coverage from HTML report'); } // Step 6: Generate final report console.log('\n🎯 Final Coverage Report:'); console.log('=========================='); if (actualCoverage && (actualCoverage.lines > 0 || actualCoverage.statements > 0)) { console.log(`Lines: ${actualCoverage.lines.toFixed(2)}%`); console.log(`Statements: ${actualCoverage.statements.toFixed(2)}%`); console.log(`Branches: ${actualCoverage.branches.toFixed(2)}%`); console.log(`Functions: ${actualCoverage.functions.toFixed(2)}%`); } else { // Fallback: estimate based on test coverage const estimatedCoverage = Math.min(parseFloat(testCoverage.testRatio) * 0.6, 85); // Conservative estimate console.log(`Estimated Coverage: ~${estimatedCoverage.toFixed(1)}% (based on test-to-source ratio)`); console.log('Note: Actual line-level coverage requires proper Jest/TypeScript integration'); } // Step 7: Recommendations console.log('\n💡 Coverage Improvement Recommendations:'); console.log('========================================='); const lowCoverageModules = Object.entries(moduleAnalysis) .filter(([_, stats]) => stats.source > 0 && (stats.tests / stats.source) < 0.5) .map(([module, stats]) => ({ module, deficit: stats.source - stats.tests, ratio: stats.tests / stats.source })) .sort((a, b) => b.deficit - a.deficit); if (lowCoverageModules.length > 0) { console.log('🎯 Priority areas for test improvement:'); lowCoverageModules.forEach((item, index) => { console.log(`${index + 1}. ${item.module} - Add ${item.deficit} more test files (${(item.ratio * 100).toFixed(1)}% coverage)`); }); } else { console.log('✅ All modules have good test coverage ratios!'); } // Step 8: Save report const report = { timestamp: new Date().toISOString(), testCoverage, moduleAnalysis, actualCoverage, recommendations: lowCoverageModules }; fs.writeFileSync('coverage/coverage-analysis.json', JSON.stringify(report, null, 2)); console.log('\n✅ Coverage analysis saved to coverage/coverage-analysis.json'); // Exit with appropriate code const overallSuccess = testCoverage.testRatio >= 30 && lowCoverageModules.length <= 2; process.exit(overallSuccess ? 0 : 1);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server