monocart-coverage-reports 2.13.2 → 3.1.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 (69) hide show
  1. package/README.md +314 -116
  2. package/README.zh-Hans.md +315 -117
  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 +29 -8
  10. package/lib/converter/collect-source-maps.js +42 -21
  11. package/lib/converter/converter.js +124 -129
  12. package/lib/converter/css-coverage.js +76 -0
  13. package/lib/converter/fake-source-coverage.js +64 -0
  14. package/lib/converter/find-original-range.js +214 -6
  15. package/lib/converter/flatten-source-maps.js +2 -4
  16. package/lib/converter/ignore.js +2 -4
  17. package/lib/converter/info-branch.js +5 -3
  18. package/lib/converter/info-function.js +5 -3
  19. package/lib/converter/info-statement.js +5 -3
  20. package/lib/converter/range-index.js +78 -0
  21. package/lib/converter/source-range-merge.js +103 -0
  22. package/lib/converter/untested.js +30 -60
  23. package/lib/default/options.js +13 -1
  24. package/lib/generate.js +44 -26
  25. package/lib/index.d.ts +52 -4
  26. package/lib/index.js +35 -41
  27. package/lib/istanbul/istanbul-summary.js +2 -2
  28. package/lib/istanbul/istanbul.js +35 -13
  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 +8 -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 +26 -11
  55. package/lib/utils/util.js +67 -42
  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 +54 -42
  60. package/skills/monocart-coverage-reports/SKILL.md +106 -0
  61. package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
  62. package/skills/monocart-coverage-reports/references/cli.md +108 -0
  63. package/skills/monocart-coverage-reports/references/collection.md +154 -0
  64. package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
  65. package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
  66. package/skills/monocart-coverage-reports/references/repository-map.md +59 -0
  67. package/lib/index.mjs +0 -9
  68. package/lib/register/hooks.mjs +0 -3
  69. package/lib/register/register.mjs +0 -3
package/README.md CHANGED
@@ -13,6 +13,7 @@
13
13
  > A JavaScript code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
14
14
 
