monocart-coverage-reports 2.13.2 → 3.0.0

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.
Files changed (68) hide show
  1. package/README.md +267 -114
  2. package/README.zh-Hans.md +268 -115
  3. package/lib/assets.js +6 -6
  4. package/lib/cli.js +191 -95
  5. package/lib/client/cdp-client.js +74 -43
  6. package/lib/client/coverage-client.js +24 -8
  7. package/lib/client/ws-session.js +115 -37
  8. package/lib/converter/ast-visitor.js +5 -7
  9. package/lib/converter/ast.js +6 -6
  10. package/lib/converter/collect-source-maps.js +9 -9
  11. package/lib/converter/converter.js +41 -115
  12. package/lib/converter/fake-source-coverage.js +64 -0
  13. package/lib/converter/find-original-range.js +3 -3
  14. package/lib/converter/flatten-source-maps.js +2 -4
  15. package/lib/converter/ignore.js +2 -4
  16. package/lib/converter/info-branch.js +5 -3
  17. package/lib/converter/info-function.js +5 -3
  18. package/lib/converter/info-statement.js +5 -3
  19. package/lib/converter/range-index.js +78 -0
  20. package/lib/converter/source-range-merge.js +103 -0
  21. package/lib/converter/untested.js +27 -55
  22. package/lib/default/options.js +13 -1
  23. package/lib/generate.js +44 -26
  24. package/lib/index.d.ts +52 -4
  25. package/lib/index.js +35 -41
  26. package/lib/istanbul/istanbul-summary.js +2 -2
  27. package/lib/istanbul/istanbul.js +35 -13
  28. package/lib/packages/chunks/chunk-RVOSNMVU.js +2 -0
  29. package/lib/packages/monocart-coverage-assets.js +4 -3
  30. package/lib/packages/monocart-coverage-converter.js +27 -0
  31. package/lib/packages/monocart-coverage-vendor.js +2 -41
  32. package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
  33. package/lib/platform/concurrency.js +47 -43
  34. package/lib/platform/share.js +1 -1
  35. package/lib/register/hooks.js +19 -13
  36. package/lib/register/register.js +5 -3
  37. package/lib/reports/codacy.js +3 -5
  38. package/lib/reports/codecov.js +3 -5
  39. package/lib/reports/console-details.js +5 -5
  40. package/lib/reports/console-summary.js +4 -4
  41. package/lib/reports/custom.js +8 -24
  42. package/lib/reports/markdown-details.js +4 -6
  43. package/lib/reports/markdown-summary.js +4 -6
  44. package/lib/reports/raw.js +10 -12
  45. package/lib/reports/v8-json.js +3 -5
  46. package/lib/reports/v8.js +3 -5
  47. package/lib/utils/dedupe.js +1 -1
  48. package/lib/utils/gc.js +30 -14
  49. package/lib/utils/markdown.js +2 -2
  50. package/lib/utils/merge/merge.js +2 -4
  51. package/lib/utils/merge/range-tree.js +1 -1
  52. package/lib/utils/request.js +85 -73
  53. package/lib/utils/snapshot.js +4 -4
  54. package/lib/utils/source-path.js +5 -5
  55. package/lib/utils/util.js +61 -41
  56. package/lib/v8/v8-summary.js +2 -4
  57. package/lib/v8/v8.js +11 -11
  58. package/migrate-to-3.md +175 -0
  59. package/package.json +153 -149
  60. package/skills/monocart-coverage-reports/SKILL.md +106 -0
  61. package/skills/monocart-coverage-reports/references/cli.md +108 -0
  62. package/skills/monocart-coverage-reports/references/collection.md +152 -0
  63. package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
  64. package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
  65. package/skills/monocart-coverage-reports/references/repository-map.md +55 -0
  66. package/lib/index.mjs +0 -9
  67. package/lib/register/hooks.mjs +0 -3
  68. package/lib/register/register.mjs +0 -3
