coverage-runner
Version:
Unified coverage analysis tool for Jest, Vitest, and other test frameworks with intelligent merging and reporting
209 lines ⢠9.72 kB
JavaScript
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