UNPKG

coverage-runner

Version:

Unified coverage analysis tool for Jest, Vitest, and other test frameworks with intelligent merging and reporting

209 lines • 9.72 kB
#!/usr/bin/env node import { Command } from 'commander'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { setDebugMode, logger } from '../src/utils/logger.js'; import { detectRunners } from '../src/utils/detectRunners.js'; import { RunnerFactory } from '../src/runners/RunnerFactory.js'; import { mergeCoverageFiles } from '../src/commands/mergeCoverage.js'; import { executeInClonedRepo } from '../src/utils/executeInClonedRepo.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); function getVersion() { try { const packageJsonPath = join(__dirname, '..', 'package.json'); const packageJsonContent = readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); return packageJson.version ?? '0.1.0-beta.1'; } catch { return '0.1.0-beta.1'; } } function createCLI() { const program = new Command(); program .name('coverage-runner') .description('A tool for running and managing code coverage analysis') .version(getVersion()) .option('-d, --debug', 'enable debug output'); program .command('detect') .description('Detect test runners in the current project') .option('-p, --path <path>', 'path to package.json file') .action((options) => { if (program.opts().debug === true) { setDebugMode(true); } const runners = detectRunners(options.path); if (runners.length > 0) { console.log(`Detected test runners: ${runners.join(', ')}`); } else { console.log('No test runners detected'); } }); program .command('run') .description('Run coverage analysis for detected test runners') .option('-p, --path <path>', 'path to package.json file') .option('-r, --repo <url>', 'clone and analyze remote repository') .option('-o, --output <dir>', 'output directory for coverage reports', 'coverage-merged') .option('-b, --branch <branch>', 'git branch to checkout (only with --repo)') .option('--no-install', 'skip dependency installation (only with --repo)') .option('--no-cleanup', 'keep cloned repository after analysis (only with --repo)') .option('--timeout <ms>', 'timeout for clone and install operations in milliseconds', '300000') .action(async (options) => { if (program.opts().debug === true) { setDebugMode(true); } try { if (options.repo !== undefined && options.repo !== '') { console.log(`🌐 Analyzing remote repository: ${options.repo}`); const result = await executeInClonedRepo(options.repo, { outputDir: options.output, branch: options.branch, installDependencies: options.install, cleanup: options.cleanup, timeout: parseInt(options.timeout), }); if (result.success) { console.log('āœ… Remote repository analysis completed successfully!'); console.log(` šŸ“ Output directory: ${result.outputDir}`); if (result.runners && result.runners.length > 0) { console.log(` šŸƒ Runners used: ${result.runners.join(', ')}`); } if (result.coverageFiles && result.coverageFiles.length > 0) { console.log(` šŸ“Š Coverage files generated: ${result.coverageFiles.length}`); } if (!options.cleanup && result.clonedPath !== undefined && result.clonedPath !== '') { console.log(` šŸ“‚ Cloned repository preserved at: ${result.clonedPath}`); } } else { console.error('āŒ Remote repository analysis failed'); console.error(` Error: ${result.error}`); process.exit(1); } return; } const runners = detectRunners(options.path); if (runners.length === 0) { console.log('No test runners detected. Skipping coverage analysis.'); return; } console.log(`Running coverage analysis for: ${runners.join(', ')}`); for (const runnerType of runners) { try { console.log(`\nšŸƒ Running ${runnerType} coverage...`); const runner = RunnerFactory.createRunner(runnerType); const result = await runner.runCoverage(); if (result.success) { console.log(`āœ… ${runnerType} coverage completed successfully`); console.log(` Output: ${result.outputPath}`); if (result.duration !== undefined && result.duration !== null && !isNaN(result.duration)) { console.log(` Duration: ${result.duration}ms`); } } else { console.error(`āŒ ${runnerType} coverage failed (exit code: ${result.exitCode})`); if (result.stderr !== undefined && result.stderr !== null && result.stderr !== '') { console.error(` Error: ${result.stderr}`); } } } catch (error) { console.error(`āŒ Failed to run ${runnerType}:`, error); } } console.log('\nšŸŽ‰ Coverage analysis completed!'); } catch (error) { logger.error('Failed to run coverage analysis:', error); process.exit(1); } }); program .command('merge') .description('Merge multiple coverage files into a single output') .option('-i, --input <patterns...>', 'input coverage file patterns (e.g., "coverage/*.lcov" "coverage/*.xml")') .option('-o, --output <dir>', 'output directory for merged coverage', 'coverage-merged') .option('--json-only', 'output only JSON format (skip LCOV) [deprecated: use --format]') .option('--format <formats...>', 'output formats: json, lcov, text (can specify multiple)', ['json', 'lcov']) .option('--text-details', 'include detailed coverage report when using text format') .option('--normalize-paths', 'normalize file paths to handle different formats') .option('--root-dir <dir>', 'root directory for path normalization') .action(async (options) => { if (program.opts().debug === true) { setDebugMode(true); } try { if (!options.input || options.input.length === 0) { console.error('āŒ No input files specified. Use -i/--input to specify coverage files.'); process.exit(1); } console.log('šŸ”„ Merging coverage files...'); logger.debug(`Input patterns: ${options.input.join(', ')}`); logger.debug(`Output directory: ${options.output}`); logger.debug(`JSON only: ${options.jsonOnly ?? false}`); logger.debug(`Format: ${options.format?.join(', ') ?? 'default'}`); logger.debug(`Text details: ${options.textDetails ?? false}`); logger.debug(`Normalize paths: ${options.normalizePaths ?? false}`); const result = await mergeCoverageFiles({ inputPatterns: options.input, outputDir: options.output, jsonOnly: options.jsonOnly ?? false, ...(options.format && { format: options.format, }), textDetails: options.textDetails ?? false, normalizePaths: options.normalizePaths ?? false, rootDir: options.rootDir, }); if (result.success) { console.log('āœ… Coverage files merged successfully!'); console.log(` šŸ“ Output directory: ${result.outputDir}`); console.log(` šŸ“Š Files processed: ${result.filesProcessed}`); console.log(` šŸ“ Unique files in output: ${result.uniqueFiles}`); if (result.normalizedPaths !== undefined && result.normalizedPaths !== null && !isNaN(result.normalizedPaths)) { console.log(` šŸ”„ Paths normalized: ${result.normalizedPaths}`); } } else { console.error('āŒ Coverage merging failed'); if (result.error !== undefined && result.error !== null && result.error !== '') { console.error(` Error: ${result.error}`); } process.exit(1); } } catch (error) { logger.error('Failed to merge coverage files:', error); process.exit(1); } }); program.action(() => { program.help(); }); return program; } function main() { const program = createCLI(); program.parse(); } if (import.meta.url === `file://${process.argv[1]}`) { main(); } export { createCLI, main }; //# sourceMappingURL=coverage-runner.js.map