@@ -0,0 +1,78 @@
1
+ // AST block arrays are immutable during a visit. Weak keys release the index
2
+ // with the array, and rebuilding coverage info creates fresh arrays/indices.
3
+ const indices = new WeakMap();
4
+ const linearSearchLimit = 16;
5
+
6
+ const createIndex = (ranges) => {
7
+ // V8 ranges are in start-offset order. Preserve last-in-input semantics for
8
+ // custom, unsorted coverage rather than sorting it and changing the result.
9
+ for (let i = 1; i < ranges.length; i++) {
10
+ if (ranges[i].startOffset < ranges[i - 1].startOffset) {
11
+ return null;
12
+ }
13
+ }
14
+
15
+ let size = 1;
16
+ while (size < ranges.length) {
17
+ size *= 2;
18
+ }
19
+ const maxEnds = new Float64Array(size * 2);
20
+ maxEnds.fill(-Infinity);
21
+ for (let i = 0; i < ranges.length; i++) {
22
+ maxEnds[size + i] = ranges[i].endOffset;
23
+ }
24
+ for (let i = size - 1; i > 0; i--) {
25
+ maxEnds[i] = Math.max(maxEnds[i * 2], maxEnds[i * 2 + 1]);
26
+ }
27
+ return {
28
+ size, maxEnds
29
+ };
30
+ };
31
+
32
+ const findRightmost = (maxEnds, node, left, right, limit, end) => {
33
+ if (left >= limit || maxEnds[node] < end) {
34
+ return -1;
35
+ }
36
+ if (right - left === 1) {
37
+ return left;
38
+ }
39
+ const mid = (left + right) / 2;
40
+ const found = findRightmost(maxEnds, node * 2 + 1, mid, right, limit, end);
41
+ if (found !== -1) {
42
+ return found;
43
+ }
44
+ return findRightmost(maxEnds, node * 2, left, mid, limit, end);
45
+ };
46
+
47
+ // Return the last enclosing range, including equal-start ranges. For sorted
48
+ // arrays the O(n) index is built once; subsequent queries take O(log n).
49
+ const findContainingRange = (ranges, start, end) => {
50
+ if (ranges.length <= linearSearchLimit) {
51
+ return ranges.findLast((range) => range.startOffset <= start && range.endOffset >= end);
52
+ }
53
+ if (!indices.has(ranges)) {
54
+ indices.set(ranges, createIndex(ranges));
55
+ }
56
+ const index = indices.get(ranges);
57
+ if (!index) {
58
+ return ranges.findLast((range) => range.startOffset <= start && range.endOffset >= end);
59
+ }
60
+
61
+ // Only the prefix with startOffset <= start can contain the query.
62
+ let low = 0;
63
+ let high = ranges.length;
64
+ while (low < high) {
65
+ const mid = Math.floor((low + high) / 2);
66
+ if (ranges[mid].startOffset <= start) {
67
+ low = mid + 1;
68
+ } else {
69
+ high = mid;
70
+ }
71
+ }
72
+ const found = findRightmost(index.maxEnds, 1, 0, index.size, low, end);
73
+ if (found !== -1) {
74
+ return ranges[found];
75
+ }
76
+ };
77
+
78
+ export { findContainingRange };
@@ -0,0 +1,103 @@
1
+ // Each group belongs to one source-file merge and is immutable while queried.
2
+ // Weak keys keep indices from retaining coverage after that merge finishes.
3
+ const rangeIndices = new WeakMap();
4
+ const mappingIndices = new WeakMap();
5
+ const mappingScanLimit = 32;
6
+
7
+ const createRangeIndex = (groupMap) => {
8
+ // Copy before sorting: custom group order must not be changed.
9
+ const ranges = Array.from(groupMap.values()).sort((a, b) => a.start - b.start);
10
+ const starts = new Float64Array(ranges.length);
11
+ const maxEnds = new Float64Array(ranges.length);
12
+ let maxEnd = -Infinity;
13
+ for (let i = 0; i < ranges.length; i++) {
14
+ starts[i] = ranges[i].start;
15
+ maxEnd = Math.max(maxEnd, ranges[i].end);
16
+ maxEnds[i] = maxEnd;
17
+ }
18
+ return {
19
+ starts, maxEnds
20
+ };
21
+ };
22
+
23
+ const containsRange = (group, start, end) => {
24
+ let index = rangeIndices.get(group);
25
+ if (!index) {
26
+ index = createRangeIndex(group.groupMap);
27
+ rangeIndices.set(group, index);
28
+ }
29
+ const { starts, maxEnds } = index;
30
+ let low = 0;
31
+ let high = starts.length;
32
+ // The prefix ending at low contains every possible enclosing range.
33
+ while (low < high) {
34
+ const mid = Math.floor((low + high) / 2);
35
+ if (starts[mid] <= start) {
36
+ low = mid + 1;
37
+ } else {
38
+ high = mid;
39
+ }
40
+ }
41
+ return low > 0 && maxEnds[low - 1] >= end;
42
+ };
43
+
44
+ const scanMappingPrefix = (mappings, start, end) => {
45
+ const limit = Math.min(mappingScanLimit, mappings.length);
46
+ for (let i = 0; i < limit; i++) {
47
+ const offset = mappings[i].originalOffset;
48
+ if (offset >= start && offset <= end) {
49
+ return true;
50
+ }
51
+ }
52
+ // A short list was exhausted; otherwise the answer is still unknown.
53
+ return mappings.length <= limit ? false : null;
54
+ };
55
+
56
+ const hasMapping = (group, start, end) => {
57
+ let offsets = mappingIndices.get(group);
58
+ if (!offsets) {
59
+ const mappings = group.state.decodedMappings;
60
+ // Early hits (or exhausted short lists) need no full copy/sort. Probe
61
+ // only a bounded prefix so many misses cannot become quadratic scans.
62
+ const match = scanMappingPrefix(mappings, start, end);
63
+ if (match !== null) {
64
+ return match;
65
+ }
66
+ // Sort a separate numeric index, not the shared decoded mappings. The
67
+ // upstream order is by line/column; malformed columns can produce
68
+ // non-monotonic offsets, so do not assume offset order here.
69
+ offsets = Float64Array.from(mappings, (item) => item.originalOffset).sort();
70
+ mappingIndices.set(group, offsets);
71
+ }
72
+ let low = 0;
73
+ let high = offsets.length;
74
+ while (low < high) {
75
+ const mid = Math.floor((low + high) / 2);
76
+ if (offsets[mid] < start) {
77
+ low = mid + 1;
78
+ } else {
79
+ high = mid;
80
+ }
81
+ }
82
+ // Preserve the old inclusive endpoints, including a mapping at range.end.
83
+ return low < offsets.length && offsets[low] <= end;
84
+ };
85
+
86
+ const isUncoveredRange = (range, key, uncoveredGroups) => {
87
+ for (const group of uncoveredGroups) {
88
+ if (group.groupMap.has(key)) {
89
+ continue;
90
+ }
91
+ if (containsRange(group, range.start, range.end)) {
92
+ continue;
93
+ }
94
+ if (hasMapping(group, range.start, range.end)) {
95
+ // Another group maps this range without marking it uncovered.
96
+ // No later group can change that decision.
97
+ return false;
98
+ }
99
+ }
100
+ return true;
101
+ };
102
+
103
+ export { isUncoveredRange };
@@ -1,10 +1,10 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const Util = require('../utils/util.js');
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Util } from '../utils/util.js';
4
4
 
