jest-test-lineage-reporter 2.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,222 @@
1
+ import {
2
+ simpleAdd,
3
+ complexLogic,
4
+ boundaryCheck,
5
+ logicalOperations,
6
+ mathOperations,
7
+ stringOperations,
8
+ incrementOperations,
9
+ subtleBugFunction,
10
+ reallyWeakFunction,
11
+ guaranteedSurvivedMutations
12
+ } from '../weak-test-example';
13
+
14
+ describe('Weak Test Examples - Some mutations will survive', () => {
15
+
16
+ // GOOD TEST - should kill mutations
17
+ describe('simpleAdd', () => {
18
+ it('should add two positive numbers', () => {
19
+ expect(simpleAdd(2, 3)).toBe(5);
20
+ });
21
+
22
+ it('should add negative numbers', () => {
23
+ expect(simpleAdd(-2, -3)).toBe(-5);
24
+ });
25
+ });
26
+
27
+ // WEAK TEST - missing edge cases, mutations may survive
28
+ describe('complexLogic', () => {
29
+ it('should handle basic case', () => {
30
+ expect(complexLogic(5, 3)).toBe(8); // Only tests the x + y path
31
+ });
32
+
33
+ it('should handle large numbers but with weak assertion', () => {
34
+ const result = complexLogic(15, 5);
35
+ expect(typeof result).toBe('number'); // Very weak! Doesn't check actual value
36
+ // This covers x > 10 path but doesn't verify x * 2 logic
37
+ // Mutations like * to / will survive!
38
+ });
39
+
40
+ it('should handle negative numbers but with weak assertion', () => {
41
+ const result = complexLogic(-5, 10);
42
+ expect(result).toBeGreaterThanOrEqual(0); // Weak! Doesn't check exact value
43
+ // This covers x < 0 path but doesn't verify return 0 logic
44
+ // Mutations like return 0 to return 1 will survive!
45
+ });
46
+ });
47
+
48
+ // WEAK TEST - doesn't test boundary conditions properly
49
+ describe('boundaryCheck', () => {
50
+ it('should return medium for normal values', () => {
51
+ expect(boundaryCheck(50)).toBe("medium");
52
+ });
53
+
54
+ it('should handle high values but with weak assertion', () => {
55
+ const result = boundaryCheck(150);
56
+ expect(typeof result).toBe('string'); // Weak! Doesn't check actual value
57
+ // This covers >= 100 path but doesn't verify "high" result
58
+ // Mutations like >= to > will survive!
59
+ });
60
+
61
+ it('should handle low values but with weak assertion', () => {
62
+ const result = boundaryCheck(-10);
63
+ expect(result.length).toBeGreaterThan(0); // Weak! Doesn't check actual value
64
+ // This covers <= 0 path but doesn't verify "low" result
65
+ // Mutations like <= to < will survive!
66
+ });
67
+ });
68
+
69
+ // WEAK TEST - doesn't test all logical combinations
70
+ describe('logicalOperations', () => {
71
+ it('should handle true and true', () => {
72
+ expect(logicalOperations(true, true)).toBe(true);
73
+ });
74
+
75
+ it('should handle other combinations but with weak assertions', () => {
76
+ const result1 = logicalOperations(true, false);
77
+ const result2 = logicalOperations(false, true);
78
+ const result3 = logicalOperations(false, false);
79
+
80
+ // Very weak assertions - just check they return booleans!
81
+ expect(typeof result1).toBe('boolean');
82
+ expect(typeof result2).toBe('boolean');
83
+ expect(typeof result3).toBe('boolean');
84
+
85
+ // This covers all logical paths but doesn't verify correct logic
86
+ // Mutations like && to ||, || to &&, !a to a will survive!
87
+ });
88
+ });
89
+
90
+ // WEAK TEST - doesn't verify the actual math
91
+ describe('mathOperations', () => {
92
+ it('should return a number', () => {
93
+ const result = mathOperations(10);
94
+ expect(typeof result).toBe('number'); // Very weak assertion!
95
+ });
96
+
97
+ it('should not throw errors', () => {
98
+ // This test covers the lines but doesn't validate the math at all!
99
+ expect(() => {
100
+ mathOperations(5);
101
+ mathOperations(0);
102
+ mathOperations(-3);
103
+ }).not.toThrow();
104
+
105
+ // This covers all the math operation lines but mutations will survive
106
+ // because we're not checking the actual results!
107
+ });
108
+ });
109
+
110
+ // WEAK TEST - incomplete string testing
111
+ describe('stringOperations', () => {
112
+ it('should handle non-empty strings', () => {
113
+ const result = stringOperations("hello");
114
+ expect(typeof result).toBe('string'); // Weak assertion
115
+ });
116
+
117
+ // Missing tests for empty string, boundary lengths
118
+ // String comparison mutations will survive
119
+ });
120
+
121
+ // WEAK TEST - doesn't verify increment behavior
122
+ describe('incrementOperations', () => {
123
+ it('should return a number', () => {
124
+ const result = incrementOperations(5);
125
+ expect(result).toBeGreaterThan(0); // Very weak assertion
126
+ });
127
+
128
+ it('should handle edge cases but with wrong expectations', () => {
129
+ // This test covers the lines but has completely wrong logic!
130
+ const result1 = incrementOperations(10);
131
+ const result2 = incrementOperations(15);
132
+
133
+ // These assertions are so generic that mutations will survive
134
+ expect(result1).toBeDefined();
135
+ expect(result2).toBeDefined();
136
+ expect(typeof result1).toBe('number');
137
+ expect(typeof result2).toBe('number');
138
+
139
+ // This covers the increment/decrement logic but doesn't validate it!
140
+ });
141
+ });
142
+
143
+ // TERRIBLE TEST - covers lines but doesn't validate behavior at all
144
+ describe('subtleBugFunction', () => {
145
+ it('should execute without errors', () => {
146
+ // This test covers all branches but validates NOTHING!
147
+ let result1, result2, result3;
148
+
149
+ expect(() => {
150
+ result1 = subtleBugFunction(0, 5); // Covers x === 0 branch
151
+ result2 = subtleBugFunction(3, 2); // Covers x > 0 branch
152
+ result3 = subtleBugFunction(-1, 4); // Covers x < 0 branch
153
+ }).not.toThrow();
154
+
155
+ // These assertions are completely useless!
156
+ expect(result1).toBeDefined();
157
+ expect(result2).toBeDefined();
158
+ expect(result3).toBeDefined();
159
+
160
+ // ALL mutations will survive because we don't check actual values!
161
+ // return 1 -> return 0: SURVIVES (we don't check result1 === 1)
162
+ // x + y -> x - y: SURVIVES (we don't check result2 === 5)
163
+ // x - y -> x + y: SURVIVES (we don't check result3 === -5)
164
+ });
165
+ });
166
+
167
+ // ABSOLUTELY TERRIBLE TEST - will definitely have survived mutations
168
+ describe('reallyWeakFunction', () => {
169
+ it('should just run without throwing errors', () => {
170
+ // This test only checks that the function doesn't crash!
171
+ // It covers all lines but validates NOTHING about the logic
172
+
173
+ let result1, result2, result3;
174
+
175
+ // Execute the function to cover the lines
176
+ expect(() => {
177
+ result1 = reallyWeakFunction(5, 10); // Covers a * b, result > 100 (false), return result - 5
178
+ result2 = reallyWeakFunction(15, 8); // Covers a * b, result > 100 (true), return result + 10
179
+ result3 = reallyWeakFunction(2, 3); // Covers a * b, result > 100 (false), return result - 5
180
+ }).not.toThrow();
181
+
182
+ // These assertions are completely useless for catching mutations!
183
+ expect(result1).toBeDefined(); // Doesn't check if result1 === 45
184
+ expect(result2).toBeDefined(); // Doesn't check if result2 === 130
185
+ expect(result3).toBeDefined(); // Doesn't check if result3 === 1
186
+
187
+ // MUTATIONS THAT WILL SURVIVE:
188
+ // a * b -> a + b: SURVIVES (we don't check actual calculation)
189
+ // a * b -> a - b: SURVIVES (we don't check actual calculation)
190
+ // a * b -> a / b: SURVIVES (we don't check actual calculation)
191
+ // result > 100 -> result < 100: SURVIVES (we don't check branching logic)
192
+ // result > 100 -> result >= 100: SURVIVES (we don't check branching logic)
193
+ // result + 10 -> result - 10: SURVIVES (we don't check return values)
194
+ // result - 5 -> result + 5: SURVIVES (we don't check return values)
195
+ });
196
+ });
197
+
198
+ // ABSOLUTELY USELESS TEST - will definitely have survived mutations
199
+ describe('guaranteedSurvivedMutations', () => {
200
+ it('should exist as a function', () => {
201
+ // This test is so useless it only checks the function exists!
202
+ expect(typeof guaranteedSurvivedMutations).toBe('function');
203
+
204
+ // We call the function but don't check ANY return values
205
+ guaranteedSurvivedMutations(5); // Covers x === 5 branch, return 42
206
+ guaranteedSurvivedMutations(15); // Covers x > 10 branch, return x * 2
207
+ guaranteedSurvivedMutations(3); // Covers default branch, return x + 1
208
+
209
+ // NO ASSERTIONS ON RETURN VALUES!
210
+ // This means ALL mutations will survive:
211
+ // x === 5 -> x !== 5: SURVIVES (we don't check the condition)
212
+ // x === 5 -> x > 5: SURVIVES (we don't check the condition)
213
+ // return 42 -> return 0: SURVIVES (we don't check return value)
214
+ // return 42 -> return 1: SURVIVES (we don't check return value)
215
+ // x > 10 -> x < 10: SURVIVES (we don't check the condition)
216
+ // x * 2 -> x + 2: SURVIVES (we don't check return value)
217
+ // x * 2 -> x - 2: SURVIVES (we don't check return value)
218
+ // x + 1 -> x - 1: SURVIVES (we don't check return value)
219
+ // x + 1 -> x * 1: SURVIVES (we don't check return value)
220
+ });
221
+ });
222
+ });
@@ -0,0 +1,12 @@
1
+ export function add(a: number, b: number): number {
2
+ return a - b;
3
+ }
4
+ export function subtract(a: number, b: number): number {
5
+ if (!(!!(a < b))) {
6
+ return b - a;
7
+ }
8
+ return a - b;
9
+ }
10
+ export function multiply(a: number, b: number): number {
11
+ return a * b;
12
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Analyze Command
3
+ * Full workflow: test + mutation + report
4
+ */
5
+
6
+ const testCommand = require('./test');
7
+ const mutateCommand = require('./mutate');
8
+ const reportCommand = require('./report');
9
+ const { lineageDataExists } = require('../utils/data-loader');
10
+ const { section, success, error, info } = require('../utils/output-formatter');
11
+ const chalk = require('chalk');
12
+
13
+ async function analyzeCommand(options) {
14
+ try {
15
+ section('🚀 Full Analysis Workflow');
16
+
17
+ // Step 1: Run tests (unless skip-tests is specified)
18
+ if (!options.skipTests) {
19
+ info('Step 1: Running tests with lineage tracking...\n');
20
+
21
+ try {
22
+ await testCommand([], {
23
+ lineage: options.lineage !== false,
24
+ performance: options.performance !== false,
25
+ quality: options.quality !== false,
26
+ config: options.config,
27
+ quiet: false
28
+ });
29
+ } catch (err) {
30
+ // testCommand handles its own exit, this catch is for safety
31
+ error('Tests failed. Analysis stopped.');
32
+ process.exit(1);
33
+ }
34
+ } else {
35
+ // Verify lineage data exists
36
+ if (!lineageDataExists()) {
37
+ error('Lineage data not found. Remove --skip-tests or run jest-lineage test first.');
38
+ process.exit(1);
39
+ }
40
+ info('Step 1: Skipped (using existing lineage data)\n');
41
+ }
42
+
43
+ // Step 2: Run mutation testing (unless skip-mutation is specified)
44
+ if (!options.skipMutation) {
45
+ console.log(); // Add spacing
46
+ info('Step 2: Running mutation testing...\n');
47
+
48
+ try {
49
+ await mutateCommand({
50
+ data: '.jest-lineage-data.json',
51
+ threshold: options.threshold,
52
+ timeout: options.timeout || '5000',
53
+ verbose: false
54
+ });
55
+ } catch (err) {
56
+ // Don't fail the entire workflow if mutation testing fails
57
+ error('Mutation testing failed, but continuing with report generation...');
58
+ }
59
+ } else {
60
+ info('Step 2: Skipped mutation testing\n');
61
+ }
62
+
63
+ // Step 3: Generate report
64
+ console.log(); // Add spacing
65
+ info('Step 3: Generating HTML report...\n');
66
+
67
+ await reportCommand({
68
+ data: '.jest-lineage-data.json',
69
+ output: options.output,
70
+ open: options.open
71
+ });
72
+
73
+ // Success summary
74
+ console.log();
75
+ section('✨ Analysis Complete');
76
+ success('Full analysis workflow completed successfully!');
77
+ console.log(chalk.gray(`\nGenerated files:`));
78
+ console.log(chalk.gray(` • .jest-lineage-data.json (lineage data)`));
79
+ console.log(chalk.gray(` • ${options.output} (HTML report)`));
80
+
81
+ process.exit(0);
82
+ } catch (err) {
83
+ error(`Analysis workflow failed: ${err.message}`);
84
+ if (process.env.JEST_LINEAGE_DEBUG === 'true') {
85
+ console.error(err.stack);
86
+ }
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ module.exports = analyzeCommand;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Init Command
3
+ * Initialize jest-test-lineage-reporter in a project
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { success, error, info, warning } = require('../utils/output-formatter');
9
+
10
+ async function initCommand(options) {
11
+ try {
12
+ const cwd = process.cwd();
13
+ const jestConfigPath = path.join(cwd, 'jest.config.js');
14
+ const babelConfigPath = path.join(cwd, 'babel.config.js');
15
+ const packageJsonPath = path.join(cwd, 'package.json');
16
+
17
+ console.log('\n🚀 Initializing jest-test-lineage-reporter...\n');
18
+
19
+ // Check if package.json exists
20
+ if (!fs.existsSync(packageJsonPath)) {
21
+ error('No package.json found. Please run "npm init" first.');
22
+ process.exit(1);
23
+ }
24
+
25
+ // Read package.json to check dependencies
26
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
27
+ const allDeps = {
28
+ ...(packageJson.dependencies || {}),
29
+ ...(packageJson.devDependencies || {}),
30
+ };
31
+
32
+ // Check for required dependencies
33
+ const requiredDeps = ['jest', 'babel-jest', '@babel/core', '@babel/preset-env'];
34
+ const missingDeps = requiredDeps.filter(dep => !allDeps[dep]);
35
+
36
+ if (missingDeps.length > 0) {
37
+ warning('Missing required dependencies:');
38
+ missingDeps.forEach(dep => console.log(` - ${dep}`));
39
+ console.log('\n💡 Install them with:');
40
+ console.log(` npm install --save-dev ${missingDeps.join(' ')}\n`);
41
+
42
+ if (!options.force) {
43
+ error('Please install missing dependencies first, or use --force to continue anyway.');
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ // Create or update Jest config
49
+ let jestConfigCreated = false;
50
+ if (fs.existsSync(jestConfigPath)) {
51
+ if (!options.force) {
52
+ warning(`jest.config.js already exists. Use --force to overwrite.`);
53
+ info('Please manually add the following to your jest.config.js:');
54
+ console.log(`
55
+ setupFilesAfterEnv: ['jest-test-lineage-reporter/src/testSetup.js'],
56
+
57
+ reporters: [
58
+ 'default',
59
+ ['jest-test-lineage-reporter', {
60
+ outputFile: '.jest-lineage-data.json',
61
+ enableMutationTesting: false,
62
+ }]
63
+ ],
64
+
65
+ collectCoverage: true,
66
+ collectCoverageFrom: ['src/**/*.{js,ts}', '!src/**/*.test.{js,ts}'],
67
+
68
+ transform: {
69
+ '^.+\\\\.(js|jsx|ts|tsx)$': 'babel-jest',
70
+ },
71
+ `);
72
+ } else {
73
+ createJestConfig(jestConfigPath, options);
74
+ jestConfigCreated = true;
75
+ }
76
+ } else {
77
+ createJestConfig(jestConfigPath, options);
78
+ jestConfigCreated = true;
79
+ }
80
+
81
+ // Create or update Babel config
82
+ let babelConfigCreated = false;
83
+ if (fs.existsSync(babelConfigPath)) {
84
+ if (!options.force) {
85
+ warning(`babel.config.js already exists. Use --force to overwrite.`);
86
+ info('Please manually add the lineage tracker plugin to your babel.config.js:');
87
+ console.log(`
88
+ plugins: [
89
+ 'jest-test-lineage-reporter/src/babel-plugin-lineage-tracker.js',
90
+ ],
91
+ `);
92
+ } else {
93
+ createBabelConfig(babelConfigPath, options);
94
+ babelConfigCreated = true;
95
+ }
96
+ } else {
97
+ createBabelConfig(babelConfigPath, options);
98
+ babelConfigCreated = true;
99
+ }
100
+
101
+ // Summary
102
+ console.log('\n' + '═'.repeat(50));
103
+ if (jestConfigCreated && babelConfigCreated) {
104
+ success('Configuration complete! ✨\n');
105
+ console.log('✅ Created jest.config.js');
106
+ console.log('✅ Created babel.config.js\n');
107
+ } else if (!jestConfigCreated && !babelConfigCreated) {
108
+ info('Configuration files already exist.');
109
+ info('Use --force to overwrite, or manually update the files.\n');
110
+ } else {
111
+ info('Partial configuration completed.');
112
+ if (jestConfigCreated) console.log('✅ Created jest.config.js');
113
+ if (babelConfigCreated) console.log('✅ Created babel.config.js');
114
+ console.log('');
115
+ }
116
+
117
+ // Show next steps
118
+ console.log('📋 Next steps:\n');
119
+ if (missingDeps.length > 0) {
120
+ console.log('1. Install missing dependencies:');
121
+ console.log(` npm install --save-dev ${missingDeps.join(' ')}\n`);
122
+ }
123
+ console.log(`${missingDeps.length > 0 ? '2' : '1'}. Run your tests with lineage tracking:`);
124
+ console.log(' npx jest-lineage test\n');
125
+ console.log(`${missingDeps.length > 0 ? '3' : '2'}. Query coverage data:`);
126
+ console.log(' npx jest-lineage query src/yourfile.js\n');
127
+ console.log(`${missingDeps.length > 0 ? '4' : '3'}. Generate HTML report:`);
128
+ console.log(' npx jest-lineage report --open\n');
129
+ console.log('═'.repeat(50) + '\n');
130
+
131
+ } catch (err) {
132
+ error(`Failed to initialize: ${err.message}`);
133
+ if (options.verbose) {
134
+ console.error(err.stack);
135
+ }
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ function createJestConfig(filePath, options) {
141
+ const isTypeScript = options.typescript || hasTypeScriptFiles();
142
+
143
+ const config = `module.exports = {
144
+ testEnvironment: 'node',
145
+
146
+ // Required: Setup file for lineage tracking
147
+ setupFilesAfterEnv: ['jest-test-lineage-reporter/src/testSetup.js'],
148
+
149
+ // Add the lineage reporter
150
+ reporters: [
151
+ 'default',
152
+ [
153
+ 'jest-test-lineage-reporter',
154
+ {
155
+ outputFile: '.jest-lineage-data.json',
156
+ enableMutationTesting: false,
157
+ }
158
+ ]
159
+ ],
160
+
161
+ // Enable coverage
162
+ collectCoverage: true,
163
+ collectCoverageFrom: [
164
+ 'src/**/*.{js${isTypeScript ? ',ts' : ''}}',
165
+ '!src/**/*.test.{js${isTypeScript ? ',ts' : ''}}',
166
+ '!src/**/*.d.ts',
167
+ ],
168
+
169
+ // Use babel-jest for transformation
170
+ transform: {
171
+ '^.+\\\\.(js|jsx${isTypeScript ? '|ts|tsx' : ''})$': 'babel-jest',
172
+ },
173
+
174
+ // File extensions
175
+ moduleFileExtensions: ['js', 'jsx'${isTypeScript ? ", 'ts', 'tsx'" : ''}, 'json'],
176
+ };
177
+ `;
178
+
179
+ fs.writeFileSync(filePath, config, 'utf8');
180
+ }
181
+
182
+ function createBabelConfig(filePath, options) {
183
+ const isTypeScript = options.typescript || hasTypeScriptFiles();
184
+
185
+ const config = `module.exports = {
186
+ presets: [
187
+ ['@babel/preset-env', { targets: { node: 'current' } }],${isTypeScript ? `
188
+ '@babel/preset-typescript',` : ''}
189
+ ],
190
+ plugins: [
191
+ // Required: Lineage tracker plugin for instrumentation
192
+ 'jest-test-lineage-reporter/src/babel-plugin-lineage-tracker.js',
193
+ ],
194
+ };
195
+ `;
196
+
197
+ fs.writeFileSync(filePath, config, 'utf8');
198
+ }
199
+
200
+ function hasTypeScriptFiles() {
201
+ const cwd = process.cwd();
202
+ const srcDir = path.join(cwd, 'src');
203
+
204
+ if (!fs.existsSync(srcDir)) return false;
205
+
206
+ try {
207
+ const files = fs.readdirSync(srcDir);
208
+ return files.some(file => file.endsWith('.ts') || file.endsWith('.tsx'));
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ module.exports = initCommand;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Mutate Command
3
+ * Run mutation testing on existing lineage data
4
+ */
5
+
6
+ const MutationTester = require('../../MutationTester');
7
+ const { loadLineageData, processLineageDataForMutation } = require('../utils/data-loader');
8
+ const { loadFullConfig } = require('../utils/config-loader');
9
+ const { spinner, printMutationSummary, error, success, info, printLineageDataSummary } = require('../utils/output-formatter');
10
+ const chalk = require('chalk');
11
+
12
+ async function mutateCommand(options) {
13
+ let mutationTester = null;
14
+
15
+ try {
16
+ // Load configuration
17
+ const config = loadFullConfig(options);
18
+
19
+ // Load lineage data
20
+ info(`Loading lineage data from: ${chalk.yellow(options.data)}`);
21
+ const rawData = loadLineageData(options.data);
22
+
23
+ if (!options.verbose) {
24
+ printLineageDataSummary(rawData);
25
+ }
26
+
27
+ // Process data for mutation testing
28
+ const lineageData = processLineageDataForMutation(rawData);
29
+ const fileCount = Object.keys(lineageData).length;
30
+ const lineCount = Object.values(lineageData).reduce(
31
+ (sum, lines) => sum + Object.keys(lines).length,
32
+ 0
33
+ );
34
+
35
+ if (fileCount === 0 || lineCount === 0) {
36
+ error('No coverage data found in lineage file. Run tests first.');
37
+ process.exit(1);
38
+ }
39
+
40
+ info(`Processing ${chalk.cyan(fileCount)} files with ${chalk.cyan(lineCount)} covered lines\n`);
41
+
42
+ // Create mutation tester
43
+ mutationTester = new MutationTester(config);
44
+ mutationTester.setLineageData(lineageData);
45
+
46
+ // Run mutation testing
47
+ const spin = spinner('Running mutation testing...');
48
+ if (!options.verbose) {
49
+ spin.start();
50
+ }
51
+
52
+ const results = await mutationTester.runMutationTesting();
53
+
54
+ if (!options.verbose) {
55
+ spin.succeed('Mutation testing completed!');
56
+ }
57
+
58
+ // Print results
59
+ printMutationSummary(results);
60
+
61
+ // Check threshold
62
+ const threshold = parseInt(options.threshold) || 80;
63
+ if (results.mutationScore < threshold) {
64
+ console.log(chalk.yellow(`\n⚠️ Mutation score ${results.mutationScore.toFixed(1)}% is below threshold ${threshold}%`));
65
+ process.exit(1);
66
+ } else {
67
+ success(`Mutation score meets threshold (${threshold}%)`);
68
+ process.exit(0);
69
+ }
70
+ } catch (err) {
71
+ error(`Mutation testing failed: ${err.message}`);
72
+ if (options.verbose) {
73
+ console.error(err.stack);
74
+ }
75
+
76
+ // Cleanup on error
77
+ if (mutationTester) {
78
+ try {
79
+ await mutationTester.cleanup();
80
+ } catch (cleanupErr) {
81
+ // Ignore cleanup errors
82
+ }
83
+ }
84
+
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ module.exports = mutateCommand;