monocart-coverage-reports 2.13.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +267 -114
  2. package/README.zh-Hans.md +268 -115
  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 +6 -6
  10. package/lib/converter/collect-source-maps.js +9 -9
  11. package/lib/converter/converter.js +41 -115
  12. package/lib/converter/fake-source-coverage.js +64 -0
  13. package/lib/converter/find-original-range.js +3 -3
  14. package/lib/converter/flatten-source-maps.js +2 -4
  15. package/lib/converter/ignore.js +2 -4
  16. package/lib/converter/info-branch.js +5 -3
  17. package/lib/converter/info-function.js +5 -3
  18. package/lib/converter/info-statement.js +5 -3
  19. package/lib/converter/range-index.js +78 -0
  20. package/lib/converter/source-range-merge.js +103 -0
  21. package/lib/converter/untested.js +27 -55
  22. package/lib/default/options.js +13 -1
  23. package/lib/generate.js +44 -26
  24. package/lib/index.d.ts +52 -4
  25. package/lib/index.js +35 -41
  26. package/lib/istanbul/istanbul-summary.js +2 -2
  27. package/lib/istanbul/istanbul.js +35 -13
  28. package/lib/packages/chunks/chunk-RVOSNMVU.js +2 -0
  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 +1 -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 +5 -5
  55. package/lib/utils/util.js +61 -41
  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 +153 -149
  60. package/skills/monocart-coverage-reports/SKILL.md +106 -0
  61. package/skills/monocart-coverage-reports/references/cli.md +108 -0
  62. package/skills/monocart-coverage-reports/references/collection.md +152 -0
  63. package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
  64. package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
  65. package/skills/monocart-coverage-reports/references/repository-map.md +55 -0
  66. package/lib/index.mjs +0 -9
  67. package/lib/register/hooks.mjs +0 -3
  68. package/lib/register/register.mjs +0 -3