5
- const { pathToFileURL, fileURLToPath } = require('url');
5
+ import { pathToFileURL, fileURLToPath } from 'node:url';
6
6
 
7
- const { normalizeSourcePath, getSourcePathReplacer } = require('../utils/source-path.js');
7
+ import { normalizeSourcePath, getSourcePathReplacer } from '../utils/source-path.js';
8
8
 
9
9
  // ========================================================================================================
10
10
 
@@ -20,41 +20,7 @@ const resolveAllDirList = (input) => {
20
20
  return dirList;
21
21
  };
22
22
 
23
- const resolveAllFilter = (input) => {
24
- // for function handler
25
- if (typeof input === 'function') {
26
- return input;
27
- }
28
-
29
- // for single minimatch pattern
30
- if (input && typeof input === 'string') {
31
- // string to multiple patterns "{...}"
32
- const obj = Util.strToObj(input);
33
- if (obj) {
34
- input = obj;
35
- } else {
36
- return (filePath) => {
37
- return Util.betterMinimatch(filePath, input);
38
- };
39
- }
40
- }
41
-
42
- // for patterns
43
- if (input && typeof input === 'object') {
44
- const patterns = Object.keys(input);
45
- return (filePath) => {
46
- for (const pattern of patterns) {
47
- if (Util.betterMinimatch(filePath, pattern)) {
48
- return input[pattern];
49
- }
50
- }
51
- // false if not matched
52
- };
53
- }
54
-
55
- // default
56
- return () => true;
57
- };
23
+ const resolveAllFilter = (input) => Util.createFilterHandler(input, (filePath) => filePath);
58
24
 
