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.
- package/README.md +267 -114
- package/README.zh-Hans.md +268 -115
- package/lib/assets.js +6 -6
- package/lib/cli.js +191 -95
- package/lib/client/cdp-client.js +74 -43
- package/lib/client/coverage-client.js +24 -8
- package/lib/client/ws-session.js +115 -37
- package/lib/converter/ast-visitor.js +5 -7
- package/lib/converter/ast.js +6 -6
- package/lib/converter/collect-source-maps.js +9 -9
- package/lib/converter/converter.js +41 -115
- package/lib/converter/fake-source-coverage.js +64 -0
- package/lib/converter/find-original-range.js +3 -3
- package/lib/converter/flatten-source-maps.js +2 -4
- package/lib/converter/ignore.js +2 -4
- package/lib/converter/info-branch.js +5 -3
- package/lib/converter/info-function.js +5 -3
- package/lib/converter/info-statement.js +5 -3
- package/lib/converter/range-index.js +78 -0
- package/lib/converter/source-range-merge.js +103 -0
- package/lib/converter/untested.js +27 -55
- package/lib/default/options.js +13 -1
- package/lib/generate.js +44 -26
- package/lib/index.d.ts +52 -4
- package/lib/index.js +35 -41
- package/lib/istanbul/istanbul-summary.js +2 -2
- package/lib/istanbul/istanbul.js +35 -13
- package/lib/packages/chunks/chunk-RVOSNMVU.js +2 -0
- package/lib/packages/monocart-coverage-assets.js +4 -3
- package/lib/packages/monocart-coverage-converter.js +27 -0
- package/lib/packages/monocart-coverage-vendor.js +2 -41
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
- package/lib/platform/concurrency.js +47 -43
- package/lib/platform/share.js +1 -1
- package/lib/register/hooks.js +19 -13
- package/lib/register/register.js +5 -3
- package/lib/reports/codacy.js +3 -5
- package/lib/reports/codecov.js +3 -5
- package/lib/reports/console-details.js +5 -5
- package/lib/reports/console-summary.js +4 -4
- package/lib/reports/custom.js +8 -24
- package/lib/reports/markdown-details.js +4 -6
- package/lib/reports/markdown-summary.js +4 -6
- package/lib/reports/raw.js +10 -12
- package/lib/reports/v8-json.js +3 -5
- package/lib/reports/v8.js +3 -5
- package/lib/utils/dedupe.js +1 -1
- package/lib/utils/gc.js +30 -14
- package/lib/utils/markdown.js +2 -2
- package/lib/utils/merge/merge.js +2 -4
- package/lib/utils/merge/range-tree.js +1 -1
- package/lib/utils/request.js +85 -73
- package/lib/utils/snapshot.js +4 -4
- package/lib/utils/source-path.js +5 -5
- package/lib/utils/util.js +61 -41
- package/lib/v8/v8-summary.js +2 -4
- package/lib/v8/v8.js +11 -11
- package/migrate-to-3.md +175 -0
- package/package.json +153 -149
- package/skills/monocart-coverage-reports/SKILL.md +106 -0
- package/skills/monocart-coverage-reports/references/cli.md +108 -0
- package/skills/monocart-coverage-reports/references/collection.md +152 -0
- package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +55 -0
- package/lib/index.mjs +0 -9
- package/lib/register/hooks.mjs +0 -3
- 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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Util } from '../utils/util.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
getUntestedList
|
|
307
|
-
};
|
|
279
|
+
export { getUntestedList };
|
package/lib/default/options.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
13
|
+
import { mergeV8Coverage, saveV8Report } from './v8/v8.js';
|
|
19
14
|
|
|
20
15
|
// both
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
19
|
+
import { consoleDetailsReport } from './reports/console-details.js';
|
|
20
|
+
import { consoleSummaryReport } from './reports/console-summary.js';
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
import { markdownDetailsReport } from './reports/markdown-details.js';
|
|
23
|
+
import { markdownSummaryReport } from './reports/markdown-summary.js';
|
|
31
24
|
|
|
32
|
-
|
|
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':
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|