#!/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 ', 'Run coverage for specific packages') .option('-e, --exclude ', 'Glob patterns to exclude from coverage') .option('-i, --include ', 'Glob patterns to include in coverage') .option('-r, --reporters ', 'Coverage reporters (terminal, html, json, markdown, text)') .option('-t, --threshold ', 'Set coverage threshold for all metrics', parseFloat) .option('--threshold-lines ', 'Set line coverage threshold', parseFloat) .option('--threshold-functions ', 'Set function coverage threshold', parseFloat) .option('--threshold-branches ', 'Set branch coverage threshold', parseFloat) .option('--threshold-statements ', 'Set statement coverage threshold', parseFloat) .option('-o, --output-dir ', 'Output directory for reports') .option('-c, --config ', '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();