59
25
  const resolveAllOptions = (input) => {
60
26
  if (!input) {
@@ -204,9 +170,22 @@ const getUntestedCoverageData = async (entryList, options, coverageType) => {
204
170
  return emptyCoverageList;
205
171
  };
206
172
 
173
+ const resolveUntestedPath = (entry, baseDir, sourcePathReplacer) => {
174
+ const sourcePath = normalizeSourcePath(entry.url, baseDir);
175
+ if (sourcePathReplacer) {
176
+ const replacement = sourcePathReplacer(sourcePath, entry);
177
+ if (typeof replacement === 'string' && replacement) {
178
+ return replacement;
179
+ }
180
+ }
181
+ return sourcePath;
182
+ };
183
+
207
184
  const getEmptyCoverages = async (testedMap, options, coverageType, fileList, fileTransformer) => {
208
185
  const sourceFilter = Util.getSourceFilter(options);
209
186
  const sourcePathReplacer = getSourcePathReplacer(options);
187
+ // User callbacks may inspect or mutate entry.source; object replacements cannot.
188
+ const needsSourceForPath = typeof options.sourcePath === 'function';
210
189
  const baseDir = options.baseDir;
211
190
 
212
191
  // console.log(fileList);
@@ -217,24 +196,16 @@ const getEmptyCoverages = async (testedMap, options, coverageType, fileList, fil
217
196
  const { fileType, filePath } = item;
218
197
  const type = resolveFileType(fileType, filePath);
219
198
  const url = pathToFileURL(filePath).toString();
220
- const source = Util.readFileSync(filePath);
221
199
  const entry = {
222
200
  empty: true,
223
201
  type,
224
- url,
225
- source
202
+ url
226
203
  };
227
-
228
- // normalize sourcePath here
229
- let sourcePath = normalizeSourcePath(url, baseDir);
230
- if (sourcePathReplacer) {
231
- const newSourcePath = sourcePathReplacer(sourcePath, entry);
232
- if (typeof newSourcePath === 'string' && newSourcePath) {
233
- sourcePath = newSourcePath;
234
- }
204
+ if (needsSourceForPath) {
205
+ entry.source = Util.readFileSync(filePath);
235
206
  }
236
207
 
237
- // console.log(sourcePath);
208
+ const sourcePath = resolveUntestedPath(entry, baseDir, sourcePathReplacer);
238
209
 
239
210
  if (testedMap.has(sourcePath)) {
240
211
  continue;
@@ -244,6 +215,9 @@ const getEmptyCoverages = async (testedMap, options, coverageType, fileList, fil
244
215
  continue;
245
216
  }
246
217
 
218
+ if (!needsSourceForPath) {
219
+ entry.source = Util.readFileSync(filePath);
220
+ }
247
221
  entry.sourcePath = sourcePath;
248
222
 
249
223
  await fileTransformer(entry, coverageType);
@@ -302,6 +276,4 @@ const getUntestedList = (testedMap, options, coverageType = 'v8') => {
302
276
  };
303
277
 
304
278
 
305
- module.exports = {
306
- getUntestedList
307
- };
279
+ export { getUntestedList };
@@ -1,4 +1,4 @@
1
- module.exports = {
1
+ const defaultOptions = {
2
2
 
3
3
  // {string} logging levels: off, error, info, debug
4
4
  logging: 'info',
@@ -24,6 +24,12 @@ module.exports = {
24
24
  // {string|string[]} input raw dir(s)
25
25
  inputDir: null,
26
26
 
27
+ // (mcr test only) {string|string[]|null} Test file paths or glob patterns accepted by node --test.
28
+ // A string is one positional argument; an array supplies multiple arguments unchanged.
29
+ // Used when no CLI targets are supplied; null skips tests and is not passed to Node.
30
+ // Ignored by other CLI commands and the API.
31
+ test: null,
32
+
27
33
  // {string} base dir for normalizing the relative source path, defaults to cwd
28
34
  baseDir: null,
29
35
 
@@ -43,6 +49,10 @@ module.exports = {
43
49
  // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
44
50
  sourceFilter: null,
45
51
 
52
+ // (V8 only) Filter final files by sourcePath after sourcemap conversion and adding untested files.
53
+ // outputFilter: (entry) => entry.sourcePath.startsWith('src/'),
54
+ outputFilter: null,
55
+
46
56
  // The combined filter for entryFilter and sourceFilter
47
57
  filter: null,
48
58
 
@@ -105,3 +115,5 @@ module.exports = {
105
115
  // onEnd: async (reportData) => {}
106
116
  onEnd: null
107
117
  };
118
+
119
+ export { defaultOptions };
package/lib/generate.js CHANGED
@@ -1,35 +1,28 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { fileURLToPath } = require('url');
4
- const EC = require('eight-colors');
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import EC from 'eight-colors';
5
5
 
6
- const Util = require('./utils/util.js');
7
- const { convertV8List } = require('./converter/converter.js');
8
- const { resolveSourceMap } = require('./converter/collect-source-maps.js');
9
- const { StreamZip } = require('./packages/monocart-coverage-vendor.js');
6
+ import { Util } from './utils/util.js';
7
+ import { resolveSourceMap } from './converter/collect-source-maps.js';
10
8
 
11
9
  // ========================================================================================================
12
10
  // built-in reports
13
11
 
14
- // istanbul
15
- const { mergeIstanbulCoverage, saveIstanbulReports } = require('./istanbul/istanbul.js');
16
-
17
12
  // v8
18
- const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
13
+ import { mergeV8Coverage, saveV8Report } from './v8/v8.js';
19
14
 
20
15
  // both
21
- const { codecovReport } = require('./reports/codecov.js');
22
- const { codacyReport } = require('./reports/codacy.js');
23
-
24
- const { consoleDetailsReport } = require('./reports/console-details.js');
25
- const { consoleSummaryReport } = require('./reports/console-summary.js');
16
+ import { codecovReport } from './reports/codecov.js';
17
+ import { codacyReport } from './reports/codacy.js';
26
18
 
27
- const { markdownDetailsReport } = require('./reports/markdown-details.js');
28
- const { markdownSummaryReport } = require('./reports/markdown-summary.js');
19
+ import { consoleDetailsReport } from './reports/console-details.js';
20
+ import { consoleSummaryReport } from './reports/console-summary.js';
29
21
 
30
- const { rawReport } = require('./reports/raw.js');
22
+ import { markdownDetailsReport } from './reports/markdown-details.js';
23
+ import { markdownSummaryReport } from './reports/markdown-summary.js';
31
24
 
32
- const { customReport } = require('./reports/custom.js');
25
+ import { customReport } from './reports/custom.js';
33
26
 
34
27
  const allBuiltInReports = {
35
28
  // v8
@@ -71,7 +64,10 @@ const bothBuiltInReports = {
71
64
  'markdown-details': markdownDetailsReport,
72
65
  'markdown-summary': markdownSummaryReport,
73
66
 
74
- 'raw': rawReport
67
+ 'raw': async (... args) => {
68
+ const { rawReport } = await import('./reports/raw.js');
69
+ return rawReport(... args);
70
+ }
75
71
  };
76
72
 
77
73
  // ========================================================================================================
@@ -146,6 +142,7 @@ const generateV8ListReports = async (v8list, coverageData, fileSources, options)
146
142
  let istanbulReportPath;
147
143
  // v8 to istanbul reports
148
144
  if (options.reportGroup.has('istanbul')) {
145
+ const { saveIstanbulReports } = await import('./istanbul/istanbul.js');
149
146
  const istanbulCoverageResults = await saveIstanbulReports(coverageData, fileSources, options);
150
147
  istanbulReportPath = istanbulCoverageResults.reportPath;
151
148
  }
@@ -175,17 +172,34 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
175
172
  // console.log('after merge', v8list.map((it) => it.url));
176
173
 
177
174
  const t2 = Date.now();
178
- const results = await convertV8List(v8list, options);
175
+ const { convertV8List } = await import('./converter/converter.js');
176
+ const results = await convertV8List(v8list, options, options.reportGroup.has('istanbul'));
179
177
  Util.logTime(`${EC.magenta('├')} [generate] converted coverage data`, t2);
180
178
 
181
179
  const {
182
180
  v8DataList, coverageData, fileSources
183
181
  } = results;
184
- return generateV8ListReports(v8DataList, coverageData, fileSources, options);
182
+
183
+ let filteredV8DataList = v8DataList;
184
+ if (options.outputFilter) {
185
+ const outputFilter = Util.createFilterHandler(options.outputFilter, (entry) => entry.sourcePath);
186
+ filteredV8DataList = v8DataList.filter(outputFilter);
187
+ // Keep V8 and converted Istanbul reports in sync.
188
+ const sourcePaths = new Set(filteredV8DataList.map((entry) => entry.sourcePath));
189
+ Object.keys(coverageData).forEach((sourcePath) => {
190
+ if (!sourcePaths.has(sourcePath)) {
191
+ delete coverageData[sourcePath];
192
+ delete fileSources[sourcePath];
193
+ }
194
+ });
195
+ }
196
+
197
+ return generateV8ListReports(filteredV8DataList, coverageData, fileSources, options);
185
198
  }
186
199
 
187
200
  // istanbul data
188
201
  const t3 = Date.now();
202
+ const { mergeIstanbulCoverage, saveIstanbulReports } = await import('./istanbul/istanbul.js');
189
203
  const istanbulData = await mergeIstanbulCoverage(dataList, options);
190
204
  Util.logTime(`${EC.magenta('├')} [generate] prepared istanbul coverage data`, t3);
191
205
  const fileSources = options.fileSources || {};
@@ -277,6 +291,8 @@ const addDirData = async (mcr, dataList, sourceCache, dir) => {
277
291
  };
278
292
 
279
293
  const addZipData = async (mcr, dataList, sourceCache, dir) => {
294
+ const { default: StreamZipModule } = await import('node-stream-zip');
295
+ const StreamZip = StreamZipModule.async;
280
296
  const zip = new StreamZip({
281
297
  file: dir
282
298
  });
@@ -327,7 +343,9 @@ const getInputData = async (mcr) => {
327
343
  return;
328
344
  }
329
345
  // No coverage input: use V8 so `all` can add untested files.
330
- dataList.push({ type: 'v8', data: [] });
346
+ dataList.push({
347
+ type: 'v8', data: []
348
+ });
331
349
  }
332
350
 
333
351
  return {
@@ -491,7 +509,7 @@ const readFromDir = async (mcr, dir) => {
491
509
 
492
510
  };
493
511
 
494
- module.exports = {
512
+ export {
495
513
  getInputData,
496
514
  readFromDir,
497
515
  generateCoverageReports
package/lib/index.d.ts CHANGED
@@ -322,6 +322,14 @@ declare namespace MCR {
322
322
  [pattern: string]: boolean;
323
323
  } | ((sourcePath: string) => boolean);
324
324
 
325
+ /** (V8 only) Filter final files after sourcemap conversion and adding untested files.
326
+ * Applies to V8 reports and converted Istanbul reports. Defaults to null (disabled).
327
+ * Strings and object keys match sourcePath using the same rules as entryFilter and sourceFilter.
328
+ */
329
+ outputFilter?: string | {
330
+ [pattern: string]: boolean;
331
+ } | ((entry: CoverageFile) => boolean) | null;
332
+
325
333
  /**
326
334
  * The combined filter for entryFilter and sourceFilter
327
335
  */
@@ -405,7 +413,7 @@ declare namespace MCR {
405
413
  cleanCache?: boolean;
406
414
 
407
415
  /**
408
- * {number} gc threshold
416
+ * {number} JavaScript heapUsed threshold in MB
409
417
  * for example: sets gc to 1024 means that force gc when the memory > 1024M at certain critical stages
410
418
  * https://nodejs.org/docs/latest/api/v8.html#v8setflagsfromstringflags
411
419
  */
@@ -432,6 +440,15 @@ declare namespace MCR {
432
440
 
433
441
  export interface McrCliOptions extends CoverageReportOptions {
434
442
 
443
+ /** Only used by `mcr test`. Test file paths or glob patterns accepted by node --test.
444
+ * A string is one positional argument; an array supplies multiple arguments unchanged.
445
+ * Node handles parsing and discovery relative to cwd; null is not passed to Node.
446
+ * Positional CLI targets override this option.
447
+ * Defaults to null (skip tests unless CLI targets are supplied).
448
+ * Requires Node.js >= 22.15.0. Ignored by other commands and the API.
449
+ */
450
+ test?: string | string[] | null;
451
+
435
452
  /** (CLI only) {function} onStart hook */
436
453
  onStart?: (coverageReport: CoverageReport) => void | Promise<void>;
437
454
 
@@ -479,6 +496,8 @@ declare namespace MCR {
479
496
  /** load config file.
480
497
  *
481
498
  * @param configFile custom config file path
499
+ * Throws if a custom config is missing or a discovered config fails to load.
500
+ * If no custom config is specified and no default config is found, returns normally.
482
501
  *
483
502
  * Supports loading default config file if no custom config specified:
484
503
  'mcr.config.js',
@@ -532,6 +551,9 @@ declare namespace MCR {
532
551
  //=====================================================================================================
533
552
 
534
553
  export class CoverageClient {
554
+ /** Best-effort command: logs failures at debug level and resolves to null instead of rejecting. */
555
+ safeSend: (method: string, params?: any) => Promise<any>;
556
+
535
557
  /** start js coverage */
536
558
  startJSCoverage: () => Promise<void>;
537
559
  /** stop and return js coverage */
@@ -567,7 +589,8 @@ declare namespace MCR {
567
589
 
568
590
  /** custom websocket CDPSession */
569
591
  export class WSSession implements CDPSession {
570
- constructor(ws: any);
592
+ /** Request timeout defaults to 30000ms; 0 disables the deadline. */
593
+ constructor(ws: any, options?: { timeout?: number });
571
594
  send: (method: string, params?: any) => Promise<any>;
572
595
  on: (type: string, handler: (e: any) => void) => void;
573
596
  detach: () => Promise<void>;
@@ -588,8 +611,10 @@ declare namespace MCR {
588
611
  target?: (targets: any) => any;
589
612
  /** websocket options */
590
613
  ws?: any;
591
- /** timeout, defaults to 10 * 1000 */
614
+ /** HTTP discovery / WebSocket connection timeout, defaults to 10000ms; 0 disables it. */
592
615
  timeout?: number;
616
+ /** Per-command timeout for built-in WebSocket sessions, defaults to 30000ms; 0 disables it. */
617
+ requestTimeout?: number;
593
618
  }
594
619
 
595
620
  export function CDPClient(cdpOptions: CDPOptions): Promise<CoverageClient | undefined>;
@@ -603,4 +628,27 @@ declare namespace MCR {
603
628
  /** create coverage report */
604
629
  declare function MCR(options?: MCR.CoverageReportOptions): MCR.CoverageReport;
605
630
 
606
- export = MCR;
631
+ export { MCR as default, MCR };
632
+ export import V8CoverageEntry = MCR.V8CoverageEntry;
633
+ export import Watermarks = MCR.Watermarks;
634
+ export import ReportDescription = MCR.ReportDescription;
635
+ export import AddedResults = MCR.AddedResults;
636
+ export import MetricsSummary = MCR.MetricsSummary;
637
+ export import CoverageSummary = MCR.CoverageSummary;
638
+ export import CoverageRange = MCR.CoverageRange;
639
+ export import IgnoredRange = MCR.IgnoredRange;
640
+ export import CoverageFile = MCR.CoverageFile;
641
+ export import CoverageResults = MCR.CoverageResults;
642
+ export import LoggingType = MCR.LoggingType;
643
+ export import CoverageReportOptions = MCR.CoverageReportOptions;
644
+ export import McrCliOptions = MCR.McrCliOptions;
645
+ export import CoverageSnapshot = MCR.CoverageSnapshot;
646
+ export import CoverageClient = MCR.CoverageClient;
647
+ export import CDPSession = MCR.CDPSession;
648
+ export import CDPOptions = MCR.CDPOptions;
649
+ export import CoverageReport = MCR.CoverageReport;
650
+ export import getSnapshot = MCR.getSnapshot;
651
+ export import diffSnapshot = MCR.diffSnapshot;
652
+ export import WSSession = MCR.WSSession;
653
+ export import CDPClient = MCR.CDPClient;
654
+ export import Util = MCR.Util;