qa360 2.2.7 → 2.2.9

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.

Potentially problematic release.


This version of qa360 might be problematic. Click here for more details.

@@ -1,459 +0,0 @@
1
- /**
2
- * Coverage Collector
3
- *
4
- * Collects coverage data from various test sources.
5
- * Supports Jest, Vitest, Cypress, Playwright, and LCOV formats.
6
- */
7
- import { createDefaultCoverageConfig } from './config.js';
8
- /**
9
- * Coverage Collector class
10
- */
11
- export class CoverageCollector {
12
- config;
13
- constructor(config) {
14
- this.config = { ...createDefaultCoverageConfig(), ...config };
15
- }
16
- /**
17
- * Collect coverage from a file path (auto-detect format)
18
- */
19
- async collectFromFile(filePath) {
20
- const format = this.detectFormat(filePath);
21
- switch (format) {
22
- case 'lcov':
23
- return this.collectFromLcov(filePath);
24
- case 'json':
25
- return this.collectFromJson(filePath);
26
- case 'istanbul':
27
- return this.collectFromIstanbul(filePath);
28
- default:
29
- return null;
30
- }
31
- }
32
- /**
33
- * Collect coverage from LCOV format
34
- */
35
- async collectFromLcov(filePath) {
36
- try {
37
- const fs = await import('node:fs/promises');
38
- const content = await fs.readFile(filePath, 'utf-8');
39
- const records = this.parseLcov(content);
40
- if (records.length === 0) {
41
- return null;
42
- }
43
- const files = {};
44
- for (const record of records) {
45
- files[record.file] = this.convertLcovToFileCoverage(record);
46
- }
47
- return {
48
- source: 'lcov',
49
- gate: 'unknown',
50
- files,
51
- metrics: this.calculateMetrics(files),
52
- format: 'lcov',
53
- timestamp: Date.now()
54
- };
55
- }
56
- catch (error) {
57
- return null;
58
- }
59
- }
60
- /**
61
- * Collect coverage from JSON format (Vitest/Istanbul)
62
- */
63
- async collectFromJson(filePath) {
64
- try {
65
- const fs = await import('node:fs/promises');
66
- const content = await fs.readFile(filePath, 'utf-8');
67
- const data = JSON.parse(content);
68
- const files = {};
69
- // Handle Vitest format
70
- if (data.result && typeof data.result === 'object') {
71
- for (const [filePath, coverageData] of Object.entries(data.result)) {
72
- if (typeof coverageData === 'object' && coverageData !== null) {
73
- files[filePath] = this.convertVitestToFileCoverage(filePath, coverageData);
74
- }
75
- }
76
- return {
77
- source: 'vitest',
78
- gate: 'unknown',
79
- files,
80
- metrics: this.calculateMetrics(files),
81
- format: 'json',
82
- timestamp: Date.now()
83
- };
84
- }
85
- // Handle Istanbul format
86
- if (typeof data === 'object' && !data.result) {
87
- for (const [filePath, coverageData] of Object.entries(data)) {
88
- if (typeof coverageData === 'object' && coverageData !== null) {
89
- files[filePath] = this.convertIstanbulToFileCoverage(filePath, coverageData);
90
- }
91
- }
92
- return {
93
- source: 'istanbul',
94
- gate: 'unknown',
95
- files,
96
- metrics: this.calculateMetrics(files),
97
- format: 'istanbul',
98
- timestamp: Date.now()
99
- };
100
- }
101
- return null;
102
- }
103
- catch (error) {
104
- return null;
105
- }
106
- }
107
- /**
108
- * Collect coverage from Istanbul format
109
- */
110
- async collectFromIstanbul(filePath) {
111
- return this.collectFromJson(filePath);
112
- }
113
- /**
114
- * Parse LCOV format content
115
- */
116
- parseLcov(content) {
117
- const records = [];
118
- const lines = content.split('\n');
119
- let currentRecord = null;
120
- for (const line of lines) {
121
- if (line.startsWith('SF:')) {
122
- // Start of new file record
123
- currentRecord = {
124
- file: line.substring(3),
125
- lines: {},
126
- branches: {},
127
- functions: {}
128
- };
129
- records.push(currentRecord);
130
- }
131
- else if (currentRecord) {
132
- if (line.startsWith('DA:')) {
133
- // Line data: DA:<line>,<count>
134
- const [, lineNum, count] = line.match(/^DA:(\d+),(\d+)$/) || [];
135
- if (lineNum) {
136
- currentRecord.lines[parseInt(lineNum, 10)] = parseInt(count, 10);
137
- }
138
- }
139
- else if (line.startsWith('BRDA:')) {
140
- // Branch data: BRDA:<line>,<block>,<branch>,<count>
141
- const parts = line.substring(5).split(',');
142
- if (parts.length >= 4) {
143
- const lineNum = parts[0];
144
- const branchId = `${lineNum}-${parts[1]}-${parts[2]}`;
145
- const count = parts[3] === '-' ? 0 : parseInt(parts[3], 10);
146
- if (!currentRecord.branches[lineNum]) {
147
- currentRecord.branches[lineNum] = [];
148
- }
149
- currentRecord.branches[lineNum].push(count);
150
- }
151
- }
152
- else if (line.startsWith('FN:')) {
153
- // Function data: FN:<line>,<function name>
154
- const parts = line.substring(3).split(',');
155
- if (parts.length >= 2) {
156
- currentRecord.functions[parts[1]] = 0;
157
- }
158
- }
159
- else if (line.startsWith('FNDA:')) {
160
- // Function execution data: FNDA:<count>,<function name>
161
- const parts = line.substring(5).split(',');
162
- if (parts.length >= 2) {
163
- currentRecord.functions[parts[1]] = parseInt(parts[0], 10);
164
- }
165
- }
166
- }
167
- }
168
- return records;
169
- }
170
- /**
171
- * Convert LCOV record to FileCoverage
172
- */
173
- convertLcovToFileCoverage(record) {
174
- const lineNumbers = Object.keys(record.lines).map(Number);
175
- const totalLines = lineNumbers.length > 0 ? Math.max(...lineNumbers) : 0;
176
- const coveredLines = Object.values(record.lines).filter(c => c > 0).length;
177
- // Count branches
178
- let totalBranches = 0;
179
- let coveredBranches = 0;
180
- const branchesByLine = {};
181
- for (const [lineNum, branchCounts] of Object.entries(record.branches)) {
182
- const line = parseInt(lineNum, 10);
183
- const branches = branchCounts;
184
- totalBranches += branches.length;
185
- coveredBranches += branches.filter(c => c > 0).length;
186
- branchesByLine[line] = branches.map((count, idx) => ({
187
- index: idx,
188
- count,
189
- location: { start: { line, column: 0 }, end: { line, column: 0 } }
190
- }));
191
- }
192
- // Count functions
193
- const totalFunctions = Object.keys(record.functions).length;
194
- const coveredFunctions = Object.values(record.functions).filter(f => f > 0).length;
195
- // Uncovered lines
196
- const uncoveredLines = Object.entries(record.lines)
197
- .filter(([, count]) => count === 0)
198
- .map(([line]) => parseInt(line, 10));
199
- const partiallyCoveredLines = Object.entries(record.lines)
200
- .filter(([, count]) => count > 0 && count < 10) // Low execution count
201
- .map(([line]) => parseInt(line, 10));
202
- return {
203
- path: record.file,
204
- totalLines,
205
- coveredLines,
206
- lineCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
207
- totalBranches,
208
- coveredBranches,
209
- branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
210
- totalFunctions,
211
- coveredFunctions,
212
- functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
213
- totalStatements: totalLines, // Approximation
214
- coveredStatements: coveredLines,
215
- statementCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
216
- uncoveredLines,
217
- partiallyCoveredLines,
218
- branchesByLine
219
- };
220
- }
221
- /**
222
- * Convert Vitest coverage data to FileCoverage
223
- */
224
- convertVitestToFileCoverage(filePath, data) {
225
- const s = data.s || { total: 0, covered: 0, pct: 0 };
226
- const b = data.b || {};
227
- const f = data.f || {};
228
- const stmt = data.statementMap || {};
229
- const branchMap = data.branchMap || {};
230
- // Calculate line coverage from statement map
231
- const lines = new Set();
232
- const coveredLines = new Set();
233
- for (const [idx, stmtInfo] of Object.entries(stmt)) {
234
- if (stmtInfo && typeof stmtInfo === 'object') {
235
- const start = stmtInfo.start;
236
- const end = stmtInfo.end;
237
- if (start && end) {
238
- for (let i = start.line; i <= end.line; i++) {
239
- lines.add(i);
240
- }
241
- }
242
- }
243
- const count = s[parseInt(idx)];
244
- if (count > 0 && stmtInfo) {
245
- const start = stmtInfo.start;
246
- const end = stmtInfo.end;
247
- if (start && end) {
248
- for (let i = start.line; i <= end.line; i++) {
249
- coveredLines.add(i);
250
- }
251
- }
252
- }
253
- }
254
- // Calculate branch coverage
255
- let totalBranches = 0;
256
- let coveredBranches = 0;
257
- const branchesByLine = {};
258
- for (const [idx, branches] of Object.entries(b)) {
259
- if (Array.isArray(branches)) {
260
- const branchInfo = branchMap[parseInt(idx)];
261
- const line = branchInfo?.line || 0;
262
- totalBranches += branches.length;
263
- coveredBranches += branches.filter((c) => c > 0).length;
264
- branchesByLine[line] = branches.map((count, idx) => ({
265
- index: idx,
266
- count,
267
- location: { start: { line, column: 0 }, end: { line, column: 0 } }
268
- }));
269
- }
270
- }
271
- // Calculate function coverage
272
- const totalFunctions = Object.keys(f).length;
273
- const coveredFunctions = Object.values(f).filter((c) => typeof c === 'number' && c > 0).length;
274
- const totalLines = lines.size;
275
- const coveredLinesCount = coveredLines.size;
276
- const uncoveredLines = Array.from(lines).filter(l => !coveredLines.has(l));
277
- return {
278
- path: filePath,
279
- totalLines,
280
- coveredLines: coveredLinesCount,
281
- lineCoverage: s.pct || 0,
282
- totalBranches,
283
- coveredBranches,
284
- branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
285
- totalFunctions,
286
- coveredFunctions,
287
- functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
288
- totalStatements: s.total || 0,
289
- coveredStatements: s.covered || 0,
290
- statementCoverage: s.pct || 0,
291
- uncoveredLines,
292
- partiallyCoveredLines: [],
293
- branchesByLine
294
- };
295
- }
296
- /**
297
- * Convert Istanbul coverage data to FileCoverage
298
- */
299
- convertIstanbulToFileCoverage(filePath, data) {
300
- return this.convertVitestToFileCoverage(filePath, data);
301
- }
302
- /**
303
- * Calculate aggregate metrics from file coverage data
304
- */
305
- calculateMetrics(files) {
306
- const fileArray = Object.values(files);
307
- const threshold = this.config.thresholds.line || 80;
308
- if (fileArray.length === 0) {
309
- return {
310
- lineCoverage: 0,
311
- branchCoverage: 0,
312
- functionCoverage: 0,
313
- statementCoverage: 0,
314
- totalFiles: 0,
315
- filesWithCoverage: 0,
316
- filesMeetingThreshold: 0,
317
- totalLines: 0,
318
- coveredLines: 0,
319
- timestamp: Date.now()
320
- };
321
- }
322
- let totalLines = 0;
323
- let coveredLines = 0;
324
- let totalBranches = 0;
325
- let coveredBranches = 0;
326
- let totalFunctions = 0;
327
- let coveredFunctions = 0;
328
- let totalStatements = 0;
329
- let coveredStatements = 0;
330
- let filesMeetingThreshold = 0;
331
- for (const file of fileArray) {
332
- totalLines += file.totalLines;
333
- coveredLines += file.coveredLines;
334
- totalBranches += file.totalBranches;
335
- coveredBranches += file.coveredBranches;
336
- totalFunctions += file.totalFunctions;
337
- coveredFunctions += file.coveredFunctions;
338
- totalStatements += file.totalStatements;
339
- coveredStatements += file.coveredStatements;
340
- if (file.lineCoverage >= threshold) {
341
- filesMeetingThreshold++;
342
- }
343
- }
344
- return {
345
- lineCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
346
- branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
347
- functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
348
- statementCoverage: totalStatements > 0 ? (coveredStatements / totalStatements) * 100 : 0,
349
- totalFiles: fileArray.length,
350
- filesWithCoverage: fileArray.filter(f => f.totalLines > 0).length,
351
- filesMeetingThreshold,
352
- totalLines,
353
- coveredLines,
354
- timestamp: Date.now()
355
- };
356
- }
357
- /**
358
- * Detect coverage format from file path
359
- */
360
- detectFormat(filePath) {
361
- if (filePath.endsWith('.json')) {
362
- return 'json';
363
- }
364
- if (filePath.endsWith('.lcov') || filePath.endsWith('.info')) {
365
- return 'lcov';
366
- }
367
- if (filePath.includes('coverage') && filePath.endsWith('.json')) {
368
- return 'istanbul';
369
- }
370
- return 'unknown';
371
- }
372
- /**
373
- * Filter files based on include/exclude patterns
374
- */
375
- filterFiles(files) {
376
- const filtered = {};
377
- const isIncluded = (path) => {
378
- // Check exclude patterns first
379
- for (const pattern of this.config.exclude) {
380
- if (this.matchPattern(path, pattern)) {
381
- return false;
382
- }
383
- }
384
- // Check include patterns
385
- if (this.config.include.length === 0) {
386
- return true; // No include filters means all
387
- }
388
- for (const pattern of this.config.include) {
389
- if (this.matchPattern(path, pattern)) {
390
- return true;
391
- }
392
- }
393
- return false;
394
- };
395
- for (const [path, coverage] of Object.entries(files)) {
396
- if (isIncluded(path)) {
397
- filtered[path] = coverage;
398
- }
399
- }
400
- return filtered;
401
- }
402
- /**
403
- * Match path against glob pattern
404
- */
405
- matchPattern(path, pattern) {
406
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.').replace(/\//g, '\\/') + '$');
407
- return regex.test(path);
408
- }
409
- /**
410
- * Merge multiple coverage results
411
- */
412
- mergeCoverageResults(results) {
413
- const mergedFiles = {};
414
- for (const result of results) {
415
- for (const [path, file] of Object.entries(result.files)) {
416
- if (!mergedFiles[path]) {
417
- mergedFiles[path] = { ...file };
418
- }
419
- else {
420
- // Merge coverage data
421
- const existing = mergedFiles[path];
422
- // For lines, take the max execution count
423
- const uncoveredLines = new Set([
424
- ...existing.uncoveredLines,
425
- ...file.uncoveredLines
426
- ]);
427
- // Update totals (approximate merging)
428
- mergedFiles[path] = {
429
- ...existing,
430
- uncoveredLines: Array.from(uncoveredLines),
431
- lineCoverage: Math.max(existing.lineCoverage, file.lineCoverage),
432
- branchCoverage: Math.max(existing.branchCoverage, file.branchCoverage),
433
- functionCoverage: Math.max(existing.functionCoverage, file.functionCoverage)
434
- };
435
- }
436
- }
437
- }
438
- return {
439
- source: 'custom',
440
- gate: 'merged',
441
- files: mergedFiles,
442
- metrics: this.calculateMetrics(mergedFiles),
443
- format: 'json',
444
- timestamp: Date.now()
445
- };
446
- }
447
- /**
448
- * Update configuration
449
- */
450
- updateConfig(updates) {
451
- this.config = { ...this.config, ...updates };
452
- }
453
- }
454
- /**
455
- * Create a coverage collector with default config
456
- */
457
- export function createCoverageCollector(config) {
458
- return new CoverageCollector(config);
459
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Coverage Configuration
3
- *
4
- * Default configuration and factory functions for coverage analytics.
5
- */
6
- import type { CoverageConfig, CoverageThreshold } from './types.js';
7
- /**
8
- * Create default coverage configuration
9
- */
10
- export declare function createDefaultCoverageConfig(): CoverageConfig;
11
- /**
12
- * Create strict coverage configuration (enterprise/production)
13
- */
14
- export declare function createStrictCoverageConfig(): CoverageConfig;
15
- /**
16
- * Create relaxed coverage configuration (development/prototyping)
17
- */
18
- export declare function createRelaxedCoverageConfig(): CoverageConfig;
19
- /**
20
- * Parse coverage threshold from string format
21
- * Supported formats:
22
- * - "80" (line coverage only)
23
- * - "line:80,branch:70"
24
- * - "{line:80,branch:70,function:75}"
25
- */
26
- export declare function parseCoverageThreshold(input: string): CoverageThreshold;
27
- /**
28
- * Format coverage threshold as string
29
- */
30
- export declare function formatCoverageThreshold(threshold: CoverageThreshold): string;
31
- /**
32
- * Validate coverage configuration
33
- */
34
- export declare function validateCoverageConfig(config: CoverageConfig): {
35
- valid: boolean;
36
- errors: string[];
37
- };
@@ -1,156 +0,0 @@
1
- /**
2
- * Coverage Configuration
3
- *
4
- * Default configuration and factory functions for coverage analytics.
5
- */
6
- /**
7
- * Create default coverage configuration
8
- */
9
- export function createDefaultCoverageConfig() {
10
- return {
11
- enabled: true,
12
- thresholds: {
13
- line: 80,
14
- branch: 70,
15
- function: 75,
16
- statement: 80,
17
- scope: 'global',
18
- files: {}
19
- },
20
- failOnThreshold: false,
21
- include: [
22
- 'src/**/*.ts',
23
- 'src/**/*.js',
24
- 'lib/**/*.ts',
25
- 'lib/**/*.js'
26
- ],
27
- exclude: [
28
- '**/*.test.ts',
29
- '**/*.test.js',
30
- '**/*.spec.ts',
31
- '**/*.spec.js',
32
- '**/node_modules/**',
33
- '**/dist/**',
34
- '**/build/**',
35
- '**/*.d.ts',
36
- '**/*.config.*',
37
- '**/mock*/**',
38
- '**/__tests__/**',
39
- '**/__mocks__/**'
40
- ],
41
- reportFormats: ['json', 'lcov'],
42
- sources: ['jest', 'vitest', 'cypress', 'playwright', 'istanbul', 'lcov'],
43
- storeInVault: true,
44
- trackTrends: true
45
- };
46
- }
47
- /**
48
- * Create strict coverage configuration (enterprise/production)
49
- */
50
- export function createStrictCoverageConfig() {
51
- return {
52
- ...createDefaultCoverageConfig(),
53
- thresholds: {
54
- line: 90,
55
- branch: 85,
56
- function: 88,
57
- statement: 90,
58
- scope: 'per-file',
59
- files: {}
60
- },
61
- failOnThreshold: true
62
- };
63
- }
64
- /**
65
- * Create relaxed coverage configuration (development/prototyping)
66
- */
67
- export function createRelaxedCoverageConfig() {
68
- return {
69
- ...createDefaultCoverageConfig(),
70
- thresholds: {
71
- line: 60,
72
- branch: 50,
73
- function: 55,
74
- statement: 60,
75
- scope: 'global',
76
- files: {}
77
- },
78
- failOnThreshold: false
79
- };
80
- }
81
- /**
82
- * Parse coverage threshold from string format
83
- * Supported formats:
84
- * - "80" (line coverage only)
85
- * - "line:80,branch:70"
86
- * - "{line:80,branch:70,function:75}"
87
- */
88
- export function parseCoverageThreshold(input) {
89
- // JSON format
90
- if (input.startsWith('{')) {
91
- try {
92
- return JSON.parse(input);
93
- }
94
- catch {
95
- return createDefaultCoverageConfig().thresholds;
96
- }
97
- }
98
- // Key:value format
99
- if (input.includes(':')) {
100
- const result = {};
101
- for (const part of input.split(',')) {
102
- const [key, value] = part.split(':');
103
- const numValue = parseInt(value, 10);
104
- if (!isNaN(numValue) && ['line', 'branch', 'function', 'statement'].includes(key)) {
105
- result[key] = numValue;
106
- }
107
- }
108
- return Object.keys(result).length > 0 ? result : createDefaultCoverageConfig().thresholds;
109
- }
110
- // Simple number (line coverage)
111
- const numValue = parseInt(input, 10);
112
- if (!isNaN(numValue)) {
113
- return { line: numValue };
114
- }
115
- return createDefaultCoverageConfig().thresholds;
116
- }
117
- /**
118
- * Format coverage threshold as string
119
- */
120
- export function formatCoverageThreshold(threshold) {
121
- const parts = [];
122
- if (threshold.line !== undefined)
123
- parts.push(`line:${threshold.line}`);
124
- if (threshold.branch !== undefined)
125
- parts.push(`branch:${threshold.branch}`);
126
- if (threshold.function !== undefined)
127
- parts.push(`function:${threshold.function}`);
128
- if (threshold.statement !== undefined)
129
- parts.push(`statement:${threshold.statement}`);
130
- return parts.join(',') || 'line:80';
131
- }
132
- /**
133
- * Validate coverage configuration
134
- */
135
- export function validateCoverageConfig(config) {
136
- const errors = [];
137
- if (config.thresholds.line !== undefined && (config.thresholds.line < 0 || config.thresholds.line > 100)) {
138
- errors.push('Line coverage threshold must be between 0 and 100');
139
- }
140
- if (config.thresholds.branch !== undefined && (config.thresholds.branch < 0 || config.thresholds.branch > 100)) {
141
- errors.push('Branch coverage threshold must be between 0 and 100');
142
- }
143
- if (config.thresholds.function !== undefined && (config.thresholds.function < 0 || config.thresholds.function > 100)) {
144
- errors.push('Function coverage threshold must be between 0 and 100');
145
- }
146
- if (config.thresholds.statement !== undefined && (config.thresholds.statement < 0 || config.thresholds.statement > 100)) {
147
- errors.push('Statement coverage threshold must be between 0 and 100');
148
- }
149
- if (config.include.length === 0) {
150
- errors.push('At least one include pattern is required');
151
- }
152
- return {
153
- valid: errors.length === 0,
154
- errors
155
- };
156
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * QA360 Coverage Analytics Module
3
- *
4
- * Exports all coverage analytics functionality.
5
- */
6
- export type { CoverageType, CoverageSource, FileCoverage, BranchInfo, CoverageMetrics, CoverageThreshold, CoverageResult, CoverageTrend, CoverageGap, CoverageComparison, CoverageConfig, TestCoverageMap, CoverageReport } from './types.js';
7
- export { createDefaultCoverageConfig, createStrictCoverageConfig, createRelaxedCoverageConfig, parseCoverageThreshold, formatCoverageThreshold, validateCoverageConfig } from './config.js';
8
- export { CoverageCollector, createCoverageCollector } from './collector.js';
9
- export { CoverageAnalyzer, createCoverageAnalyzer } from './analyzer.js';
10
- export { CoverageVault } from './vault.js';
11
- export { createDefaultCoverageConfig as createCoverageConfig } from './config.js';
@@ -1,15 +0,0 @@
1
- /**
2
- * QA360 Coverage Analytics Module
3
- *
4
- * Exports all coverage analytics functionality.
5
- */
6
- // Config
7
- export { createDefaultCoverageConfig, createStrictCoverageConfig, createRelaxedCoverageConfig, parseCoverageThreshold, formatCoverageThreshold, validateCoverageConfig } from './config.js';
8
- // Collector
9
- export { CoverageCollector, createCoverageCollector } from './collector.js';
10
- // Analyzer
11
- export { CoverageAnalyzer, createCoverageAnalyzer } from './analyzer.js';
12
- // Vault integration
13
- export { CoverageVault } from './vault.js';
14
- // Re-exports for convenience
15
- export { createDefaultCoverageConfig as createCoverageConfig } from './config.js';