stock-bot/tools/coverage-cli/src/index.ts

127 lines
No EOL
4.6 KiB
TypeScript

#!/usr/bin/env bun
import { Command } from 'commander';
import { existsSync } from 'fs';
import { resolve } from 'path';
import chalk from 'chalk';
import { loadConfig } from './config';
import { CoverageRunner } from './runner';
import { CoverageProcessor } from './processor';
import { ReporterManager } from './reporters';
import type { CLIOptions } from './types';
const program = new Command();
program
.name('stock-bot-coverage')
.description('Advanced coverage tool for Stock Bot with exclusion support and beautiful reporting')
.version('1.0.0')
.option('-p, --packages <packages...>', 'Run coverage for specific packages')
.option('-e, --exclude <patterns...>', 'Glob patterns to exclude from coverage')
.option('-i, --include <patterns...>', 'Glob patterns to include in coverage')
.option('-r, --reporters <reporters...>', 'Coverage reporters (terminal, html, json, markdown, text)')
.option('-t, --threshold <number>', 'Set coverage threshold for all metrics', parseFloat)
.option('--threshold-lines <number>', 'Set line coverage threshold', parseFloat)
.option('--threshold-functions <number>', 'Set function coverage threshold', parseFloat)
.option('--threshold-branches <number>', 'Set branch coverage threshold', parseFloat)
.option('--threshold-statements <number>', 'Set statement coverage threshold', parseFloat)
.option('-o, --output-dir <path>', 'Output directory for reports')
.option('-c, --config <path>', 'Path to coverage config file')
.option('--fail-under', 'Exit with non-zero code if coverage is below threshold')
.action(async (options: CLIOptions) => {
try {
console.log(chalk.bold.blue('\n🚀 Stock Bot Coverage Tool\n'));
// Load configuration
const config = loadConfig(options);
console.log(chalk.gray('Configuration loaded'));
console.log(chalk.gray(`Workspace root: ${config.workspaceRoot}`));
console.log(chalk.gray(`Excluded patterns: ${config.exclude.length}`));
console.log(chalk.gray(`Reporters: ${config.reporters.join(', ')}\n`));
// Run coverage
const runner = new CoverageRunner(config);
console.log(chalk.yellow('Running tests with coverage...\n'));
const result = await runner.run();
if (!result.success) {
console.error(chalk.red('\n❌ Some tests failed'));
if (options.failUnder) {
process.exit(1);
}
}
// Process coverage data
const processor = new CoverageProcessor(config);
const report = processor.process(result.coverage, result.testResults);
// Generate reports
console.log(chalk.yellow('\nGenerating reports...\n'));
const reporterManager = new ReporterManager();
await reporterManager.report(report, config.reporters, config.outputDir);
// Check thresholds
if (options.failUnder) {
const thresholdResult = processor.checkThresholds(report);
if (!thresholdResult.passed) {
console.error(chalk.red('\n❌ Coverage thresholds not met'));
for (const failure of thresholdResult.failures) {
console.error(
chalk.red(
` ${failure.metric}: ${failure.actual.toFixed(1)}% < ${failure.expected}%`
)
);
}
process.exit(1);
}
}
console.log(chalk.green('\n✅ Coverage analysis complete!\n'));
} catch (error) {
console.error(chalk.red('\n❌ Error running coverage:'), error);
process.exit(1);
}
});
// Add init command to create default config
program
.command('init')
.description('Create a default .coveragerc.json configuration file')
.action(() => {
const configPath = resolve(process.cwd(), '.coveragerc.json');
if (existsSync(configPath)) {
console.error(chalk.red('Configuration file already exists'));
process.exit(1);
}
const defaultConfig = {
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/coverage/**',
'**/*.test.ts',
'**/*.test.js',
'**/*.spec.ts',
'**/*.spec.js',
'**/test/**',
'**/tests/**',
'**/__tests__/**',
'**/__mocks__/**',
],
reporters: ['terminal', 'html'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
outputDir: 'coverage',
};
require('fs').writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
console.log(chalk.green(`✅ Created ${configPath}`));
});
program.parse();