monocart-coverage-reports 2.1.0 → 2.2.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 CHANGED
@@ -13,6 +13,7 @@
13
13
  * [Available Reports](#available-reports)
14
14
  * [Multiprocessing Support](#multiprocessing-support)
15
15
  * [onEnd Hook](#onend-hook)
16
+ * [`mcr` CLI](#mcr-cli)
16
17
  * [Compare Reports](#compare-reports)
17
18
  * [Compare Workflows](#compare-workflows)
18
19
  * [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
@@ -49,9 +50,13 @@ console.log(coverageResults.summary);
49
50
 
50
51
  ## Available Reports
51
52
  - `v8` (V8 data only)
52
- - [V8 html](https://cenfun.github.io/monocart-coverage-reports/v8) - Example for browser, build with webpack, and also [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
53
- - [V8 Node](https://cenfun.github.io/monocart-coverage-reports/v8-node-env) - Example for Node.js using env, and also [V8 API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp)
54
- - [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
53
+ - Test in browser
54
+ - Build with webpack: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
55
+ - Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
56
+ - Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/)
57
+ - Test in Node.js
58
+ - Collect with [env](https://cenfun.github.io/monocart-coverage-reports/v8-node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp)
59
+ - Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
55
60
 
56
61
  ![](test/v8.gif)
57
62
 
@@ -164,6 +169,43 @@ const coverageOptions = {
164
169
  }
165
170
  ```
166
171
 
172
+ ## `mcr` CLI
173
+ - Global mode
174
+ ```sh
175
+ npm i monocart-coverage-reports -g
176
+ mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
177
+ ```
178
+ - Current working directory mode
179
+ ```sh
180
+ npm i monocart-coverage-reports
181
+ npx mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
182
+ ```
183
+ - CLI Options
184
+ ```sh
185
+ Usage: mcr [options] <command>
186
+
187
+ CLI to generate coverage reports
188
+
189
+ Arguments:
190
+ command command to execute
191
+
192
+ Options:
193
+ -V, --version output the version number
194
+ -c, --config <path> config path for options
195
+ -o, --outputDir <dir> output dir for reports
196
+ -r, --reports <name[,name]> coverage reports to use
197
+ -n, --name <name> report name for title
198
+ --outputFile <path> output file for v8 report
199
+ --inline inline html for v8 report
200
+ --assetsPath <path> assets path if not inline
201
+ --lcov generate lcov.info file
202
+ -h, --help display help for command
203
+ ```
204
+ - Using configuration file for more options
205
+ ```sh
206
+ mcr "node ./test/test-node-env.js" -c test/cli-options.js
207
+ ```
208
+
167
209
  ## Compare Reports
168
210
  | | Istanbul | V8 | V8 to Istanbul |
169
211
  | :--------------| :------ | :------ | :---------------------- |
@@ -206,33 +248,39 @@ const coverageOptions = {
206
248
  - Rollup: build with [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
207
249
  - Vite: build with [options](https://vitejs.dev/config/build-options.html) `sourcemap: true` and `minify: false`
208
250
  - Browser (Chromium Only)
209
- > Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api), see [example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js)
251
+ > Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api), see [playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js) or [puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
210
252
  - Node.js
211
253
  > see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
212
254
 
213
255
 
214
256
  ## Node.js V8 Coverage Report for Server Side
215
- - Using Node.js env [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
257
+ Possible solutions:
258
+ - Node.js env [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
216
259
  - Before running your Node.js application, set env `NODE_V8_COVERAGE`=`dir`. After the application runs and exits, the coverage data will be saved to the `dir` directory in JSON file format
217
260
  - Read the json file(s) from the `dir` and generate coverage report
218
261
  - example:
219
262
  > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-env` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-node-report.js](./test/generate-node-report.js)
220
263
 
221
- - Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
264
+ - [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
222
265
  - Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
223
266
  - example:
224
267
  > cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
225
268
 
226
- - Using [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
269
+ - [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
227
270
  - Connecting to the V8 inspector and enable V8 coverage.
228
271
  - Taking coverage data and adding to the report after your application runs.
229
272
  - example: [./test/test-node-ins.js](./test/test-node-ins.js)
230
273
 
231
- - Using [CDP](https://chromedevtools.github.io/devtools-protocol/) API
274
+ - [CDP](https://chromedevtools.github.io/devtools-protocol/) API
232
275
  - Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
233
276
  - Collecting coverage data with CDP API.
234
277
  - example: [./test/test-node-cdp.js](./test/test-node-cdp.js)
235
278
 
279
+ - [Debugger](https://nodejs.org/en/guides/debugging-getting-started) + NODE_V8_COVERAGE + CDP + V8 API
280
+ > cross-env NODE_V8_COVERAGE=.temp/v8-coverage-koa node --inspect=9229 [./test/koa.js](./test/koa.js)
281
+ - example:
282
+ > node [./test/test-node-koa.js](./test/test-node-koa.js)
283
+
236
284
  ## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
237
285
  When V8 coverage data collected, it actually contains the data of all entry files, for example:
238
286
  ```
@@ -333,8 +381,8 @@ if (platform === 'linux') {
333
381
 
334
382
  ## Chromium Coverage API
335
383
  - [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
336
- - [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
337
384
  - [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
385
+ - [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
338
386
  - [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
339
387
 
340
388
  ## V8 Coverage Data Format
@@ -380,6 +428,25 @@ export type V8CoverageData = ScriptCoverage[];
380
428
  ```
381
429
  see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
382
430
 
431
+ ### Collect raw v8 coverage data with Puppeteer
432
+ ```js
433
+ await page.coverage.startJSCoverage({
434
+ // provide raw v8 coverage data
435
+ includeRawScriptCoverage: true
436
+ });
437
+
438
+ await page.goto(url);
439
+
440
+ const jsCoverage = await page.coverage.stopJSCoverage();
441
+ const rawV8CoverageData = jsCoverage.map((it) => {
442
+ // Convert to raw v8 coverage format
443
+ return {
444
+ source: it.text,
445
+ ... it.rawScriptCoverage
446
+ };
447
+ }
448
+ ```
449
+ see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
383
450
 
384
451
  ## Istanbul Introduction
385
452
  - [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
package/lib/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const EC = require('eight-colors');
6
+ const { fileURLToPath } = require('url');
7
+
8
+ const {
9
+ foregroundChild,
10
+ program
11
+ } = require('./packages/monocart-coverage-vendor.js');
12
+
13
+ const MCR = require('./index.js');
14
+ const Util = require('./utils/util.js');
15
+ const defaultOptions = require('./default/options.js');
16
+ const version = require('../package.json').version;
17
+
18
+ const cleanDir = (dir) => {
19
+ if (fs.existsSync(dir)) {
20
+ fs.rmSync(dir, {
21
+ recursive: true,
22
+ force: true
23
+ });
24
+ }
25
+ };
26
+
27
+ const initOptions = (cliOptions) => {
28
+ // merge all cli options
29
+ const configPath = cliOptions.config;
30
+ if (configPath) {
31
+ if (fs.existsSync(configPath)) {
32
+ const config = require(path.resolve(configPath));
33
+ Object.assign(cliOptions, config);
34
+ } else {
35
+ EC.logRed(`Not found config: ${configPath}`);
36
+ }
37
+ }
38
+
39
+ // init reports to list with `,`
40
+ const reports = cliOptions.reports;
41
+ if (reports) {
42
+ cliOptions.reports = reports.split(',');
43
+ }
44
+
45
+ // report options
46
+ const options = {
47
+ ... defaultOptions
48
+ };
49
+ Object.keys(cliOptions).forEach((k) => {
50
+ if (Util.hasOwn(options, k)) {
51
+ options[k] = cliOptions[k];
52
+ }
53
+ });
54
+
55
+ return options;
56
+ };
57
+
58
+ const executeCommand = async (command, cliOptions) => {
59
+
60
+ const options = initOptions(cliOptions);
61
+
62
+ console.log('Execute:', EC.cyan(command));
63
+ // console.log(options);
64
+
65
+ const nodeV8CoverageDir = path.resolve(options.outputDir, '.v8-coverage');
66
+ process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
67
+
68
+ cleanDir(nodeV8CoverageDir);
69
+
70
+ // console.log('nodeV8CoverageDir', nodeV8CoverageDir);
71
+
72
+ const coverageReport = MCR(options);
73
+ coverageReport.cleanCache();
74
+
75
+ // on start hook
76
+ const onStart = cliOptions.onStart;
77
+ if (typeof onStart === 'function') {
78
+ await onStart(coverageReport);
79
+ }
80
+
81
+ foregroundChild(command, {
82
+ shell: true
83
+ }, async () => {
84
+
85
+ if (!fs.existsSync(nodeV8CoverageDir)) {
86
+ EC.logRed(`Not found coverage data: ${Util.relativePath(nodeV8CoverageDir)}`);
87
+ return 1;
88
+ }
89
+
90
+ const files = fs.readdirSync(nodeV8CoverageDir);
91
+ for (const filename of files) {
92
+ const content = fs.readFileSync(path.resolve(nodeV8CoverageDir, filename)).toString('utf-8');
93
+ const json = JSON.parse(content);
94
+ let coverageList = json.result;
95
+
96
+ // filter node internal files
97
+ coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
98
+
99
+ if (!coverageList.length) {
100
+ continue;
101
+ }
102
+ // attached source content
103
+ coverageList.forEach((entry) => {
104
+ const filePath = fileURLToPath(entry.url);
105
+ if (fs.existsSync(filePath)) {
106
+ entry.source = fs.readFileSync(filePath).toString('utf8');
107
+ } else {
108
+ EC.logRed('not found file', Util.relativePath(filePath));
109
+ }
110
+ });
111
+
112
+ await coverageReport.add(coverageList);
113
+ }
114
+
115
+ await coverageReport.generate();
116
+
117
+ // remove nodeV8CoverageDir
118
+ if (options.logging !== 'debug') {
119
+ cleanDir(nodeV8CoverageDir);
120
+ }
121
+
122
+ return process.exitCode;
123
+ });
124
+
125
+ };
126
+
127
+ program
128
+ .name('mcr')
129
+ .description('CLI to generate coverage reports')
130
+ .version(version)
131
+ .argument('<command>', 'command to execute')
132
+ .option('-c, --config <path>', 'config path for options')
133
+ .option('-o, --outputDir <dir>', 'output dir for reports')
134
+ .option('-r, --reports <name[,name]>', 'coverage reports to use')
135
+ .option('-n, --name <name>', 'report name for title')
136
+
137
+ .option('--outputFile <path>', 'output file for v8 report')
138
+ .option('--inline', 'inline html for v8 report')
139
+ .option('--assetsPath <path>', 'assets path if not inline')
140
+
141
+ .option('--lcov', 'generate lcov.info file')
142
+
143
+ .action((command, cliOptions) => {
144
+ executeCommand(command, cliOptions);
145
+ });
146
+
147
+ program.parse();
@@ -170,7 +170,7 @@ const getJsAstInfo = (item, coverageList) => {
170
170
  try {
171
171
  ast = acorn.parse(source, options);
172
172
  } catch (e) {
173
- Util.logError(`failed to parse source file and fall back to loose: ${item.sourcePath} ${e.message}`);
173
+ Util.logInfo(`failed to parse source file: ${item.sourcePath} ${e.message}`);
174
174
  // https://github.com/acornjs/acorn/tree/master/acorn-loose
175
175
  // It is recommended to always try a parse with the regular acorn parser first,
176
176
  // and only fall back to this parser when that one finds syntax errors.
@@ -774,8 +774,9 @@ const collectOriginalList = (item, state, originalMap, options) => {
774
774
 
775
775
  // add file item
776
776
  const url = fileUrls[sourcePath] || sourcePath;
777
+
777
778
  // add dist for id
778
- const id = Util.calculateSha1(distFile + url + source);
779
+ const id = Util.calculateSha1(distFile + sourcePath + source);
779
780
 
780
781
  const sourceItem = {
781
782
  url,
@@ -975,22 +976,30 @@ const convertV8List = async (v8list, options) => {
975
976
  // merge state
976
977
  Object.assign(fileSources, state.fileSources);
977
978
  Object.assign(coverageData, state.coverageData);
978
- if (state.sourceList) {
979
+
980
+ if (Util.isList(state.sourceList)) {
979
981
  sourceList = sourceList.concat(state.sourceList);
980
982
  }
981
983
 
982
984
  }
983
985
 
984
- // dedupe
985
- dedupeV8List(v8list);
986
-
987
986
  // add all sources
988
987
  if (sourceList.length) {
989
988
  sourceList.forEach((item) => {
989
+
990
+ // exists same id, mark previous item as dedupe
991
+ const prevItem = v8list.find((it) => it.id === item.id);
992
+ if (prevItem) {
993
+ prevItem.dedupe = true;
994
+ }
995
+
990
996
  v8list.push(item);
991
997
  });
992
998
  }
993
999
 
1000
+ // dedupe
1001
+ dedupeV8List(v8list);
1002
+
994
1003
  return {
995
1004
  fileSources,
996
1005
  coverageData
package/lib/index.d.ts CHANGED
@@ -104,19 +104,16 @@ declare namespace MCR {
104
104
  total: number;
105
105
  pct: number | "";
106
106
  status: "low" | "medium" | "high" | "unknown";
107
- }
108
-
109
- export interface LinesSummary extends MetricsSummary {
110
- /** V8 only */
107
+ /** V8 lines only */
111
108
  blank?: number;
112
- /** V8 only */
109
+ /** V8 lines only */
113
110
  comment?: number;
114
111
  }
115
112
 
116
113
  export interface CoverageSummary {
117
114
  functions: MetricsSummary;
118
115
  branches: MetricsSummary;
119
- lines: LinesSummary;
116
+ lines: MetricsSummary;
120
117
  /** V8 only */
121
118
  bytes?: MetricsSummary;
122
119
  /** Istanbul only */
@@ -232,6 +229,11 @@ declare namespace MCR {
232
229
  cleanCache: () => boolean;
233
230
  }
234
231
 
232
+ export interface McrCliOptions extends CoverageReportOptions {
233
+ /** {function} onStart hook */
234
+ onStart?: (coverageReport: CoverageReport) => Promise<void>;
235
+ }
236
+
235
237
  }
236
238
 
237
239
  /** create coverage report */