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 CHANGED
@@ -6,15 +6,14 @@
6
6
  ![](https://img.shields.io/github/license/cenfun/monocart-coverage-reports)
7
7
  ![](https://img.shields.io/github/actions/workflow/status/cenfun/monocart-coverage-reports/static.yml)
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
- * [`mcr` CLI](#mcr-cli)
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
- ## `mcr` CLI
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
- - custom config file with `-c` or `--config`
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 [`mcr` CLI](#mcr-cli)
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 resolveEntrySource = (entry, sourceMapCache = {}) => {
20
- let source;
21
- const filePath = fileURLToPath(entry.url);
22
- const extname = path.extname(filePath);
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
- // not for typescript, but it could be jsx even it is `.js`
28
- if (source && !['.ts', '.tsx'].includes(extname)) {
29
- entry.source = source;
30
- return;
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
- const sourcemapData = sourceMapCache[entry.url];
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
- // Note: no runtime code in source map cache
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 collectCoverageData = (coverageList, entryFilter, sourceMapCache = {}) => {
62
-
63
- if (!Util.isList(coverageList)) {
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
- // filter node internal files
68
- coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
69
- coverageList = coverageList.filter(entryFilter);
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
- for (const entry of coverageList) {
76
- resolveEntrySource(entry, sourceMapCache);
77
- resolveEntrySourceMap(entry, sourceMapCache);
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
- return coverageList;
81
- };
62
+ // console.log(nodeOptions);
82
63
 
83
- const addCoverageData = async (coverageReport, nodeV8CoverageDir) => {
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
- let added = false;
82
+ // console.log(nodeOptions);
86
83
 
87
- const files = fs.readdirSync(nodeV8CoverageDir);
88
- if (!files.length) {
89
- return added;
90
- }
84
+ process.env.NODE_OPTIONS = nodeOptions.join(' ');
91
85
 
92
- // filter before adding
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
- return added;
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
- await addCoverageData(coverageReport, nodeV8CoverageDir);
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(`ERROR: ${err.message || err}`);
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 = convertSourceMap.fromSource(source);
180
+ const smc = getInlineSourceMap(source);
170
181
  if (smc) {
171
182
  sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
172
183
  smList.push({