monocart-coverage-reports 2.7.1 → 2.7.3
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 +14 -8
- package/lib/cli.js +74 -88
- package/lib/converter/collect-source-maps.js +12 -1
- package/lib/converter/find-original-range.js +97 -94
- package/lib/generate.js +130 -1
- package/lib/index.d.ts +6 -0
- package/lib/index.js +20 -6
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/register/hooks.js +58 -0
- package/lib/register/hooks.mjs +3 -0
- package/lib/register/register.js +4 -0
- package/lib/register/register.mjs +3 -0
- package/lib/utils/source-path.js +30 -14
- package/package.json +97 -83
package/README.md
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
> A code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports. As early as 2017, V8 started offering native support for coverage data. However, we have essentially only been able to see coverage reports from Istanbul. Nowadays, we can directly generate V8 coverage reports with `monocart-coverage-reports`.
|
|
9
|
+
> A code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
11
10
|
|
|
12
11
|
* [Usage](#usage)
|
|
13
12
|
* [Default Options](#default-options)
|
|
14
13
|
* [Available Reports](#available-reports)
|
|
15
14
|
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
|
|
16
15
|
* [onEnd Hook](#onend-hook)
|
|
17
|
-
* [
|
|
16
|
+
* [Command Line](#command-line)
|
|
18
17
|
* [Compare Reports](#compare-reports)
|
|
19
18
|
* [Compare Workflows](#compare-workflows)
|
|
20
19
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
@@ -275,7 +274,7 @@ const coverageOptions = {
|
|
|
275
274
|
}
|
|
276
275
|
```
|
|
277
276
|
|
|
278
|
-
##
|
|
277
|
+
## Command Line
|
|
279
278
|
> The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
|
|
280
279
|
|
|
281
280
|
- Installing globally
|
|
@@ -313,21 +312,27 @@ Options:
|
|
|
313
312
|
--inline inline html for v8 report
|
|
314
313
|
--assetsPath <path> assets path if not inline
|
|
315
314
|
--lcov generate lcov.info file
|
|
315
|
+
--import <module> preload module at startup
|
|
316
|
+
--require <module> preload module at startup
|
|
316
317
|
-h, --help display help for command
|
|
317
318
|
```
|
|
318
319
|
|
|
319
320
|
- Loading config file by priority:
|
|
320
|
-
-
|
|
321
|
+
- Custom config file with `-c` or `--config`
|
|
321
322
|
- `mcr.config.js`
|
|
322
323
|
- `mcr.config.cjs`
|
|
323
324
|
- `mcr.config.mjs`
|
|
324
325
|
- `mcr.config.json` - json format
|
|
326
|
+
- `mcr.config.ts` (requires preloading the ts execution module)
|
|
325
327
|
- `.mcrrc.js`
|
|
326
328
|
- `.mcrrc` - json format
|
|
327
|
-
- `mcr.config.ts`
|
|
328
329
|
|
|
329
|
-
- Working with `ts-node`, see [mcr-ts-node](https://github.com/cenfun/mcr-ts-node)
|
|
330
330
|
- Working with `tsx`, see [mcr-tsx](https://github.com/cenfun/mcr-tsx)
|
|
331
|
+
```sh
|
|
332
|
+
npx mcr --import tsx tsx ./src/example.ts
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
- Working with `ts-node`, see [mcr-ts-node](https://github.com/cenfun/mcr-ts-node)
|
|
331
336
|
|
|
332
337
|
## Compare Reports
|
|
333
338
|
| | Istanbul | V8 | V8 to Istanbul |
|
|
@@ -455,7 +460,7 @@ Possible solutions:
|
|
|
455
460
|
> node [./test/test-node-koa.js](./test/test-node-koa.js)
|
|
456
461
|
|
|
457
462
|
- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
|
|
458
|
-
- see [
|
|
463
|
+
- see [Command Line](#command-line)
|
|
459
464
|
|
|
460
465
|
## Collecting V8 Coverage Data with `CDPClient` API
|
|
461
466
|
- Work with node debugger `--inspect=9229`
|
|
@@ -815,6 +820,7 @@ const coverageOptions = {
|
|
|
815
820
|
- [cypress-monocart-coverage](https://github.com/cenfun/cypress-monocart-coverage) - A Cypress plugin for coverage reports
|
|
816
821
|
|
|
817
822
|
### [CodeceptJS](https://github.com/codeceptjs/CodeceptJS)
|
|
823
|
+
- CodeceptJS has integrated MCR from version [3.5.15](https://github.com/codeceptjs/CodeceptJS/releases/tag/3.5.15)
|
|
818
824
|
- [codeceptjs-monocart-coverage](https://github.com/cenfun/codeceptjs-monocart-coverage) - A CodeceptJS plugin for coverage reports
|
|
819
825
|
|
|
820
826
|
### [WebdriverIO](https://github.com/webdriverio/webdriverio)
|
package/lib/cli.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const EC = require('eight-colors');
|
|
6
|
-
const { fileURLToPath } = require('url');
|
|
7
6
|
|
|
8
7
|
const {
|
|
9
8
|
foregroundChild,
|
|
@@ -12,120 +11,104 @@ const {
|
|
|
12
11
|
|
|
13
12
|
const MCR = require('./index.js');
|
|
14
13
|
const Util = require('./utils/util.js');
|
|
15
|
-
const { resolveSourceMap } = require('./converter/collect-source-maps.js');
|
|
16
14
|
|
|
17
15
|
const version = require('../package.json').version;
|
|
18
16
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (fs.existsSync(filePath)) {
|
|
24
|
-
source = fs.readFileSync(filePath).toString('utf8');
|
|
17
|
+
const getRegisterPath = (filename) => {
|
|
18
|
+
const rel = Util.relativePath(path.resolve(__dirname, 'register', filename));
|
|
19
|
+
if (rel.startsWith('.')) {
|
|
20
|
+
return rel;
|
|
25
21
|
}
|
|
22
|
+
return `./${rel}`;
|
|
23
|
+
};
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const isFeatureSupported = (nv) => {
|
|
26
|
+
// module register added in Node.js: v20.6.0
|
|
27
|
+
const [major, minor] = nv.split('.').map((s) => parseInt(s));
|
|
28
|
+
if (major < 20) {
|
|
29
|
+
return false;
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const lineLengths = sourcemapData && sourcemapData.lineLengths;
|
|
35
|
-
|
|
36
|
-
// for fake source file (can not parse to AST)
|
|
37
|
-
if (lineLengths) {
|
|
38
|
-
let fakeSource = '';
|
|
39
|
-
sourcemapData.lineLengths.forEach((length) => {
|
|
40
|
-
fakeSource += `${''.padEnd(length, '*')}\n`;
|
|
41
|
-
});
|
|
42
|
-
entry.fake = true;
|
|
43
|
-
entry.source = fakeSource;
|
|
31
|
+
if (major > 20) {
|
|
32
|
+
return true;
|
|
44
33
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// This is a problem for typescript
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const resolveEntrySourceMap = (entry, sourceMapCache = {}) => {
|
|
52
|
-
// sourcemap data
|
|
53
|
-
const sourcemapData = sourceMapCache[entry.url];
|
|
54
|
-
if (sourcemapData) {
|
|
55
|
-
if (sourcemapData.data) {
|
|
56
|
-
entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
|
|
57
|
-
}
|
|
34
|
+
if (minor < 6) {
|
|
35
|
+
return false;
|
|
58
36
|
}
|
|
37
|
+
return true;
|
|
59
38
|
};
|
|
60
39
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (!
|
|
40
|
+
const initNodeOptions = async (cliOptions) => {
|
|
41
|
+
const nv = process.versions.node;
|
|
42
|
+
if (!isFeatureSupported(nv)) {
|
|
43
|
+
Util.logInfo(EC.yellow(`The current Node.js version "${nv}" does NOT support "module.register", it requires "20.6.0" or higher.`));
|
|
64
44
|
return;
|
|
65
45
|
}
|
|
66
46
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!Util.isList(coverageList)) {
|
|
72
|
-
return;
|
|
47
|
+
const nodeOptions = [];
|
|
48
|
+
if (process.env.NODE_OPTIONS) {
|
|
49
|
+
nodeOptions.push(process.env.NODE_OPTIONS);
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
if (cliOptions.import) {
|
|
53
|
+
nodeOptions.push('--import');
|
|
54
|
+
nodeOptions.push(cliOptions.import);
|
|
55
|
+
await import(cliOptions.import);
|
|
56
|
+
} else if (cliOptions.require) {
|
|
57
|
+
nodeOptions.push('--require');
|
|
58
|
+
nodeOptions.push(cliOptions.require);
|
|
59
|
+
await import(cliOptions.require);
|
|
78
60
|
}
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
};
|
|
62
|
+
// console.log(nodeOptions);
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
// export source after
|
|
65
|
+
const hasImport = nodeOptions.find((it) => typeof it === 'string' && it.includes('--import'));
|
|
66
|
+
if (hasImport) {
|
|
67
|
+
const importPath = getRegisterPath('register.mjs');
|
|
68
|
+
const hasImportPath = nodeOptions.find((it) => typeof it === 'string' && it.includes(importPath));
|
|
69
|
+
if (!hasImportPath) {
|
|
70
|
+
nodeOptions.push('--import');
|
|
71
|
+
nodeOptions.push(importPath);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const requirePath = getRegisterPath('register.js');
|
|
75
|
+
const hasRequirePath = nodeOptions.find((it) => typeof it === 'string' && it.includes(requirePath));
|
|
76
|
+
if (!hasRequirePath) {
|
|
77
|
+
nodeOptions.push('--require');
|
|
78
|
+
nodeOptions.push(requirePath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
84
81
|
|
|
85
|
-
|
|
82
|
+
// console.log(nodeOptions);
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
if (!files.length) {
|
|
89
|
-
return added;
|
|
90
|
-
}
|
|
84
|
+
process.env.NODE_OPTIONS = nodeOptions.join(' ');
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
const entryFilter = coverageReport.getEntryFilter();
|
|
94
|
-
for (const filename of files) {
|
|
95
|
-
const filePath = path.resolve(nodeV8CoverageDir, filename);
|
|
96
|
-
const content = fs.readFileSync(filePath).toString('utf-8');
|
|
97
|
-
if (content) {
|
|
98
|
-
const json = JSON.parse(content);
|
|
99
|
-
const coverageList = json.result;
|
|
100
|
-
const sourceMapCache = json['source-map-cache'];
|
|
101
|
-
const coverageData = collectCoverageData(coverageList, entryFilter, sourceMapCache);
|
|
102
|
-
if (coverageData) {
|
|
103
|
-
await coverageReport.add(coverageData);
|
|
104
|
-
added = true;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
86
|
+
};
|
|
108
87
|
|
|
109
|
-
|
|
88
|
+
const initNodeV8CoverageDir = (coverageOptions) => {
|
|
89
|
+
// dir for node v8 coverage
|
|
90
|
+
const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
|
|
91
|
+
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
|
|
92
|
+
// clean v8 cache before running
|
|
93
|
+
Util.rmSync(nodeV8CoverageDir);
|
|
94
|
+
// Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
|
|
95
|
+
|
|
96
|
+
return nodeV8CoverageDir;
|
|
110
97
|
};
|
|
111
98
|
|
|
112
99
|
const executeCommand = async (command, cliOptions) => {
|
|
113
100
|
|
|
114
101
|
Util.logInfo(`Execute: ${EC.cyan(command)}`);
|
|
115
102
|
|
|
103
|
+
await initNodeOptions(cliOptions);
|
|
104
|
+
|
|
116
105
|
// console.log(options);
|
|
117
106
|
const coverageReport = MCR(cliOptions);
|
|
118
107
|
await coverageReport.loadConfig(cliOptions.config);
|
|
119
108
|
coverageReport.cleanCache();
|
|
120
109
|
|
|
121
110
|
const coverageOptions = coverageReport.options;
|
|
122
|
-
|
|
123
|
-
// dir for node v8 coverage
|
|
124
|
-
const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
|
|
125
|
-
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
|
|
126
|
-
// clean v8 cache before running
|
|
127
|
-
Util.rmSync(nodeV8CoverageDir);
|
|
128
|
-
// Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
|
|
111
|
+
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
|
|
129
112
|
|
|
130
113
|
// =========================================
|
|
131
114
|
// onStart hook
|
|
@@ -141,11 +124,6 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
141
124
|
|
|
142
125
|
// generate coverage even it is failed. code != 0
|
|
143
126
|
|
|
144
|
-
if (!fs.existsSync(nodeV8CoverageDir)) {
|
|
145
|
-
EC.logRed(`Not found V8 coverage dir: ${Util.relativePath(nodeV8CoverageDir)}`);
|
|
146
|
-
return 1;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
127
|
// =========================================
|
|
150
128
|
// onReady hook before adding coverage data.
|
|
151
129
|
// Sometimes, the child process has not yet finished writing the coverage data, and it needs to wait here.
|
|
@@ -155,7 +133,12 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
155
133
|
}
|
|
156
134
|
// =========================================
|
|
157
135
|
|
|
158
|
-
|
|
136
|
+
if (!fs.existsSync(nodeV8CoverageDir)) {
|
|
137
|
+
EC.logRed(`Invalid V8 coverage dir: ${Util.relativePath(nodeV8CoverageDir)}`);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
159
142
|
await coverageReport.generate();
|
|
160
143
|
|
|
161
144
|
// remove nodeV8CoverageDir
|
|
@@ -169,7 +152,7 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
169
152
|
};
|
|
170
153
|
|
|
171
154
|
process.on('uncaughtException', function(err) {
|
|
172
|
-
Util.logError(`
|
|
155
|
+
Util.logError(`Process uncaughtException: ${err.message || err}`);
|
|
173
156
|
});
|
|
174
157
|
|
|
175
158
|
program
|
|
@@ -196,6 +179,9 @@ program
|
|
|
196
179
|
|
|
197
180
|
.option('--lcov', 'generate lcov.info file')
|
|
198
181
|
|
|
182
|
+
.option('--import <module>', 'preload module at startup')
|
|
183
|
+
.option('--require <module>', 'preload module at startup')
|
|
184
|
+
|
|
199
185
|
.action((_command, cliOptions) => {
|
|
200
186
|
const command = program.args.join(' ');
|
|
201
187
|
executeCommand(command, cliOptions);
|
|
@@ -128,6 +128,17 @@ const saveSourceFile = async (filePath, data) => {
|
|
|
128
128
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
const getInlineSourceMap = (content) => {
|
|
132
|
+
let smc;
|
|
133
|
+
try {
|
|
134
|
+
smc = convertSourceMap.fromSource(content);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// ignore "//# sourceMappingURL=" in a string
|
|
137
|
+
// console.log(e);
|
|
138
|
+
}
|
|
139
|
+
return smc;
|
|
140
|
+
};
|
|
141
|
+
|
|
131
142
|
const collectSourceMaps = async (v8list, options) => {
|
|
132
143
|
|
|
133
144
|
const smList = [];
|
|
@@ -166,7 +177,7 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
166
177
|
// check sourceMap only for js
|
|
167
178
|
if (type === 'js' && !sourceData.sourceMap) {
|
|
168
179
|
// from inline sync
|
|
169
|
-
const smc =
|
|
180
|
+
const smc = getInlineSourceMap(source);
|
|
170
181
|
if (smc) {
|
|
171
182
|
sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
|
|
172
183
|
smList.push({
|