package/README.md CHANGED
@@ -31,6 +31,7 @@
31
31
  * [Multiprocessing Support](#multiprocessing-support)
32
32
  * [Command Line](#command-line)
33
33
  * [Config File](#config-file)
34
+ * [Run Tests with `mcr test`](#run-tests-with-mcr-test)
34
35
  * [Merge Coverage Reports](#merge-coverage-reports)
35
36
  - [Automatic Merging](#automatic-merging)
36
37
  - [Manual Merging](#manual-merging)
@@ -66,18 +67,21 @@
66
67
  * [Thanks](#thanks)
67
68
 
68
69
  ## Usage
69
- > [Node.js 22+](https://nodejs.org/) is recommended and is the actively maintained target.
70
+ > **MCR 3.x requires [Node.js >= 22.15.0](https://nodejs.org/)** for all APIs and CLI commands, including ordinary commands, `merge` and `test`.
71
+
72
+ **Upgrading from 2.x:** See the [3.x migration guide](migrate-to-3.md).
73
+
70
74
  - Install
71
75
  ```sh
72
76
  npm install monocart-coverage-reports
73
77
  ```
74
78
  - API
75
79
  ```js
76
- const MCR = require('monocart-coverage-reports');
80
+ import { MCR } from 'monocart-coverage-reports';
77
81
  const mcr = MCR({
78
82
  name: 'My Coverage Report - 2024-02-28',
79
83
  outputDir: './coverage-reports',
80
- reports: ["v8", "console-details"],
84
+ reports: ['v8', 'console-details'],
81
85
  cleanCache: true
82
86
  });
83
87
  await mcr.add(coverageData);
@@ -99,6 +103,21 @@ mcr node my-app.js -r v8,console-details
99
103
  ```
100
104
  For more information, see [Command Line](#command-line)
101
105
 
106
+ ## AI Agent Skill
107
+
108
+ 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.
109
+
110
+ 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.
111
+
112
+ From the host project root, locate the installed skill and package manifest (older releases may not include the skill):
113
+
114
+ ```sh
115
+ node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
116
+ node -p "require.resolve('monocart-coverage-reports/package.json')"
117
+ ```
118
+
119
+ 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.
120
+
102
121
  ## Options
103
122
  - Default options: [lib/default/options.js](./lib/default/options.js)
104
123
  - Options declaration see `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
@@ -115,6 +134,7 @@ For more information, see [Command Line](#command-line)
115
134
  | `dataDir` | `string` | `null` | Coverage data directory loaded automatically in `generate()` — alternative to `addFromDir()`. |
116
135
  | `entryFilter` | `string \| object \| function` | `null` | (V8 only) Filter V8 entries by url. See [Filtering Results](#filtering-results). |
117
136
  | `sourceFilter` | `string \| object \| function` | `null` | (V8 only) Filter sources unpacked from sourcemaps. |
137
+ | `outputFilter` | `string \| object \| function` | `null` | (V8 only) Filter final files by `sourcePath` after sourcemap conversion. See [Filtering Results](#filtering-results). |
118
138
  | `filter` | `string \| object \| function` | `null` | Combined filter replacing both `entryFilter` and `sourceFilter`. |
119
139
  | `sourcePath` | `object \| function` | `null` | Rewrite source paths; see [Resolve `sourcePath`](#resolve-sourcepath-for-the-source-files). |
120
140
  | `all` | `string \| string[] \| object` | `null` | Include untested files as empty coverage. See [Adding Empty Coverage](#adding-empty-coverage-for-untested-files). |
@@ -127,7 +147,7 @@ For more information, see [Command Line](#command-line)
127
147
  | `watermarks` | `[number, number] \| object` | `[50, 80]` | Low/high thresholds (percent). Accepts per-metric object like `{ bytes:[50,80], lines:[50,80] }`. |
128
148
  | `clean` | `boolean` | `true` | Clean previous reports in `outputDir` before generating (the `.cache` dir is preserved unless `cleanCache` is also `true`). |
129
149
  | `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). |
150
+ | `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
151
  | `sourceMap` | `boolean` | `false` | Save source/sourcemap files to cache for debugging (requires `logging: "debug"`). |
132
152
  | `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | Custom sourcemap loader (e.g. from a build cache). Call `defaultResolver(url)` to fall back. |
133
153
  | `onEntry` | `(entry) => void \| Promise<void>` | `null` | (V8 only) Per-entry hook; see [Hooks](#hooks). |
@@ -202,7 +222,7 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
202
222
 
203
223
  - Custom Reporter
204
224
  ```js
205
- {
225
+ const coverageOptions = {
206
226
  reports: [
207
227
  [path.resolve('./test/custom-istanbul-reporter.js'), {
208
228
  type: 'istanbul',
@@ -216,21 +236,23 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
216
236
  type: 'both'
217
237
  }]
218
238
  ]
219
- }
239
+ };
220
240
  ```
221
241
  - Istanbul custom reporter
222
242
  > 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
243
  - V8 custom reporter
224
244
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
225
245
 
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`.
246
+ The `type` option selects the reporter interface and when it runs:
247
+ - `'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.
248
+ - `'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.
249
+ - `'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.
250
+
251
+ 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
252
 
231
253
  ### Multiple Reports:
232
254
  ```js
233
- const MCR = require('monocart-coverage-reports');
255
+ import { MCR } from 'monocart-coverage-reports';
234
256
  const coverageOptions = {
235
257
  outputDir: './coverage-reports',
236
258
  reports: [
@@ -247,15 +269,15 @@ const coverageOptions = {
247
269
 
248
270
  // custom reports
249
271
  // Specify reporter name with the NPM package
250
- ["custom-reporter-1"],
251
- ["custom-reporter-2", {
252
- type: "istanbul",
253
- key: "value"
272
+ ['custom-reporter-1'],
273
+ ['custom-reporter-2', {
274
+ type: 'istanbul',
275
+ key: 'value'
254
276
  }],
255
277
  // Specify reporter name with local path
256
278
  ['/absolute/path/to/custom-reporter.js']
257
279
  ]
258
- }
280
+ };
259
281
  const mcr = MCR(coverageOptions);
260
282
  ```
261
283
 
@@ -322,7 +344,7 @@ await Promise.all([
322
344
  })
323
345
  ]);
324
346
 
325
- await page.goto("your page url");
347
+ await page.goto('your page url');
326
348
 
327
349
  const [jsCoverage, cssCoverage] = await Promise.all([
328
350
  page.coverage.stopJSCoverage(),
@@ -350,7 +372,7 @@ await Promise.all([
350
372
  })
351
373
  ]);
352
374
 
353
- await page.goto("your page url");
375
+ await page.goto('your page url');
354
376
 
355
377
  const [jsCoverage, cssCoverage] = await Promise.all([
356
378
  page.coverage.stopJSCoverage(),
@@ -403,29 +425,34 @@ Possible solutions:
403
425
  - see [Command Line](#command-line)
404
426
 
405
427
  ### Collecting V8 Coverage Data with `CDPClient` API
428
+
429
+ `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 })`.
430
+
406
431
  - `CDPClient` available APIs
407
- ```js
408
- startJSCoverage: () => Promise<void>;
409
- stopJSCoverage: () => Promise<V8CoverageEntry[]>;
432
+ ```ts
433
+ interface CoverageClient {
434
+ startJSCoverage: () => Promise<void>;
435
+ stopJSCoverage: () => Promise<V8CoverageEntry[]>;
410
436
 
411
- startCSSCoverage: () => Promise<void>;
412
- stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
437
+ startCSSCoverage: () => Promise<void>;
438
+ stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
413
439
 
414
- /** start both js and css coverage */
415
- startCoverage: () => Promise<void>;
416
- /** stop and return both js and css coverage */
417
- stopCoverage: () => Promise<V8CoverageEntry[]>;
440
+ /** start both js and css coverage */
441
+ startCoverage: () => Promise<void>;
442
+ /** stop and return both js and css coverage */
443
+ stopCoverage: () => Promise<V8CoverageEntry[]>;
418
444
 
419
- /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
420
- writeCoverage: () => Promise<string>;
445
+ /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
446
+ writeCoverage: () => Promise<string>;
421
447
 
422
- /** get istanbul coverage data */
423
- getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
448
+ /** get istanbul coverage data */
449
+ getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
450
+ }
424
451
  ```
425
452
 
426
453
  - Work with node debugger port `--inspect=9229` or browser debugging port `--remote-debugging-port=9229`
427
454
  ```js
428
- const MCR = require('monocart-coverage-reports');
455
+ import { MCR } from 'monocart-coverage-reports';
429
456
  const client = await MCR.CDPClient({
430
457
  port: 9229
431
458
  });
@@ -436,8 +463,8 @@ const coverageData = await client.stopJSCoverage();
436
463
 
437
464
  - Work with [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
438
465
  ```js
439
- const { chromium } = require('playwright');
440
- const MCR = require('monocart-coverage-reports');
466
+ import { chromium } from 'playwright';
467
+ import { MCR } from 'monocart-coverage-reports';
441
468
  const browser = await chromium.launch();
442
469
  const page = await browser.newPage();
443
470
  const session = await page.context().newCDPSession(page);
@@ -447,14 +474,14 @@ const client = await MCR.CDPClient({
447
474
  // both js and css coverage
448
475
  await client.startCoverage();
449
476
  // run your test page here
450
- await page.goto("your page url");
477
+ await page.goto('your page url');
451
478
  const coverageData = await client.stopCoverage();
452
479
  ```
453
480
 
454
481
  - Work with [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
455
482
  ```js
456
- const puppeteer = require('puppeteer');
457
- const MCR = require('monocart-coverage-reports');
483
+ import puppeteer from 'puppeteer';
484
+ import { MCR } from 'monocart-coverage-reports';
458
485
  const browser = await puppeteer.launch({});
459
486
  const page = await browser.newPage();
460
487
  const session = await page.target().createCDPSession();
@@ -464,20 +491,20 @@ const client = await MCR.CDPClient({
464
491
  // both js and css coverage
465
492
  await client.startCoverage();
466
493
  // run your test page here
467
- await page.goto("your page url");
494
+ await page.goto('your page url');
468
495
  const coverageData = await client.stopCoverage();
469
496
  ```
470
497
 
471
498
  - Work with [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (Chrome/Edge Browser)
472
499
  ```js
473
- const { Builder, Browser } = require('selenium-webdriver');
474
- const MCR = require('monocart-coverage-reports');
500
+ import { Builder, Browser } from 'selenium-webdriver';
501
+ import { MCR } from 'monocart-coverage-reports';
475
502
  const driver = await new Builder().forBrowser(Browser.CHROME).build();
476
503
  const pageCdpConnection = await driver.createCDPConnection('page');
477
504
  const session = new MCR.WSSession(pageCdpConnection._wsConnection);
478
505
  const client = await MCR.CDPClient({
479
506
  session
480
- })
507
+ });
481
508
  ```
482
509
 
483
510
  ### V8 Coverage Data API
@@ -485,7 +512,7 @@ const client = await MCR.CDPClient({
485
512
  - [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
486
513
  - [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
487
514
  - [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
515
+ ```ts
489
516
  // Coverage data for a source range.
490
517
  export interface CoverageRange {
491
518
  // JavaScript script source offset for the range start.
@@ -574,15 +601,15 @@ We can use `sourceFilter` to filter the source files. For example, we should rem
574
601
  For example:
575
602
  ```js
576
603
  const coverageOptions = {
577
- entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
604
+ entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
578
605
  sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
579
606
  };
580
607
  ```
581
608
  Or using [`minimatch`](https://github.com/isaacs/minimatch) pattern:
582
609
  ```js
583
610
  const coverageOptions = {
584
- entryFilter: "**/main.js",
585
- sourceFilter: "**/src/**"
611
+ entryFilter: '**/main.js',
612
+ sourceFilter: '**/src/**'
586
613
  };
587
614
  ```
588
615
  Support multiple patterns:
@@ -607,10 +634,17 @@ Note, those patterns will be transformed to a function, and the order of the pat
607
634
  ```js
608
635
  const coverageOptions = {
609
636
  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
637
+ if (minimatch(entry.url, '**/node_modules/**')) {
638
+ return false;
639
+ }
640
+ if (minimatch(entry.url, '**/vendor.js')) {
641
+ return false;
642
+ }
643
+ if (minimatch(entry.url, '**/src/**')) {
644
+ return true;
645
+ }
646
+ // else unmatched
647
+ return false;
614
648
  }
615
649
  };
616
650
  ```
@@ -629,6 +663,42 @@ const coverageOptions = {
629
663
  };
630
664
  ```
631
665
 
666
+ ### Using `outputFilter` to filter final files
667
+ > **Version requirement:** `outputFilter` is introduced in **v3.0.0**. It is not supported in v2.13.x or earlier.
668
+
669
+ **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`.
670
+
671
+ | Approach | When and what it filters | Advantages | Trade-offs |
672
+ | :-- | :-- | :-- | :-- |
673
+ | `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. |
674
+ | `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. |
675
+
676
+ **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.
677
+
678
+ For V8 input, `outputFilter` filters both V8 reports and converted Istanbul reports. It defaults to `null` (disabled).
679
+
680
+ 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.
681
+
682
+ ```js
683
+ const coverageOptions = {
684
+ outputFilter: {
685
+ '**/node_modules/**': false,
686
+ '**/src/**': true
687
+ }
688
+ };
689
+ // Alternatively:
690
+ // outputFilter: '**/src/**'
691
+ // outputFilter: (entry) => entry.sourcePath.startsWith('src/')
692
+ ```
693
+
694
+ CLI examples (quote patterns to prevent shell expansion):
695
+ ```sh
696
+ mcr merge --inputDir ./raw --outputFilter "**/src/**"
697
+ mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"
698
+ ```
699
+
700
+ `outputFilter` does not change raw coverage exports and does not apply to Istanbul input.
701
+
632
702
  ## Resolve `sourcePath` for the Source Files
633
703
  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
704
  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.
@@ -650,34 +720,34 @@ It also supports simple key/value replacement:
650
720
  ```js
651
721
  const coverageOptions = {
652
722
  sourcePath: {
653
- 'my-dist-file1/': '',
723
+ 'my-dist-file1/': '',
654
724
  'my-dist-file2/': ''
655
725
  }
656
726
  };
657
727
  ```
658
728
  Normalize the full path of the file:
659
729
  ```js
660
- const path = require("path")
730
+ import path from 'node:path';
661
731
 
662
732
  // MCR coverage options
663
733
  const coverageOptions = {
664
- sourcePath: (filePath, info)=> {
734
+ sourcePath: (filePath, info) => {
665
735
  if (!filePath.includes('/') && info.distFile) {
666
736
  return `${path.dirname(info.distFile)}/${filePath}`;
667
737
  }
668
738
  return filePath;
669
739
  }
670
- }
740
+ };
671
741
  ```
672
742
 
673
743
  ## Adding Empty Coverage for Untested Files
674
744
  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.
675
745
  ```js
676
746
  const coverageOptions = {
677
- all: './src',
747
+ all: './src'
678
748
 
679
749
  // or multiple dirs
680
- all: ['./src', './lib'],
750
+ // all: ['./src', './lib']
681
751
  };
682
752
  ```
683
753
  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 +760,29 @@ const coverageOptions = {
690
760
  '**/ignored-*.js': false,
691
761
  '**/*.html': false,
692
762
  // empty css coverage
693
- '**/*.scss': "css",
763
+ '**/*.scss': 'css',
694
764
  '**/*': true
695
765
  }
696
766
  }
697
767
  };
698
768
  ```
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.
769
+ 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
770
  ```js
701
- const path = require("path");
702
- const swc = require("@swc/core");
771
+ import { fileURLToPath } from 'node:url';
772
+ import { transform } from '@swc/core';
703
773
  const coverageOptions = {
704
774
  all: {
705
775
  dir: ['./src'],
776
+ filter: '**/*.ts',
706
777
  transformer: async (entry) => {
707
- const { code, map } = await swc.transform(entry.source, {
708
- filename: path.basename(entry.url),
778
+ const { code, map } = await transform(entry.source, {
779
+ filename: fileURLToPath(entry.url),
709
780
  sourceMaps: true,
710
781
  isModule: true,
711
782
  jsc: {
712
783
  parser: {
713
- syntax: "typescript",
714
- jsx: true
715
- },
716
- transform: {}
784
+ syntax: 'typescript'
785
+ }
717
786
  }
718
787
  });
719
788
  entry.source = code;
@@ -726,36 +795,45 @@ const coverageOptions = {
726
795
  ## Hooks
727
796
 
728
797
  ### `onEnd` — after the report is generated
729
- Typical use case: enforce coverage thresholds.
798
+ 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
799
  ```js
731
- const EC = require('eight-colors');
800
+ const getCoverageError = (metric, summary, minimum) => {
801
+ if (!summary || !(summary.total > 0)) {
802
+ return `${metric}: missing or empty coverage`;
803
+ }
804
+ const { pct } = summary;
805
+ if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
806
+ return `${metric}: ${pct}%, required ${minimum}%`;
807
+ }
808
+ return '';
809
+ };
732
810
  const coverageOptions = {
733
811
  name: 'My Coverage Report',
734
812
  outputDir: './coverage-reports',
735
813
  onEnd: (coverageResults) => {
814
+ if (!coverageResults) {
815
+ throw new Error('No coverage results');
816
+ }
736
817
  const thresholds = {
737
818
  bytes: 80,
738
819
  lines: 60
739
820
  };
740
- console.log('check thresholds ...', thresholds);
741
821
  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]} %`);
822
+ for (const [metric, minimum] of Object.entries(thresholds)) {
823
+ const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
824
+ if (error) {
825
+ errors.push(error);
747
826
  }
748
- });
827
+ }
749
828
  if (errors.length) {
750
- const errMsg = errors.join('\n');
751
- console.log(EC.red(errMsg));
752
- // throw new Error(errMsg);
753
- // process.exit(1);
829
+ throw new Error(errors.join('\n'));
754
830
  }
755
831
  }
756
- }
832
+ };
757
833
  ```
758
834
 
835
+ 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).
836
+
759
837
  ### `onEntry` — before each V8 entry is processed (V8 only)
760
838
  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
839
  ```js
@@ -772,7 +850,7 @@ Only available when using `mcr <command>`:
772
850
  - `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
851
  ```js
774
852
  // mcr.config.js
775
- module.exports = {
853
+ export default {
776
854
  onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
777
855
  // wait for something, or pre-process the raw files
778
856
  }
@@ -780,6 +858,8 @@ module.exports = {
780
858
  ```
781
859
 
782
860
  ## Ignoring Uncovered Codes
861
+ <!-- readme-lint-disable -->
862
+
783
863
  To ignore codes, use the special comment which starts with `v8 ignore `:
784
864
  - Ignoring all until stop
785
865
  ```js
@@ -814,12 +894,14 @@ function uncovered() {
814
894
  ```
815
895
  - To disable comment-based ignoring entirely, set `v8Ignore: false`.
816
896
 
897
+ <!-- readme-lint-enable -->
898
+
817
899
  ## Multiprocessing Support
818
900
  > 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
901
  - Main process, before the start of testing
820
902
  ```js
821
- const MCR = require('monocart-coverage-reports');
822
- const coverageOptions = require('path-to/same-options.js');
903
+ import { MCR } from 'monocart-coverage-reports';
904
+ import coverageOptions from 'path-to/same-options.js';
823
905
  const mcr = MCR(coverageOptions);
824
906
  // clean previous cache before the start of testing
825
907
  // unless the running environment is new and no cache
@@ -828,16 +910,16 @@ mcr.cleanCache();
828
910
 
829
911
  - Sub process 1, testing stage 1
830
912
  ```js
831
- const MCR = require('monocart-coverage-reports');
832
- const coverageOptions = require('path-to/same-options.js');
913
+ import { MCR } from 'monocart-coverage-reports';
914
+ import coverageOptions from 'path-to/same-options.js';
833
915
  const mcr = MCR(coverageOptions);
834
916
  await mcr.add(coverageData1);
835
917
  ```
836
918
 
837
919
  - Sub process 2, testing stage 2
838
920
  ```js
839
- const MCR = require('monocart-coverage-reports');
840
- const coverageOptions = require('path-to/same-options.js');
921
+ import { MCR } from 'monocart-coverage-reports';
922
+ import coverageOptions from 'path-to/same-options.js';
841
923
  const mcr = MCR(coverageOptions);
842
924
  await mcr.add(coverageData2);
843
925
  ```
@@ -845,8 +927,8 @@ await mcr.add(coverageData2);
845
927
  - Main process, after the completion of testing
846
928
  ```js
847
929
  // generate coverage reports after the completion of testing
848
- const MCR = require('monocart-coverage-reports');
849
- const coverageOptions = require('path-to/same-options.js');
930
+ import { MCR } from 'monocart-coverage-reports';
931
+ import coverageOptions from 'path-to/same-options.js';
850
932
  const mcr = MCR(coverageOptions);
851
933
  await mcr.generate();
852
934
  ```
@@ -868,14 +950,17 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
868
950
 
869
951
  - CLI Options
870
952
 
953
+ 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.
954
+
871
955
  The options below are only available when using the CLI (`mcr <command>`):
872
956
 
873
957
  | Option | Type | Default | Description |
874
958
  | :-- | :-- | :-- | :-- |
875
959
  | `-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` |
960
+ | `--import` | `string` | `null` | Preload ESM module via `NODE_OPTIONS`, e.g. `tsx` for TypeScript. |
961
+ | `--require` | `string` | `null` | Deprecated alias for `--import`; uses ESM loading, not CommonJS resolution |
962
+ | `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). |
963
+ | `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
964
  | `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
880
965
  | `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
881
966
 
@@ -886,16 +971,18 @@ For other options (e.g. `--name`, `--reports`, `--outputDir`, `--lcov`, etc.), s
886
971
  mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
887
972
  ```
888
973
 
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`.
974
+ - `--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
975
  ```sh
891
976
  mcr --import tsx node ./test.ts
892
977
  ```
893
978
 
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`.
979
+ - `--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
980
  ```sh
896
981
  mcr --env .env.test node ./test.js
897
982
  ```
898
983
 
984
+ - `test`: run Node.js tests and generate coverage reports. See [Run Tests with `mcr test`](#run-tests-with-mcr-test).
985
+
899
986
  - `merge`: merge raw coverage data from `inputDir`(s) and generate merged reports directly, without running a child process.
900
987
  ```sh
901
988
  mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
@@ -908,6 +995,8 @@ See [Merge Coverage Reports](#merge-coverage-reports).
908
995
  - [AVA](#ava)
909
996
 
910
997
  ## Config File
998
+ 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.
999
+
911
1000
  Loading config file by priority:
912
1001
  - Custom config file:
913
1002
  - CLI: `mcr --config <my-config-file-path>`
@@ -923,6 +1012,59 @@ mcr --import tsx -c mcr.config.ts node ./test.js
923
1012
 
924
1013
  > 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
1014
 
1015
+ ## Run Tests with `mcr test`
1016
+
1017
+ **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**.
1018
+
1019
+ Install MCR and run your test files directly — no config file is required:
1020
+
1021
+ ```sh
1022
+ npm install --save-dev monocart-coverage-reports
1023
+ npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports
1024
+ ```
1025
+
1026
+ 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.
1027
+
1028
+ 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`):
1029
+
1030
+ ```js
1031
+ export default {
1032
+ test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
1033
+ reports: ['v8', 'console-summary'],
1034
+ outputDir: './coverage-reports'
1035
+ };
1036
+ ```
1037
+
1038
+ ```sh
1039
+ # Run the targets from the config
1040
+ npx mcr test
1041
+ # Override the configured targets and forward Node.js test runner options after --
1042
+ npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
1043
+
1044
+ # Use an explicit config file
1045
+ npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
1046
+
1047
+ # Choose reports from the command line
1048
+ npx mcr test "../tests/**/*.test.js" -r v8,console-summary
1049
+
1050
+ # Run multiple test files
1051
+ npx mcr test ./test/first.test.js ./test/second.test.js
1052
+
1053
+ # Filter test names and select a reporter
1054
+ npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
1055
+
1056
+ # Place the test command after an initial separator
1057
+ npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec
1058
+ ```
1059
+
1060
+ 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.
1061
+
1062
+ 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`.
1063
+
1064
+ `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()`.
1065
+
1066
+ See [Command Line](#command-line) for other CLI options.
1067
+
926
1068
  ## Merge Coverage Reports
927
1069
  The following usage scenarios may require merging coverage reports:
928
1070
  - 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 +1085,12 @@ First, using the `raw` report to export the original coverage data to the specif
943
1085
  ```js
944
1086
  const coverageOptions = {
945
1087
  name: 'My Unit Test Coverage Report',
946
- outputDir: "./coverage-reports/unit",
1088
+ outputDir: './coverage-reports/unit',
947
1089
  reports: [
948
1090
  ['raw', {
949
1091
  // relative path will be "./coverage-reports/unit/raw"
950
1092
  // defaults to raw
951
- outputDir: "raw"
1093
+ outputDir: 'raw'
952
1094
  }],
953
1095
  ['v8'],
954
1096
  ['console-details']
@@ -964,8 +1106,8 @@ const coverageOptions = {
964
1106
  - Then create a script `merge-coverage.js` to generate a merged report with option `inputDir`.
965
1107
  ```js
966
1108
  // merge-coverage.js
967
- const fs = require('fs');
968
- const { CoverageReport } = require('monocart-coverage-reports');
1109
+ import fs from 'fs';
1110
+ import { CoverageReport } from 'monocart-coverage-reports';
969
1111
  const inputDir = [
970
1112
  './coverage-reports/unit/raw',
971
1113
  './coverage-reports/e2e/raw'
@@ -984,7 +1126,7 @@ const coverageOptions = {
984
1126
  '**/node_modules/**': false,
985
1127
  '**/src/**': true
986
1128
  },
987
-
1129
+
988
1130
  sourcePath: (filePath, info) => {
989
1131
  // Unify the file path for the same files
990
1132
  // For example, the file index.js has different paths:
@@ -998,7 +1140,7 @@ const coverageOptions = {
998
1140
  ['v8'],
999
1141
  ['console-details']
1000
1142
  ],
1001
-
1143
+
1002
1144
  onEnd: () => {
1003
1145
  // remove the raw files if no longer needed
1004
1146
  // inputDir.forEach((p) => {
@@ -1027,7 +1169,7 @@ see example: [merge-code-coverage](https://github.com/cenfun/merge-code-coverage
1027
1169
  ## Common issues
1028
1170
  ### Unexpected coverage
1029
1171
  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
1172
+ ```text
1031
1173
  const a = tf ? 'true' : 'false';
1032
1174
  ^ ^ ^
1033
1175
  m1 p m2
@@ -1043,21 +1185,21 @@ How `MCR` Works:
1043
1185
  ### Unparsable source
1044
1186
  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
1187
  ```js
1046
- import * as fs from "fs";
1047
- import * as path from "path";
1048
- import { fileURLToPath } from "url";
1188
+ import * as fs from 'fs';
1189
+ import * as path from 'path';
1190
+ import { fileURLToPath } from 'url';
1049
1191
  import * as TsNode from 'ts-node';
1050
1192
  const coverageOptions = {
1051
- onEntry: async (entry) => {
1052
- const filePath = fileURLToPath(entry.url)
1053
- const originalSource = fs.readFileSync(filePath).toString("utf-8");
1193
+ onEntry: (entry) => {
1194
+ const filePath = fileURLToPath(entry.url);
1195
+ const originalSource = fs.readFileSync(filePath).toString('utf-8');
1054
1196
  const fileName = path.basename(filePath);
1055
1197
  const tn = TsNode.create({});
1056
1198
  const source = tn.compile(originalSource, fileName);
1057
1199
  entry.fake = false;
1058
1200
  entry.source = source;
1059
1201
  }
1060
- }
1202
+ };
1061
1203
  ```
1062
1204
 
1063
1205
  ### JavaScript heap out of memory
@@ -1098,7 +1240,7 @@ const coverageOptions = {
1098
1240
 
1099
1241
  - Show time logs with env `MCR_LOG_TIME`
1100
1242
  ```js
1101
- process.env.MCR_LOG_TIME = true
1243
+ process.env.MCR_LOG_TIME = true;
1102
1244
  ```
1103
1245
 
1104
1246
  ## Integration with Any Testing Framework
@@ -1181,7 +1323,7 @@ mcr mocha ./test/**/*.js
1181
1323
  ```sh
1182
1324
  cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
1183
1325
  cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
1184
- # Node.js v18.19.0 +
1326
+ # MCR 3.x: Node.js >= 22.15.0
1185
1327
  mcr --import tsx tsx ./src/example.ts
1186
1328
  mcr --import tsx mocha ./test/**/*.ts
1187
1329
  ```
@@ -1201,7 +1343,7 @@ mcr ava
1201
1343
  - Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
1202
1344
  ```js
1203
1345
  const coverageOptions = {
1204
- outputDir: "./coverage-reports",
1346
+ outputDir: './coverage-reports',
1205
1347
  reports: [
1206
1348
  ['codecov']
1207
1349
  ]
@@ -1221,7 +1363,7 @@ const coverageOptions = {
1221
1363
  - Using `lcov` report:
1222
1364
  ```js
1223
1365
  const coverageOptions = {
1224
- outputDir: "./coverage-reports",
1366
+ outputDir: './coverage-reports',
1225
1367
  lcov: true
1226
1368
  };
1227
1369
  ```
@@ -1239,7 +1381,7 @@ const coverageOptions = {
1239
1381
  - Using `lcov` report:
1240
1382
  ```js
1241
1383
  const coverageOptions = {
1242
- outputDir: "./coverage-reports",
1384
+ outputDir: './coverage-reports',
1243
1385
  lcov: true
1244
1386
  };
1245
1387
  ```
@@ -1272,17 +1414,25 @@ const coverageOptions = {
1272
1414
  ```
1273
1415
 
1274
1416
  ## Contributing
1275
- - Node.js 22+ (recommended development version)
1417
+ - 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
1418
  - VSCode (extensions: eslint/stylelint/vue)
1277
1419
  ```sh
1278
1420
  npm install
1279
1421
  npx playwright install --with-deps
1280
1422
 
1281
1423
  npm run build
1424
+ npm run lint
1282
1425
  npm run test
1283
1426
 
1284
1427
  npm run dev
1285
1428
  ```
1429
+ 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.
1430
+
1431
+ ```sh
1432
+ npm run lint:readme
1433
+ npm run lint:readme -- --fix
1434
+ ```
1435
+
1286
1436
  - Refreshing `eol=lf` for snapshot of test (Windows)
1287
1437
  ```sh
1288
1438
  git add . -u
@@ -1293,4 +1443,7 @@ npm run eol
1293
1443
 
1294
1444
  ## Thanks
1295
1445
  - [@bcoe](https://github.com/bcoe)
1296
- - [@edumserrano](https://github.com/edumserrano)
1446
+ - [@edumserrano](https://github.com/edumserrano)
1447
+
1448
+
1449
+ ![Monocart Coverage Reports](./assets/og-image.png)