15
15
  * [Usage](#usage)
16
+ * [Best Practices](#best-practices)
16
17
  * [Options](#options)
17
18
  * [Available Reports](#available-reports)
18
19
  * [Compare Reports](#compare-reports)
@@ -31,6 +32,7 @@
31
32
  * [Multiprocessing Support](#multiprocessing-support)
32
33
  * [Command Line](#command-line)
33
34
  * [Config File](#config-file)
35
+ * [Run Tests with `mcr test`](#run-tests-with-mcr-test)
34
36
  * [Merge Coverage Reports](#merge-coverage-reports)
35
37
  - [Automatic Merging](#automatic-merging)
36
38
  - [Manual Merging](#manual-merging)
@@ -66,18 +68,21 @@
66
68
  * [Thanks](#thanks)
67
69
 
68
70
  ## Usage
69
- > [Node.js 22+](https://nodejs.org/) is recommended and is the actively maintained target.
71
+ > **MCR 3.x requires [Node.js >= 22.15.0](https://nodejs.org/)** for all APIs and CLI commands, including ordinary commands, `merge` and `test`.
72
+
73
+ **Upgrading from 2.x:** See the [3.x migration guide](migrate-to-3.md).
74
+
70
75
  - Install
71
76
  ```sh
72
77
  npm install monocart-coverage-reports
73
78
  ```
74
79
  - API
75
80
  ```js
76
- const MCR = require('monocart-coverage-reports');
81
+ import { MCR } from 'monocart-coverage-reports';
77
82
  const mcr = MCR({
78
83
  name: 'My Coverage Report - 2024-02-28',
79
84
  outputDir: './coverage-reports',
80
- reports: ["v8", "console-details"],
85
+ reports: ['v8', 'console-details'],
81
86
  cleanCache: true
82
87
  });
83
88
  await mcr.add(coverageData);
@@ -99,6 +104,35 @@ mcr node my-app.js -r v8,console-details
99
104
  ```
100
105
  For more information, see [Command Line](#command-line)
101
106
 
107
+ ## Best Practices
108
+
109
+ For new test setups, prefer native coverage collection without an instrumentation step:
110
+
111
+ | Target | Recommended setup | Responsibility |
112
+ | --- | --- | --- |
113
+ | Node.js | **Node.js native test runner + `mcr test`** | `node:test` runs assertions; MCR automatically collects native V8 JavaScript coverage and generates reports. |
114
+ | Browser | **Playwright Test + MCR** | Playwright runs assertions; automatic fixtures collect Chromium JS/CSS coverage; MCR merges it and generates reports. |
115
+ | Browser with integrated test reporting | **Playwright Test + [monocart-reporter](https://github.com/cenfun/monocart-reporter#code-coverage-report)** | The reporter already integrates MCR; configure its coverage option and submit coverage from fixtures for unified test and coverage reporting. |
116
+
117
+ For detailed setup instructions and examples, see the [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md).
118
+
119
+ ## AI Agent Skill
120
+
121
+ For AI-assisted development in a source checkout, start with [AGENTS.md](https://github.com/cenfun/monocart-coverage-reports/blob/main/AGENTS.md). It describes the source layout, development commands, validation, and safety boundaries. `.github/copilot-instructions.md` provides a self-contained summary of the core rules for GitHub Copilot. These checkout-only guides are not included in the npm package and are separate from the reusable integration skill below.
122
+
123
+ The reusable [Monocart Coverage Reports skill](skills/monocart-coverage-reports/SKILL.md) helps AI coding agents collect coverage, configure the CLI, debug sourcemaps and filters, merge raw reports, and enforce CI thresholds. It uses a main `SKILL.md` with task-specific references loaded on demand, targeting MCR 3.x only and checking its runtime requirements.
124
+
125
+ The npm package includes `skills/monocart-coverage-reports/`. Copy this **entire directory**, including `references/`, into your agent's supported skills location, or ask the agent to read `SKILL.md` directly. Installing MCR does not automatically register the skill or modify agent settings. The instructions are in English; requests and responses can use any language.
126
+
127
+ From the host project root, locate the installed skill and package manifest (older releases may not include the skill):
128
+
129
+ ```sh
130
+ node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
131
+ node -p "require.resolve('monocart-coverage-reports/package.json')"
132
+ ```
133
+
134
+ Read the resolved skill, then follow its relative reference links. Use the installed package's runtime, API, and version requirements rather than assuming all examples apply to older releases. These commands resolve files; they do not import Markdown as JavaScript.
135
+
102
136
  ## Options
103
137
  - Default options: [lib/default/options.js](./lib/default/options.js)
104
138
  - Options declaration see `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
@@ -115,6 +149,7 @@ For more information, see [Command Line](#command-line)
115
149
  | `dataDir` | `string` | `null` | Coverage data directory loaded automatically in `generate()` — alternative to `addFromDir()`. |
116
150
  | `entryFilter` | `string \| object \| function` | `null` | (V8 only) Filter V8 entries by url. See [Filtering Results](#filtering-results). |
117
151
  | `sourceFilter` | `string \| object \| function` | `null` | (V8 only) Filter sources unpacked from sourcemaps. |
152
+ | `outputFilter` | `string \| object \| function` | `null` | (V8 only) Filter final files by `sourcePath` after sourcemap conversion. See [Filtering Results](#filtering-results). |
118
153
  | `filter` | `string \| object \| function` | `null` | Combined filter replacing both `entryFilter` and `sourceFilter`. |
119
154
  | `sourcePath` | `object \| function` | `null` | Rewrite source paths; see [Resolve `sourcePath`](#resolve-sourcepath-for-the-source-files). |
120
155
  | `all` | `string \| string[] \| object` | `null` | Include untested files as empty coverage. See [Adding Empty Coverage](#adding-empty-coverage-for-untested-files). |
@@ -127,7 +162,7 @@ For more information, see [Command Line](#command-line)
127
162
  | `watermarks` | `[number, number] \| object` | `[50, 80]` | Low/high thresholds (percent). Accepts per-metric object like `{ bytes:[50,80], lines:[50,80] }`. |
128
163
  | `clean` | `boolean` | `true` | Clean previous reports in `outputDir` before generating (the `.cache` dir is preserved unless `cleanCache` is also `true`). |
129
164
  | `cleanCache` | `boolean` | `false` | Clean the cache dir on start. When both `clean` and `cleanCache` are `false`, cached coverage data from previous runs is preserved and can be reused across processes. |
130
- | `gc` | `number` | `null` | Memory threshold in MB; force GC at critical stages when RSS exceeds it. Useful for very large data sets, see [JavaScript heap out of memory](#javascript-heap-out-of-memory). |
165
+ | `gc` | `number` | `null` | Memory threshold in MB; force GC at critical stages when JavaScript heap usage (`heapUsed`) exceeds it. Useful for very large data sets, see [JavaScript heap out of memory](#javascript-heap-out-of-memory). |
131
166
  | `sourceMap` | `boolean` | `false` | Save source/sourcemap files to cache for debugging (requires `logging: "debug"`). |
132
167
  | `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | Custom sourcemap loader (e.g. from a build cache). Call `defaultResolver(url)` to fall back. |
133
168
  | `onEntry` | `(entry) => void \| Promise<void>` | `null` | (V8 only) Per-entry hook; see [Hooks](#hooks). |
@@ -202,7 +237,7 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
202
237
 
203
238
  - Custom Reporter
204
239
  ```js
205
- {
240
+ const coverageOptions = {
206
241
  reports: [
207
242
  [path.resolve('./test/custom-istanbul-reporter.js'), {
208
243
  type: 'istanbul',
@@ -216,21 +251,23 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
216
251
  type: 'both'
217
252
  }]
218
253
  ]
219
- }
254
+ };
220
255
  ```
221
256
  - Istanbul custom reporter
222
257
  > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
223
258
  - V8 custom reporter
224
259
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
225
260
 
226
- The `type` option determines which coverage format the reporter receives:
227
- - `'istanbul'` — the reporter receives Istanbul-format coverage data (`coverageResults.istanbulData`).
228
- - `'v8'` — the reporter receives native V8-format coverage data (`coverageResults.v8Data`).
229
- - `'both'` — the reporter receives both formats, accessible via `coverageResults.v8Data` and `coverageResults.istanbulData`.
261
+ The `type` option selects the reporter interface and when it runs:
262
+ - `'istanbul'` — uses the Istanbul reporter interface: `new Reporter(reportOptions)` followed by `execute(context)`. The context provides Istanbul report trees and source lookup. V8 input is converted to Istanbul coverage first.
263
+ - `'v8'` (default) — uses `new Reporter(reportOptions, globalOptions)` followed by `await generate(reportData)` for V8 input. `reportData` is the V8 coverage result, with `type`, `summary` and `files`; each file contains V8 ranges and summaries.
264
+ - `'both'` — uses the same `generate(reportData)` interface for either V8 or Istanbul input. Check `reportData.type` to handle the corresponding result structure. This does **not** supply both raw formats together; there are no `v8Data` or `istanbulData` properties.
265
+
266
+ Reporters are loaded with native ESM `import()`. Use `export default Reporter` for ESM; Node's built-in interoperability also accepts CommonJS `module.exports = Reporter` (use `.cjs` inside an ESM project). Local paths must include the file extension; extensionless paths and directory entries are not resolved. No separate CommonJS loader is provided. Reporter loading and initialization errors reject report generation instead of silently skipping the reporter.
230
267
 
231
268
  ### Multiple Reports:
232
269
  ```js
233
- const MCR = require('monocart-coverage-reports');
270
+ import { MCR } from 'monocart-coverage-reports';
234
271
  const coverageOptions = {
235
272
  outputDir: './coverage-reports',
236
273
  reports: [
@@ -247,15 +284,15 @@ const coverageOptions = {
247
284
 
248
285
  // custom reports
249
286
  // Specify reporter name with the NPM package
250
- ["custom-reporter-1"],
251
- ["custom-reporter-2", {
252
- type: "istanbul",
253
- key: "value"
287
+ ['custom-reporter-1'],
288
+ ['custom-reporter-2', {
289
+ type: 'istanbul',
290
+ key: 'value'
254
291
  }],
255
292
  // Specify reporter name with local path
256
293
  ['/absolute/path/to/custom-reporter.js']
257
294
  ]
258
- }
295
+ };
259
296
  const mcr = MCR(coverageOptions);
260
297
  ```
261
298
 
@@ -322,7 +359,7 @@ await Promise.all([
322
359
  })
323
360
  ]);
324
361
 
325
- await page.goto("your page url");
362
+ await page.goto('your page url');
326
363
 
327
364
  const [jsCoverage, cssCoverage] = await Promise.all([
328
365
  page.coverage.stopJSCoverage(),
@@ -332,11 +369,18 @@ const [jsCoverage, cssCoverage] = await Promise.all([
332
369
  const coverageData = [... jsCoverage, ... cssCoverage];
333
370
 
334
371
  ```
335
- Collect coverage with `@playwright/test` [`Automatic fixtures`](https://playwright.dev/docs/test-fixtures#automatic-fixtures), see example: [fixtures.ts](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.ts)
372
+ For Playwright Test automatic fixtures, see [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js) in the complete [playwright-coverage example](https://github.com/cenfun/playwright-coverage). The [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md#complete-project-example) explains the integration.
336
373
  For more examples, see [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
337
374
 
338
375
 
339
376
  ### Collecting Raw V8 Coverage Data with Puppeteer
377
+
378
+ > Depending on your package manager, Puppeteer may not download a browser during installation. If no browser is available, install one manually:
379
+ >
380
+ > ```sh
381
+ > npx puppeteer browsers install chrome
382
+ > ```
383
+
340
384
  ```js
341
385
  await Promise.all([
342
386
  page.coverage.startJSCoverage({
@@ -350,7 +394,7 @@ await Promise.all([
350
394
  })
351
395
  ]);
352
396
 
353
- await page.goto("your page url");
397
+ await page.goto('your page url');
354
398
 
355
399
  const [jsCoverage, cssCoverage] = await Promise.all([
356
400
  page.coverage.stopJSCoverage(),
@@ -403,29 +447,34 @@ Possible solutions:
403
447
  - see [Command Line](#command-line)
404
448
 
405
449
  ### Collecting V8 Coverage Data with `CDPClient` API
450
+
451
+ `timeout` controls HTTP discovery and WebSocket connection deadlines (default: 10000ms). `requestTimeout` controls each CDP command on built-in WebSocket sessions (default: 30000ms). Set either to `0` to disable its deadline. Custom Playwright/Puppeteer sessions manage their own command timeouts. For custom sockets, use `new MCR.WSSession(ws, { timeout: 30000 })`.
452
+
406
453
  - `CDPClient` available APIs
407
- ```js
408
- startJSCoverage: () => Promise<void>;
409
- stopJSCoverage: () => Promise<V8CoverageEntry[]>;
454
+ ```ts
455
+ interface CoverageClient {
456
+ startJSCoverage: () => Promise<void>;
457
+ stopJSCoverage: () => Promise<V8CoverageEntry[]>;
410
458
 
411
- startCSSCoverage: () => Promise<void>;
412
- stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
459
+ startCSSCoverage: () => Promise<void>;
460
+ stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
413
461
 
414
- /** start both js and css coverage */
415
- startCoverage: () => Promise<void>;
416
- /** stop and return both js and css coverage */
417
- stopCoverage: () => Promise<V8CoverageEntry[]>;
462
+ /** start both js and css coverage */
463
+ startCoverage: () => Promise<void>;
464
+ /** stop and return both js and css coverage */
465
+ stopCoverage: () => Promise<V8CoverageEntry[]>;
418
466
 
419
- /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
420
- writeCoverage: () => Promise<string>;
467
+ /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
468
+ writeCoverage: () => Promise<string>;
421
469
 
422
- /** get istanbul coverage data */
423
- getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
470
+ /** get istanbul coverage data */
471
+ getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
472
+ }
424
473
  ```
425
474
 
426
475
  - Work with node debugger port `--inspect=9229` or browser debugging port `--remote-debugging-port=9229`
427
476
  ```js
428
- const MCR = require('monocart-coverage-reports');
477
+ import { MCR } from 'monocart-coverage-reports';
429
478
  const client = await MCR.CDPClient({
430
479
  port: 9229
431
480
  });
@@ -436,8 +485,8 @@ const coverageData = await client.stopJSCoverage();
436
485
 
437
486
  - Work with [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
438
487
  ```js
439
- const { chromium } = require('playwright');
440
- const MCR = require('monocart-coverage-reports');
488
+ import { chromium } from 'playwright';
489
+ import { MCR } from 'monocart-coverage-reports';
441
490
  const browser = await chromium.launch();
442
491
  const page = await browser.newPage();
443
492
  const session = await page.context().newCDPSession(page);
@@ -447,14 +496,14 @@ const client = await MCR.CDPClient({
447
496
  // both js and css coverage
448
497
  await client.startCoverage();
449
498
  // run your test page here
450
- await page.goto("your page url");
499
+ await page.goto('your page url');
451
500
  const coverageData = await client.stopCoverage();
452
501
  ```
453
502
 
454
503
  - Work with [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
455
504
  ```js
456
- const puppeteer = require('puppeteer');
457
- const MCR = require('monocart-coverage-reports');
505
+ import puppeteer from 'puppeteer';
506
+ import { MCR } from 'monocart-coverage-reports';
458
507
  const browser = await puppeteer.launch({});
459
508
  const page = await browser.newPage();
460
509
  const session = await page.target().createCDPSession();
@@ -464,20 +513,20 @@ const client = await MCR.CDPClient({
464
513
  // both js and css coverage
465
514
  await client.startCoverage();
466
515
  // run your test page here
467
- await page.goto("your page url");
516
+ await page.goto('your page url');
468
517
  const coverageData = await client.stopCoverage();
469
518
  ```
470
519
 
471
520
  - Work with [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (Chrome/Edge Browser)
472
521
  ```js
473
- const { Builder, Browser } = require('selenium-webdriver');
474
- const MCR = require('monocart-coverage-reports');
522
+ import { Builder, Browser } from 'selenium-webdriver';
523
+ import { MCR } from 'monocart-coverage-reports';
475
524
  const driver = await new Builder().forBrowser(Browser.CHROME).build();
476
525
  const pageCdpConnection = await driver.createCDPConnection('page');
477
526
  const session = new MCR.WSSession(pageCdpConnection._wsConnection);
478
527
  const client = await MCR.CDPClient({
479
528
  session
480
- })
529
+ });
481
530
  ```
482
531
 
483
532
  ### V8 Coverage Data API
@@ -485,7 +534,7 @@ const client = await MCR.CDPClient({
485
534
  - [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
486
535
  - [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
487
536
  - [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
488
- ```js
537
+ ```ts
489
538
  // Coverage data for a source range.
490
539
  export interface CoverageRange {
491
540
  // JavaScript script source offset for the range start.
@@ -574,15 +623,15 @@ We can use `sourceFilter` to filter the source files. For example, we should rem
574
623
  For example:
575
624
  ```js
576
625
  const coverageOptions = {
577
- entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
626
+ entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
578
627
  sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
579
628
  };
580
629
  ```
581
630
  Or using [`minimatch`](https://github.com/isaacs/minimatch) pattern:
582
631
  ```js
583
632
  const coverageOptions = {
584
- entryFilter: "**/main.js",
585
- sourceFilter: "**/src/**"
633
+ entryFilter: '**/main.js',
634
+ sourceFilter: '**/src/**'
586
635
  };
587
636
  ```
588
637
  Support multiple patterns:
@@ -607,10 +656,17 @@ Note, those patterns will be transformed to a function, and the order of the pat
607
656
  ```js
608
657
  const coverageOptions = {
609
658
  entryFilter: (entry) => {
610
- if (minimatch(entry.url, '**/node_modules/**')) { return false; }
611
- if (minimatch(entry.url, '**/vendor.js')) { return false; }
612
- if (minimatch(entry.url, '**/src/**')) { return true; }
613
- return false; // else unmatched
659
+ if (minimatch(entry.url, '**/node_modules/**')) {
660
+ return false;
661
+ }
662
+ if (minimatch(entry.url, '**/vendor.js')) {
663
+ return false;
664
+ }
665
+ if (minimatch(entry.url, '**/src/**')) {
666
+ return true;
667
+ }
668
+ // else unmatched
669
+ return false;
614
670
  }
615
671
  };
616
672
  ```
@@ -629,6 +685,42 @@ const coverageOptions = {
629
685
  };
630
686
  ```
631
687
 
688
+ ### Using `outputFilter` to filter final files
689
+ > **Version requirement:** `outputFilter` is introduced in **v3.0.0**. It is not supported in v2.13.x or earlier.
690
+
691
+ **Choose one filtering approach.** `outputFilter` and the existing `entryFilter` + `sourceFilter` approach serve the same purpose: deciding which files appear in the report. They are alternatives, not additional filters you need to configure together. The combined `filter` option is shorthand for `entryFilter` + `sourceFilter`; it does not configure `outputFilter`.
692
+
693
+ | Approach | When and what it filters | Advantages | Trade-offs |
694
+ | :-- | :-- | :-- | :-- |
695
+ | `entryFilter` + `sourceFilter` (or `filter`) | Filters inputs during discovery: `entryFilter` matches entry URLs, and `sourceFilter` matches source paths as sourcemaps are unpacked. | Excludes unwanted files earlier, avoiding unnecessary downstream processing. Prefer this approach for better performance, especially with large projects or bundles. | Requires understanding entry files versus sourcemap sources and configuring both stages. Excluding a bundle via `entryFilter` also removes any desired sources inside it. |
696
+ | `outputFilter` | Filters final files by `sourcePath` after sourcemap conversion, merging and adding untested files (`all`), immediately before report generation. | Simpler configuration: use one filter against the final file list, without distinguishing entries from unpacked sources. | Unwanted files are still discovered and processed before being discarded. This does not save their earlier processing cost and can increase generation time and memory usage, especially when many files will ultimately be excluded. |
697
+
698
+ **Recommendation:** use input filtering (`entryFilter` + `sourceFilter`, or `filter`) when performance matters. Choose `outputFilter` when simpler configuration and filtering the final results are more important, and the extra processing cost is acceptable. If switching to `outputFilter`, remove the old input filters for the same scope: output filtering cannot restore files already excluded during input filtering.
699
+
700
+ For V8 input, `outputFilter` filters both V8 reports and converted Istanbul reports. It defaults to `null` (disabled).
701
+
702
+ Like `entryFilter` and `sourceFilter`, it supports strings and ordered pattern objects (the first matching pattern wins; unmatched files are excluded). Patterns match the final `sourcePath`, including any `sourcePath` rewrites. A function receives the final file entry, not a path string.
703
+
704
+ ```js
705
+ const coverageOptions = {
706
+ outputFilter: {
707
+ '**/node_modules/**': false,
708
+ '**/src/**': true
709
+ }
710
+ };
711
+ // Alternatively:
712
+ // outputFilter: '**/src/**'
713
+ // outputFilter: (entry) => entry.sourcePath.startsWith('src/')
714
+ ```
715
+
716
+ CLI examples (quote patterns to prevent shell expansion):
717
+ ```sh
718
+ mcr merge --inputDir ./raw --outputFilter "**/src/**"
719
+ mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"
720
+ ```
721
+
722
+ `outputFilter` does not change raw coverage exports and does not apply to Istanbul input.
723
+
632
724
  ## Resolve `sourcePath` for the Source Files
633
725
  If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to resolve a custom path.
634
726
  For example, we have tested multiple dist files, which contain some common files. We hope to merge the coverage of the same files, so we need to unify the `sourcePath` in order to be able to merge the coverage data.
@@ -646,38 +738,40 @@ const coverageOptions = {
646
738
  }
647
739
  };
648
740
  ```
741
+ Built-in report path normalization is best-effort; it cannot fully reconcile every nonstandard, virtual, or foreign-platform path. Use `sourcePath` to correct these special cases rather than relying on automatic inference. The callback receives the normalized `filePath`; for V8 entries and sourcemap sources, `info.url` provides the original URL before report path normalization (including the sourcemap's `sourceRoot`, when present). This rewrites report paths, not the filesystem paths used to load missing source content. When `sourcesContent` is missing, local sources are loaded relative to the external map's URL (or the generated file for inline maps), with `sourceRoot` applied as a directory.
742
+
649
743
  It also supports simple key/value replacement:
650
744
  ```js
651
745
  const coverageOptions = {
652
746
  sourcePath: {
653
- 'my-dist-file1/': '',
747
+ 'my-dist-file1/': '',
654
748
  'my-dist-file2/': ''
655
749
  }
656
750
  };
657
751
  ```
658
752
  Normalize the full path of the file:
659
753
  ```js
660
- const path = require("path")
754
+ import path from 'node:path';
661
755
 
662
756
  // MCR coverage options
663
757
  const coverageOptions = {
664
- sourcePath: (filePath, info)=> {
758
+ sourcePath: (filePath, info) => {
665
759
  if (!filePath.includes('/') && info.distFile) {
666
760
  return `${path.dirname(info.distFile)}/${filePath}`;
667
761
  }
668
762
  return filePath;
669
763
  }
670
- }
764
+ };
671
765
  ```
672
766
 
673
767
  ## Adding Empty Coverage for Untested Files
674
- By default the untested files will not be included in the coverage report. Use `all` to include them with 0% coverage. This also works when no coverage data has been collected: a V8 report is generated from the matching untested files.
768
+ Untested files are excluded by default. Use `all` to include them with 0% coverage, even when no coverage data was collected. For the same original source, real V8 coverage takes precedence over empty entries.
675
769
  ```js
676
770
  const coverageOptions = {
677
- all: './src',
771
+ all: './src'
678
772
 
679
773
  // or multiple dirs
680
- all: ['./src', './lib'],
774
+ // all: ['./src', './lib']
681
775
  };
682
776
  ```
683
777
  The untested files will apply to the `sourceFilter`. And it also supports additional `filter` (return the file type for js or css coverage):
@@ -690,30 +784,29 @@ const coverageOptions = {
690
784
  '**/ignored-*.js': false,
691
785
  '**/*.html': false,
692
786
  // empty css coverage
693
- '**/*.scss': "css",
787
+ '**/*.scss': 'css',
694
788
  '**/*': true
695
789
  }
696
790
  }
697
791
  };
698
792
  ```
699
- We can also compile these untested files, such as .ts, .jsx, or .vue, etc., so that they can be analyzed by the default AST parser, thus get more coverage metric data.
793
+ Untested files can be transformed into JavaScript for AST analysis. This example handles **TypeScript without JSX** and assumes `@swc/core` is installed. Use an appropriate compiler/parser for JSX/TSX or Vue; this is not a universal transformer. Preserve the full filename to keep same-basename files in different directories distinct, and parse SWC's JSON map before assigning it.
700
794
  ```js
701
- const path = require("path");
702
- const swc = require("@swc/core");
795
+ import { fileURLToPath } from 'node:url';
796
+ import { transform } from '@swc/core';
703
797
  const coverageOptions = {
704
798
  all: {
705
799
  dir: ['./src'],
800
+ filter: '**/*.ts',
706
801
  transformer: async (entry) => {
707
- const { code, map } = await swc.transform(entry.source, {
708
- filename: path.basename(entry.url),
802
+ const { code, map } = await transform(entry.source, {
803
+ filename: fileURLToPath(entry.url),
709
804
  sourceMaps: true,
710
805
  isModule: true,
711
806
  jsc: {
712
807
  parser: {
713
- syntax: "typescript",
714
- jsx: true
715
- },
716
- transform: {}
808
+ syntax: 'typescript'
809
+ }
717
810
  }
718
811
  });
719
812
  entry.source = code;
@@ -726,36 +819,45 @@ const coverageOptions = {
726
819
  ## Hooks
727
820
 
728
821
  ### `onEnd` — after the report is generated
729
- Typical use case: enforce coverage thresholds.
822
+ Typical use case: enforce coverage thresholds. Throw on failure so API callers can handle the error and the CLI exits nonzero, without forcing `process.exit()` before cleanup finishes.
730
823
  ```js
731
- const EC = require('eight-colors');
824
+ const getCoverageError = (metric, summary, minimum) => {
825
+ if (!summary || !(summary.total > 0)) {
826
+ return `${metric}: missing or empty coverage`;
827
+ }
828
+ const { pct } = summary;
829
+ if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
830
+ return `${metric}: ${pct}%, required ${minimum}%`;
831
+ }
832
+ return '';
833
+ };
732
834
  const coverageOptions = {
733
835
  name: 'My Coverage Report',
734
836
  outputDir: './coverage-reports',
735
837
  onEnd: (coverageResults) => {
838
+ if (!coverageResults) {
839
+ throw new Error('No coverage results');
840
+ }
736
841
  const thresholds = {
737
842
  bytes: 80,
738
843
  lines: 60
739
844
  };
740
- console.log('check thresholds ...', thresholds);
741
845
  const errors = [];
742
- const { summary } = coverageResults;
743
- Object.keys(thresholds).forEach((k) => {
744
- const pct = summary[k].pct;
745
- if (pct < thresholds[k]) {
746
- errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
846
+ for (const [metric, minimum] of Object.entries(thresholds)) {
847
+ const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
848
+ if (error) {
849
+ errors.push(error);
747
850
  }
748
- });
851
+ }
749
852
  if (errors.length) {
750
- const errMsg = errors.join('\n');
751
- console.log(EC.red(errMsg));
752
- // throw new Error(errMsg);
753
- // process.exit(1);
853
+ throw new Error(errors.join('\n'));
754
854
  }
755
855
  }
756
- }
856
+ };
757
857
  ```
758
858
 
859
+ This policy rejects missing/zero-total metrics; adapt it explicitly if a metric is legitimately empty. With no usable input, `generate()` may return `undefined` without calling `onEnd`. API callers must check that result; ordinary CLI workflows should use a fresh output directory and verify a newly generated nonempty report. See the [strict CI recipes](skills/monocart-coverage-reports/references/reports-and-ci.md#reject-stale-reports-and-missing-collection-in-ci).
860
+
759
861
  ### `onEntry` — before each V8 entry is processed (V8 only)
760
862
  Use this to mutate the entry (for example, transform non-standard sources into valid ECMAScript before the AST parser runs). See [Unparsable source](#unparsable-source).
761
863
  ```js
@@ -772,7 +874,7 @@ Only available when using `mcr <command>`:
772
874
  - `onReady(coverageReport, nodeV8CoverageDir, subprocess)` — runs after the child exits, before MCR reads coverage from `nodeV8CoverageDir`. Useful when the child hasn't finished flushing coverage data and needs to be waited on.
773
875
  ```js
774
876
  // mcr.config.js
775
- module.exports = {
877
+ export default {
776
878
  onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
777
879
  // wait for something, or pre-process the raw files
778
880
  }
@@ -780,6 +882,8 @@ module.exports = {
780
882
  ```
781
883
 
782
884
  ## Ignoring Uncovered Codes
885
+ <!-- readme-lint-disable -->
886
+
783
887
  To ignore codes, use the special comment which starts with `v8 ignore `:
784
888
  - Ignoring all until stop
785
889
  ```js
@@ -814,12 +918,14 @@ function uncovered() {
814
918
  ```
815
919
  - To disable comment-based ignoring entirely, set `v8Ignore: false`.
816
920
 
921
+ <!-- readme-lint-enable -->
922
+
817
923
  ## Multiprocessing Support
818
924
  > The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
819
925
  - Main process, before the start of testing
820
926
  ```js
821
- const MCR = require('monocart-coverage-reports');
822
- const coverageOptions = require('path-to/same-options.js');
927
+ import { MCR } from 'monocart-coverage-reports';
928
+ import coverageOptions from 'path-to/same-options.js';
823
929
  const mcr = MCR(coverageOptions);
824
930
  // clean previous cache before the start of testing
825
931
  // unless the running environment is new and no cache
@@ -828,16 +934,16 @@ mcr.cleanCache();
828
934
 
829
935
  - Sub process 1, testing stage 1
830
936
  ```js
831
- const MCR = require('monocart-coverage-reports');
832
- const coverageOptions = require('path-to/same-options.js');
937
+ import { MCR } from 'monocart-coverage-reports';
938
+ import coverageOptions from 'path-to/same-options.js';
833
939
  const mcr = MCR(coverageOptions);
834
940
  await mcr.add(coverageData1);
835
941
  ```
836
942
 
837
943
  - Sub process 2, testing stage 2
838
944
  ```js
839
- const MCR = require('monocart-coverage-reports');
840
- const coverageOptions = require('path-to/same-options.js');
945
+ import { MCR } from 'monocart-coverage-reports';
946
+ import coverageOptions from 'path-to/same-options.js';
841
947
  const mcr = MCR(coverageOptions);
842
948
  await mcr.add(coverageData2);
843
949
  ```
@@ -845,8 +951,8 @@ await mcr.add(coverageData2);
845
951
  - Main process, after the completion of testing
846
952
  ```js
847
953
  // generate coverage reports after the completion of testing
848
- const MCR = require('monocart-coverage-reports');
849
- const coverageOptions = require('path-to/same-options.js');
954
+ import { MCR } from 'monocart-coverage-reports';
955
+ import coverageOptions from 'path-to/same-options.js';
850
956
  const mcr = MCR(coverageOptions);
851
957
  await mcr.generate();
852
958
  ```
@@ -868,14 +974,17 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
868
974
 
869
975
  - CLI Options
870
976
 
977
+ Commands are executed with Execa's default options, without enabling shell mode. Pass the executable and its arguments separately, for example `mcr node "./my app.js"`; do not quote an entire command as a single argument. Argument boundaries are preserved: MCR does not expand environment variables or globs, or interpret pipes, redirections, `&&` or other shell syntax. Move command sequences into a script instead. Use `mcr [options] -- executable ...args` to keep child options separate from MCR options. Execa handles platform-specific command execution, including escaped batch-file invocation through `cmd.exe` when needed on Windows.
978
+
871
979
  The options below are only available when using the CLI (`mcr <command>`):
872
980
 
873
981
  | Option | Type | Default | Description |
874
982
  | :-- | :-- | :-- | :-- |
875
983
  | `-c, --config` | `string` | — | Custom config file path |
876
- | `--import` | `string` | `null` | Preload ESM module via `NODE_OPTIONS`, e.g. `tsx` for TypeScript. Requires Node.js `>= 18.19` |
877
- | `--require` | `string` | `null` | Preload CJS module via `NODE_OPTIONS` |
878
- | `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). Requires Node.js `>= 20.6` |
984
+ | `--import` | `string` | `null` | Preload ESM module via `NODE_OPTIONS`, e.g. `tsx` for TypeScript. |
985
+ | `--require` | `string` | `null` | Deprecated alias for `--import`; uses ESM loading, not CommonJS resolution |
986
+ | `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). |
987
+ | `test` (config only) | `string \| string[] \| null` | `null` | Default test file paths or glob patterns accepted by `node --test`, passed unchanged when `mcr test` has no positional targets; ignored by other commands and the API. Requires Node.js `>= 22.15.0` |
879
988
  | `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
880
989
  | `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
881
990
 
@@ -886,16 +995,18 @@ For other options (e.g. `--name`, `--reports`, `--outputDir`, `--lcov`, etc.), s
886
995
  mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
887
996
  ```
888
997
 
889
- - `--import <module>` / `--require <module>`: forward a preload module to the child process via `NODE_OPTIONS`. Use this for TypeScript/JSX runtimes (`tsx`, `ts-node`, etc.) and also when loading a `mcr.config.ts`. Requires Node.js `>= 18.19`.
998
+ - `--import <module>` / `--require <module>`: forward a preload module to the child process via `NODE_OPTIONS`. Use this for TypeScript/JSX runtimes (`tsx`, `ts-node`, etc.) and also when loading a `mcr.config.ts`. Available on all supported Node.js versions. Local paths are relative to the current working directory and must include the file extension; paths containing spaces are supported. Package names use native ESM resolution from MCR's module location. Both flags use `import()` and forward `--import` to Node; `--require` is a deprecated alias, not a CommonJS loader. CJS modules work only through Node's built-in import interoperability.
890
999
  ```sh
891
1000
  mcr --import tsx node ./test.ts
892
1001
  ```
893
1002
 
894
- - `--env [path]`: load environment variables from a dotenv file before the child starts (defaults to `.env`). Requires `process.loadEnvFile`, added in Node.js `20.6`.
1003
+ - `--env [path]`: load environment variables from a dotenv file before the child starts (defaults to `.env`). Uses `process.loadEnvFile`, available on all supported Node.js versions.
895
1004
  ```sh
896
1005
  mcr --env .env.test node ./test.js
897
1006
  ```
898
1007
 
1008
+ - `test`: run Node.js tests and generate coverage reports. See [Run Tests with `mcr test`](#run-tests-with-mcr-test).
1009
+
899
1010
  - `merge`: merge raw coverage data from `inputDir`(s) and generate merged reports directly, without running a child process.
900
1011
  ```sh
901
1012
  mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
@@ -908,6 +1019,8 @@ See [Merge Coverage Reports](#merge-coverage-reports).
908
1019
  - [AVA](#ava)
909
1020
 
910
1021
  ## Config File
1022
+ In MCR 3.x, `loadConfig()` throws if an explicitly requested config does not exist or a discovered config fails to load (including JSON parsing, module syntax and startup errors). All CLI commands exit with a non-zero status for these errors. No `strict` option is needed. If no custom config is requested and no default config is found, execution continues with existing options.
1023
+
911
1024
  Loading config file by priority:
912
1025
  - Custom config file:
913
1026
  - CLI: `mcr --config <my-config-file-path>`
@@ -923,6 +1036,59 @@ mcr --import tsx -c mcr.config.ts node ./test.js
923
1036
 
924
1037
  > If no custom path is given, MCR walks up from the current working directory to find the default config file (via [`find-up`](https://github.com/sindresorhus/find-up)). Files outside the search path will be ignored.
925
1038
 
1039
+ ## Run Tests with `mcr test`
1040
+
1041
+ **New in MCR 3.x:** Run tests with the built-in [Node.js test runner](https://nodejs.org/docs/latest/api/test.html) and automatically generate coverage reports with one command. Requires **Node.js >= 22.15.0**.
1042
+
1043
+ Install MCR and run your test files directly — no config file is required:
1044
+
1045
+ ```sh
1046
+ npm install --save-dev monocart-coverage-reports
1047
+ npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports
1048
+ ```
1049
+
1050
+ This runs the matching tests, prints a coverage summary in the terminal, and writes the V8 HTML report to `./coverage-reports/index.html`. Quote glob patterns so Node.js, rather than your shell, handles matching.
1051
+
1052
+ For repeated runs, save the test targets and coverage options in `mcr.config.js` (ESM; use `"type": "module"` in `package.json`, or name the config `mcr.config.mjs`):
1053
+
1054
+ ```js
1055
+ export default {
1056
+ test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
1057
+ reports: ['v8', 'console-summary'],
1058
+ outputDir: './coverage-reports'
1059
+ };
1060
+ ```
1061
+
1062
+ ```sh
1063
+ # Run the targets from the config
1064
+ npx mcr test
1065
+ # Override the configured targets and forward Node.js test runner options after --
1066
+ npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
1067
+
1068
+ # Use an explicit config file
1069
+ npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
1070
+
1071
+ # Choose reports from the command line
1072
+ npx mcr test "../tests/**/*.test.js" -r v8,console-summary
1073
+
1074
+ # Run multiple test files
1075
+ npx mcr test ./test/first.test.js ./test/second.test.js
1076
+
1077
+ # Filter test names and select a reporter
1078
+ npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
1079
+
1080
+ # Place the test command after an initial separator
1081
+ npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec
1082
+ ```
1083
+
1084
+ Positional targets override (rather than append to) the config `test` option. If no positional targets are given, the config option is used. A config file is not required when targets are supplied on the command line. Existing MCR options such as `-c`, `-r`, `-o` and `--lcov` can appear before or after targets. Put Node runner options after `--`; those arguments are forwarded unchanged, not parsed as MCR options or target overrides. Quote globs to prevent shell expansion. In the alternative `mcr [options] -- test ...` form, put all MCR options before the first `--`; a second `--` separates Node runner options.
1085
+
1086
+ Targets are passed unchanged to `node --test`. MCR does not check their types or filesystem existence, expand directories/globs, resolve or normalize paths, escape special characters, or deduplicate targets. `node --test` accepts positional test file paths or glob patterns, not a JavaScript configuration object. In MCR config, a string represents one such argument (it is not split on spaces), and a string array represents multiple arguments forwarded in their original order. `null` is only MCR's skip default and is never passed to Node. Node handles discovery, matching, interpretation and errors. Relative paths use the current working directory, not the config file directory. A bare directory is not promised to trigger recursive discovery; use a glob such as `"../tests/**/*.test.js"` for recursive matching, following the installed Node version's native behavior. TypeScript execution depends on your Node version or a loader such as `--import tsx`.
1087
+
1088
+ `test` defaults to `null`; when no positional targets are supplied, `null`, an omitted value, an empty string or `[]` skips tests without generating a report. MCR only checks whether targets were supplied; invalid or unmatched targets are left to Node. If no coverage data is produced and no report can be generated, the command fails at report generation. Missing explicit config files and failed config loading also fail the command rather than silently skipping tests. Test failures still produce coverage reports and retain a non-zero exit status; report generation failures also fail the command. Existing coverage options and CLI hooks are supported. Use coverage filters to exclude test files if needed. **The `test` option has no effect on other CLI commands or API calls**, including `merge` and `generate()`.
1089
+
1090
+ See [Command Line](#command-line) for other CLI options.
1091
+
926
1092
  ## Merge Coverage Reports
927
1093
  The following usage scenarios may require merging coverage reports:
928
1094
  - When the code is executed in different environments, like Node.js `server side` and browser `client side` (`Next.js` for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage.
@@ -943,12 +1109,12 @@ First, using the `raw` report to export the original coverage data to the specif
943
1109
  ```js
944
1110
  const coverageOptions = {
945
1111
  name: 'My Unit Test Coverage Report',
946
- outputDir: "./coverage-reports/unit",
1112
+ outputDir: './coverage-reports/unit',
947
1113
  reports: [
948
1114
  ['raw', {
949
1115
  // relative path will be "./coverage-reports/unit/raw"
950
1116
  // defaults to raw
951
- outputDir: "raw"
1117
+ outputDir: 'raw'
952
1118
  }],
953
1119
  ['v8'],
954
1120
  ['console-details']
@@ -964,8 +1130,8 @@ const coverageOptions = {
964
1130
  - Then create a script `merge-coverage.js` to generate a merged report with option `inputDir`.
965
1131
  ```js
966
1132
  // merge-coverage.js
967
- const fs = require('fs');
968
- const { CoverageReport } = require('monocart-coverage-reports');
1133
+ import fs from 'fs';
1134
+ import { CoverageReport } from 'monocart-coverage-reports';
969
1135
  const inputDir = [
970
1136
  './coverage-reports/unit/raw',
971
1137
  './coverage-reports/e2e/raw'
@@ -984,7 +1150,7 @@ const coverageOptions = {
984
1150
  '**/node_modules/**': false,
985
1151
  '**/src/**': true
986
1152
  },
987
-
1153
+
988
1154
  sourcePath: (filePath, info) => {
989
1155
  // Unify the file path for the same files
990
1156
  // For example, the file index.js has different paths:
@@ -998,7 +1164,7 @@ const coverageOptions = {
998
1164
  ['v8'],
999
1165
  ['console-details']
1000
1166
  ],
1001
-
1167
+
1002
1168
  onEnd: () => {
1003
1169
  // remove the raw files if no longer needed
1004
1170
  // inputDir.forEach((p) => {
@@ -1027,7 +1193,7 @@ see example: [merge-code-coverage](https://github.com/cenfun/merge-code-coverage
1027
1193
  ## Common issues
1028
1194
  ### Unexpected coverage
1029
1195
  In most cases, it happens when the coverage of the generated code is converted to the coverage of the original code through a sourcemap. In other words, it's an issue with the sourcemap. Most of the time, we can solve this by setting `minify` to `false` in the configuration of build tools. Let's take a look at an example:
1030
- ```js
1196
+ ```text
1031
1197
  const a = tf ? 'true' : 'false';
1032
1198
  ^ ^ ^
1033
1199
  m1 p m2
@@ -1043,21 +1209,21 @@ How `MCR` Works:
1043
1209
  ### Unparsable source
1044
1210
  It happens during the parsing of the source code into AST, if the source code is not in the standard ECMAScript. For example `ts`, `jsx` and so on. There is a option to fix it, which is to manually compile the source code for these files.
1045
1211
  ```js
1046
- import * as fs from "fs";
1047
- import * as path from "path";
1048
- import { fileURLToPath } from "url";
1212
+ import * as fs from 'fs';
1213
+ import * as path from 'path';
1214
+ import { fileURLToPath } from 'url';
1049
1215
  import * as TsNode from 'ts-node';
1050
1216
  const coverageOptions = {
1051
- onEntry: async (entry) => {
1052
- const filePath = fileURLToPath(entry.url)
1053
- const originalSource = fs.readFileSync(filePath).toString("utf-8");
1217
+ onEntry: (entry) => {
1218
+ const filePath = fileURLToPath(entry.url);
1219
+ const originalSource = fs.readFileSync(filePath).toString('utf-8');
1054
1220
  const fileName = path.basename(filePath);
1055
1221
  const tn = TsNode.create({});
1056
1222
  const source = tn.compile(originalSource, fileName);
1057
1223
  entry.fake = false;
1058
1224
  entry.source = source;
1059
1225
  }
1060
- }
1226
+ };
1061
1227
  ```
1062
1228
 
1063
1229
  ### JavaScript heap out of memory
@@ -1098,9 +1264,30 @@ const coverageOptions = {
1098
1264
 
1099
1265
  - Show time logs with env `MCR_LOG_TIME`
1100
1266
  ```js
1101
- process.env.MCR_LOG_TIME = true
1267
+ process.env.MCR_LOG_TIME = true;
1102
1268
  ```
1103
1269
 
1270
+ ### CSS coverage issues
1271
+
1272
+ CSS coverage from multiple bundles is merged as a union for each original source.
1273
+
1274
+ For preprocessor syntax that `all` cannot parse directly, use `all.transformer` to compile to CSS, set `entry.type = 'css'`, and preserve the sourcemap.
1275
+
1276
+ #### Sass mixins and `@include`
1277
+
1278
+ Sass `@include` can map a generated rule to non-contiguous source locations. When a rule cannot be reliably mapped, MCR reports the **whole bundle as generated CSS** instead of the preprocessor sources. That bundle's coverage is not merged into the original-source coverage.
1279
+
1280
+ #### Line coverage of untested preprocessor sources
1281
+
1282
+ Untested files included through `all` have `0%` byte coverage, including sourcemap originals. Lines outside parsed or mapped rules (such as variables or unused mixins) may appear covered; use **bytes** coverage for these files.
1283
+
1284
+ #### Source map can be inaccurate after minification
1285
+ CSS minification can produce imprecise sourcemaps and trigger fallback to generated CSS. If source mapping is inaccurate, try **disabling CSS minification**.
1286
+
1287
+ #### V8 covered the whole block
1288
+
1289
+ Chromium may mark an entire media query or nested CSS block as covered. Consider [postcss-atomic-selectors](https://www.npmjs.com/package/postcss-atomic-selectors) as a potential workaround.
1290
+
1104
1291
  ## Integration with Any Testing Framework
1105
1292
  - API
1106
1293
  - Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter. `await mcr.add(coverageData)`
@@ -1181,7 +1368,7 @@ mcr mocha ./test/**/*.js
1181
1368
  ```sh
1182
1369
  cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
1183
1370
  cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
1184
- # Node.js v18.19.0 +
1371
+ # MCR 3.x: Node.js >= 22.15.0
1185
1372
  mcr --import tsx tsx ./src/example.ts
1186
1373
  mcr --import tsx mocha ./test/**/*.ts
1187
1374
  ```
@@ -1201,7 +1388,7 @@ mcr ava
1201
1388
  - Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
1202
1389
  ```js
1203
1390
  const coverageOptions = {
1204
- outputDir: "./coverage-reports",
1391
+ outputDir: './coverage-reports',
1205
1392
  reports: [
1206
1393
  ['codecov']
1207
1394
  ]
@@ -1221,7 +1408,7 @@ const coverageOptions = {
1221
1408
  - Using `lcov` report:
1222
1409
  ```js
1223
1410
  const coverageOptions = {
1224
- outputDir: "./coverage-reports",
1411
+ outputDir: './coverage-reports',
1225
1412
  lcov: true
1226
1413
  };
1227
1414
  ```
@@ -1239,7 +1426,7 @@ const coverageOptions = {
1239
1426
  - Using `lcov` report:
1240
1427
  ```js
1241
1428
  const coverageOptions = {
1242
- outputDir: "./coverage-reports",
1429
+ outputDir: './coverage-reports',
1243
1430
  lcov: true
1244
1431
  };
1245
1432
  ```
@@ -1272,17 +1459,25 @@ const coverageOptions = {
1272
1459
  ```
1273
1460
 
1274
1461
  ## Contributing
1275
- - Node.js 22+ (recommended development version)
1462
+ - Latest Node.js 24 LTS recommended for development; minimum package runtime is 22.15.0. Development tools may require a newer patch than the minimum runtime.
1276
1463
  - VSCode (extensions: eslint/stylelint/vue)
1277
1464
  ```sh
1278
1465
  npm install
1279
1466
  npx playwright install --with-deps
1280
1467
 
1281
1468
  npm run build
1469
+ npm run lint
1282
1470
  npm run test
1283
1471
 
1284
1472
  npm run dev
1285
1473
  ```
1474
+ README JavaScript examples are checked with the project's ESLint configuration via `npm run lint:readme` and `npm run lint`. Only `no-undef` and `no-unused-vars` are disabled for snippets that omit surrounding context; syntax, style, and other correctness rules remain enabled. Use `ts` fences for TypeScript declarations and `text` for diagrams. For intentionally nonstandard syntax demonstrations, keep `js` highlighting and wrap the region in standalone `<!-- readme-lint-disable -->` / `<!-- readme-lint-enable -->` comments. Such regions are neither linted nor autofixed. Markers must be paired and cannot be nested; markers inside code fences are treated as example text.
1475
+
1476
+ ```sh
1477
+ npm run lint:readme
1478
+ npm run lint:readme -- --fix
1479
+ ```
1480
+
1286
1481
  - Refreshing `eol=lf` for snapshot of test (Windows)
1287
1482
  ```sh
1288
1483
  git add . -u
@@ -1293,4 +1488,7 @@ npm run eol
1293
1488
 
1294
1489
  ## Thanks
1295
1490
  - [@bcoe](https://github.com/bcoe)
1296
- - [@edumserrano](https://github.com/edumserrano)
1491
+ - [@edumserrano](https://github.com/edumserrano)
1492
+
1493
+
1494
+ ![Monocart Coverage Reports](./assets/og-image.png)