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
@@ -0,0 +1,218 @@
1
+ # MCR best practices: test code and get useful coverage
2
+
3
+ Use this module to choose a test setup and implement coverage collection. These recipes target **MCR 3.x and Node.js >= 22.15.0**. Reuse the host package manager, module format, test server, and installed tools. Do not upgrade dependencies or migrate an existing runner without approval.
4
+
5
+ ## 1. Choose the simplest collection path
6
+
7
+ | Target | Default recommendation | Why |
8
+ | --- | --- | --- |
9
+ | New Node.js tests | **Node.js native test runner + `mcr test`** | Built-in assertions/tests, automatic native V8 JS coverage, no instrumentation or custom collector. |
10
+ | Browser tests | **Playwright Test + MCR** | Runner-managed lifecycle and assertions; direct control over JS/CSS coverage reports. |
11
+ | Browser tests with integrated reporting | **Playwright Test + monocart-reporter** | Already integrates MCR; combines test results and coverage reporting. |
12
+ | Existing Jest/Vitest/Mocha/etc. | Keep the existing runner and its compatible collection integration | Adding coverage should not require rewriting working tests. |
13
+
14
+ MCR collects/processes coverage and generates reports; the test runner executes assertions. Prefer native V8 collection where supported. Choose **one owner** for collection and final generation, rather than collecting the same execution through multiple integrations.
15
+
16
+ ## 2. Node.js: prefer `mcr test <targets>`
17
+
18
+ Write tests with `node:test` and `node:assert/strict`. Cover meaningful outputs, errors, boundaries, and asynchronous behavior; use the real module rather than duplicating its implementation in a test. See the [complete module and test example](../SKILL.md#quick-start-test-your-code-and-get-coverage-3x).
19
+
20
+ Install only if needed, using the host package manager:
21
+
22
+ ```sh
23
+ npm install --save-dev monocart-coverage-reports@^3
24
+ npx mcr test "./test/**/*.test.mjs" -r v8,console-summary
25
+ ```
26
+
27
+ Replace the glob with actual test targets. For a repeatable project command, put `mcr test -c mcr.config.mjs` in an npm script such as `test:coverage`, with this config:
28
+
29
+ ```js
30
+ // mcr.config.mjs
31
+ export default {
32
+ test: ['./test/**/*.test.mjs'],
33
+ outputDir: './coverage-reports/node',
34
+ reports: ['v8', 'console-summary'],
35
+ outputFilter: {
36
+ '**/node_modules/**': false,
37
+ '**/src/**': true
38
+ }
39
+ };
40
+ ```
41
+
42
+ This example selects final `src/` paths. Adapt it to the host's real source layout and verify the resulting file list; do not copy a filter that excludes the code under test. See [filtering and sourcemaps](filtering-and-sourcemaps.md) for early input filtering, transpiled sources, and `all` for files never loaded by tests.
43
+
44
+ - `mcr test` invokes Node's native `--test`, automatically collects V8 JS coverage, and generates the selected reports. No manual `NODE_V8_COVERAGE`, coverage instrumentation, or API collector is needed.
45
+ - Quote target globs. Forward Node runner flags after `--`, for example `npx mcr test "./test/**/*.test.mjs" -- --test-reporter=spec`.
46
+ - Supply actual targets or config `test`; an empty target list skips testing and reporting.
47
+ - Open `coverage-reports/index.html` for the first command, or `coverage-reports/node/index.html` for the config above. Verify expected sources and use uncovered branches to choose the next tests.
48
+ - Failed tests still attempt report generation and remain failures. Do not suppress the exit status because an HTML file exists.
49
+ - Existing runners may use `mcr -c mcr.config.mjs -- npm run test:unit` for Node V8 collection where compatible; avoid duplicate coverage providers. TypeScript needs the host's supported loader/build and matching sourcemaps, not an assumption that every TS syntax runs directly.
50
+
51
+ See [CLI details](cli.md) for target overrides, configuration discovery, and argument boundaries.
52
+
53
+ ## 3. Browser: Playwright Test + MCR
54
+
55
+ Run tests with **`playwright test`**, not `mcr test`. Wrapping Playwright's Node runner with the MCR CLI does **not** collect JavaScript executing inside browser pages.
56
+
57
+ Use an automatic test-scoped fixture: start coverage before navigation, run real assertions, then stop and persist coverage before Playwright closes the page. This includes normal assertion failures. Generate once in the coordinator after every worker has finished writing.
58
+
59
+ ### Complete project example
60
+
61
+ [playwright-coverage](https://github.com/cenfun/playwright-coverage) is a minimal **Playwright Test + MCR 3.x** example using plain JavaScript ESM. It tests browser-side application code, not the Playwright test scripts or a Node.js backend. Its [quick start](https://github.com/cenfun/playwright-coverage#quick-start-for-your-own-project) explains how to adapt these five files to your application:
62
+
63
+ | File | Purpose |
64
+ | --- | --- |
65
+ | [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js) | Automatically start/stop page JS/CSS coverage and persist it with `add()`, including ordinary assertion failures. |
66
+ | [mcr.config.js](https://github.com/cenfun/playwright-coverage/blob/main/mcr.config.js) | Share output/report options and keep `cleanCache: false` for workers. |
67
+ | [global.setup.js](https://github.com/cenfun/playwright-coverage/blob/main/global.setup.js) | Clear the dedicated output directory before workers start. |
68
+ | [global-teardown.js](https://github.com/cenfun/playwright-coverage/blob/main/global-teardown.js) | Generate once after all workers finish; reject missing/empty results. |
69
+ | [playwright.config.js](https://github.com/cenfun/playwright-coverage/blob/main/playwright.config.js) | Select Chromium, register hooks, and configure the test runner. |
70
+
71
+ For an existing application, merge these settings, configure its actual `baseURL` and server lifecycle, and import the extended `test` from the fixture. Do not change the application's module system just for coverage; use `.mjs` as below when appropriate. Browser dependencies and binaries must already be installed or their installation approved.
72
+
73
+ The example's [Todo tests](https://github.com/cenfun/playwright-coverage/blob/main/tests/demo-todo-app.spec.js) serve real local HTML/JS/CSS through Playwright routes so the tests need no server or external website. They assert item creation, input clearing, whitespace handling, and text rendering. The routes and Todo assets are only an offline demo, not extra infrastructure to copy into your application.
74
+
75
+ Run `npm test` in the example project. Inspect `coverage-reports/index.html` for code coverage, `coverage-reports/lcov.info` for CI, and `playwright-report/index.html` for test results. These are different artifacts: a passing test report alone does not validate coverage. For JS-only coverage, omit CSS collection; retain sourcemaps and select your real application source scope for bundled code.
76
+
77
+ ### Minimal parallel-safe integration
78
+
79
+ This example assumes `@playwright/test` and MCR 3.x are installed, an approved Chromium binary is available, and the application server is already running. Reuse the host's server lifecycle/readiness checks (such as Playwright `webServer`); do not silently download browsers. Save these files at the host project root except the spec under `e2e/`. Merge the settings into an existing Playwright config rather than replacing its projects or hooks.
80
+
81
+ ```js
82
+ // coverage-options.mjs
83
+ export default {
84
+ outputDir: './coverage-reports/browser',
85
+ reports: ['v8', 'console-summary'],
86
+ // Workers share this cache. Only the coordinator may clean it.
87
+ cleanCache: false
88
+ };
89
+ ```
90
+
91
+ ```js
92
+ // coverage-setup.mjs
93
+ import fs from 'node:fs';
94
+ import options from './coverage-options.mjs';
95
+
96
+ export default function globalSetup() {
97
+ // Dedicated to this run: never share with another active Playwright invocation.
98
+ // Remove old reports as well as cache, so missing collection cannot leave stale artifacts.
99
+ fs.rmSync(options.outputDir, { recursive: true, force: true });
100
+ }
101
+ ```
102
+
103
+ ```js
104
+ // coverage-fixtures.mjs
105
+ import { test as base, expect } from '@playwright/test';
106
+ import { MCR } from 'monocart-coverage-reports';
107
+ import options from './coverage-options.mjs';
108
+
109
+ export const test = base.extend({
110
+ coverage: [async ({ page, browserName }, use) => {
111
+ if (browserName !== 'chromium') {
112
+ // Other browser projects still run tests, but do not provide native coverage.
113
+ await use();
114
+ return;
115
+ }
116
+ await Promise.all([
117
+ page.coverage.startJSCoverage({ resetOnNavigation: false }),
118
+ page.coverage.startCSSCoverage({ resetOnNavigation: false })
119
+ ]);
120
+ try {
121
+ await use();
122
+ } finally {
123
+ const [js, css] = await Promise.all([
124
+ page.coverage.stopJSCoverage(),
125
+ page.coverage.stopCSSCoverage()
126
+ ]);
127
+ await MCR(options).add([...js, ...css]);
128
+ }
129
+ }, { auto: true }]
130
+ });
131
+ export { expect };
132
+ ```
133
+
134
+ ```js
135
+ // coverage-teardown.mjs
136
+ import { MCR } from 'monocart-coverage-reports';
137
+ import options from './coverage-options.mjs';
138
+
139
+ export default async function globalTeardown() {
140
+ const results = await MCR(options).generate();
141
+ if (!results || !results.files.length) {
142
+ throw new Error('No browser coverage generated: check fixtures and source scope');
143
+ }
144
+ }
145
+ ```
146
+
147
+ ```js
148
+ // playwright.config.mjs
149
+ import { defineConfig } from '@playwright/test';
150
+
151
+ export default defineConfig({
152
+ testDir: './e2e',
153
+ globalSetup: './coverage-setup.mjs',
154
+ globalTeardown: './coverage-teardown.mjs',
155
+ use: {
156
+ browserName: 'chromium',
157
+ baseURL: process.env.APP_URL || 'http://localhost:3000'
158
+ }
159
+ });
160
+ ```
161
+
162
+ ```js
163
+ // e2e/counter.spec.mjs
164
+ import { test, expect } from '../coverage-fixtures.mjs';
165
+
166
+ // Assumes a counter starting at 0; replace with your application's actual behavior.
167
+ test('increments the counter when clicked', async ({ page }) => {
168
+ await page.goto('/');
169
+ const counter = page.getByTestId('counter');
170
+ await expect(counter).toHaveText('0');
171
+ await page.getByRole('button', { name: 'Increment', exact: true }).click();
172
+ await expect(counter).toHaveText('1');
173
+ });
174
+ ```
175
+
176
+ ```sh
177
+ npx playwright test
178
+ ```
179
+
180
+ All covered specs must import the extended `test` from the fixture, not directly from `@playwright/test`. Inspect `coverage-reports/browser/index.html`. The example deliberately starts without a source filter: inspect actual bundle URLs and mapped paths, then select application sources using [filtering and sourcemaps](filtering-and-sourcemaps.md). Leaving vendor/runtime code in the report is not an accurate application coverage scope.
181
+
182
+ ### Lifecycle and scope limitations
183
+
184
+ - Native `page.coverage` JS/CSS collection is **Chromium-only**. Firefox/WebKit require a separate instrumentation-based approach, such as collecting an Istanbul map; changing the reporter does not remove this limitation.
185
+ - `resetOnNavigation: false` preserves coverage across navigations in the same page. The fixture covers only its `page`; extra pages/popups, workers, and the Node.js backend require explicit collection strategies. Tests must not close the covered page before fixture teardown.
186
+ - Assertion failures normally run fixture teardown; crashes, forced termination, or timeouts may prevent collection. Retain the original failure and report collection failures rather than claiming coverage is complete.
187
+ - Only the coordinator removes the dedicated output directory before workers start. Clearing only `.cache` would leave old HTML reports available for upload if collection fails. Never point `outputDir` at source code, merge inputs, or another run's output.
188
+ - Do not call `generate()` or `cleanCache()` in each test/worker. `add()` persists data; the coordinator merges it during final generation. `cleanCache: true` in a shared worker would erase other tests' inputs.
189
+ - Give independent CI runs/shards separate output directories. Export `raw` per shard and merge only after validating every expected shard; do not let independent global teardowns race on shared cache. See [reports and CI](reports-and-ci.md).
190
+
191
+ ## 4. Browser alternative: Playwright Test + monocart-reporter
192
+
193
+ Choose this when you also want Monocart's Playwright test report, rather than maintaining a separate MCR reporting lifecycle. **monocart-reporter already integrates MCR**; do not add another MCR generator for the same data.
194
+
195
+ 1. Check the installed reporter's version, runtime requirements, bundled MCR version, and [official code coverage integration](https://github.com/cenfun/monocart-reporter#code-coverage-report). Do not assume its bundled MCR major matches a separate top-level installation or pass unsupported MCR 3.x options.
196
+ 2. Add `monocart-reporter` to the host's Playwright `reporter` configuration and configure its `coverage` options according to that release's documentation.
197
+ 3. Use an automatic Playwright fixture to start/stop page coverage around each test, as above, but submit the data through the reporter's documented coverage API with the current `testInfo`, instead of `MCR(options).add()`.
198
+ 4. Let the reporter own aggregation, cleanup, and final report generation. Do **not** also install the direct-MCR global setup/teardown above. Follow the reporter's own merge workflow for parallel jobs/shards.
199
+ 5. Run `npx playwright test` and verify both the test report and its coverage report contain current, intended data. Enabling a reporter alone does not start browser coverage collection.
200
+
201
+ Use the version-matched official fixture rather than guessing reporter API signatures. Direct MCR is preferable when you need independent coverage lifecycle/options; the reporter is preferable when you want unified test and coverage reporting. Both use the same browser coverage limitations.
202
+
203
+ ## 5. Make coverage trustworthy
204
+
205
+ - **Test behavior, not a percentage.** Add assertions for normal, boundary, error, and async paths. Use uncovered branches to find missing cases; do not weaken assertions or add unjustified ignores to satisfy a threshold.
206
+ - **Match the exact build.** Keep tested source, bundle, and sourcemaps together. For TS/bundlers, verify mapped source and line positions; do not rebuild different artifacts before generating/merging coverage.
207
+ - **Define the denominator.** Exclude only justified out-of-scope dependencies/generated assets, and use `all` when untouched application files must count as uncovered. A high percentage of loaded files alone does not prove the whole application is tested.
208
+ - **Choose useful artifacts.** Start with `v8` and `console-summary`; add `lcovonly` for compatible CI services, `v8-json` for machine validation, or `raw` for later merging. Do not merge HTML, summaries, or percentages.
209
+ - **Fail honestly.** Test, collection, report, and threshold failures must remain nonzero. `watermarks` only colors results; enforce thresholds explicitly. API callers must reject missing/empty results; CI must reject stale artifacts, not just check whether an old HTML file exists. See [strict CI and threshold recipes](reports-and-ci.md).
210
+ - **Keep artifacts safe.** Use dedicated per-run output directories and retain useful reports on failures without masking status. Reports and raw/debug data may contain source code, private paths, and URLs; publish only with approval.
211
+
212
+ ### Delivery checklist
213
+
214
+ - [ ] The chosen runner executes real assertions; collection starts before application execution/navigation.
215
+ - [ ] The report contains the intended current sources, accurate mappings, and any required untested files.
216
+ - [ ] Parallel tests do not clear one another's data; every required shard contributes to the merge.
217
+ - [ ] Reports are fresh and nonempty, and test/report/threshold failure paths fail CI.
218
+ - [ ] The user receives the run command, report location, source scope, and any collection limitations.
@@ -0,0 +1,108 @@
1
+ # CLI and configuration
2
+
3
+ These recipes support MCR 3.x only (Node.js >= 22.15.0). Verify the installed major version and runtime before using them.
4
+
5
+ ## Executable and argument boundaries
6
+
7
+ ```sh
8
+ npx mcr node ./app.js -r v8,console-summary
9
+ npx mcr -c mcr.config.mjs -- npm run test:unit
10
+ npx mcr -c mcr.config.mjs -- node "./path with spaces/app.js"
11
+ ```
12
+
13
+ The separator keeps child flags distinct from MCR flags. In 3.x the child executes through Execa without explicitly enabling shell mode. Do **not** pass a whole command as one string (`mcr "node app.js"`), expect MCR to expand environment variables/globs, or embed `&&`, pipes, and redirections inside a child command string. Move composite steps into an existing npm script or a dedicated script. The outer terminal shell still has its own expansion/redirection rules.
14
+
15
+ Useful MCR options include `-c/--config`, `-r/--reports`, `-o/--outputDir`, `-i/--inputDir`, `-b/--baseDir`, `-a/--all`, `--entryFilter`, `--sourceFilter`, `--outputFilter` (3.x), `--filter`, `--inline`, `--lcov`, `--import`, `--require`, and `--env [path]`. Use a JS config for functions/complex options rather than inventing flags. `--env` without a path loads `.env` on the current runtime; avoid logging secrets.
16
+
17
+ ## Configuration
18
+
19
+ ```js
20
+ // mcr.config.mjs
21
+ export default {
22
+ name: 'Application Coverage',
23
+ outputDir: './coverage-reports',
24
+ reports: ['v8', 'console-summary', 'lcovonly'],
25
+ entryFilter: { '**/node_modules/**': false, '**/src/**': true },
26
+ sourceFilter: { '**/node_modules/**': false, '**/src/**': true }
27
+ };
28
+ ```
29
+
30
+ Choose the filtering approach using the README's Approach trade-offs: input filtering for early exclusion and better performance, or 3.x `outputFilter` for simpler final-path selection at the cost of processing unwanted inputs first. This config chooses input filtering for directly executed `src/` modules. For bundles, change `entryFilter` to allow the actual bundle URLs, then use `sourceFilter` for their unpacked sources. `sourceFilter` alone does not exclude unmapped collected entries. Do not stack both strategies by default; see [filtering-and-sourcemaps.md](filtering-and-sourcemaps.md) for both alternatives.
31
+
32
+ A CommonJS config can remain `mcr.config.cjs` with `module.exports = { ... }`, even though MCR's own API is ESM. `.js` follows the host's `package.json` module type. Do not add `"type": "module"` to an entire host project merely for one config file.
33
+
34
+ Default discovery walks upward from cwd and checks `mcr.config.js`, `.cjs`, `.mjs`, `.json`, then `.ts`. Prefer explicit `-c` when a monorepo could find an unintended parent config. TypeScript configs need an appropriate loader, for example when the host already has `tsx`:
35
+
36
+ ```sh
37
+ npx mcr --import tsx -c mcr.config.ts -- node ./app.js
38
+ ```
39
+
40
+ API loading is explicit:
41
+
42
+ ```js
43
+ import { CoverageReport } from 'monocart-coverage-reports';
44
+ const report = new CoverageReport();
45
+ await report.loadConfig('./mcr.config.mjs');
46
+ await report.addFromDir('./coverage-data');
47
+ const results = await report.generate();
48
+ if (!results) throw new Error('No coverage generated');
49
+ ```
50
+
51
+ Constructor/CLI options override config. In 3.x, an explicit missing config or a discovered but broken config throws/exits nonzero; no `strict` switch is needed. No default config found is allowed. Await and propagate errors rather than generating a misleading default report.
52
+
53
+ ## Built-in Node test runner (3.x)
54
+
55
+ For simple code-validation tasks, write a test with `node:test` and `node:assert/strict`, install `monocart-coverage-reports`, then run `npx mcr test <test-file> -r v8,console-summary`. It runs the tests and automatically generates an interactive V8 report plus a terminal summary without requiring another test framework or a custom coverage collector. See the [complete quick start](../SKILL.md#quick-start-test-your-code-and-get-coverage-3x) for a module and its test. The two reports are an explicitly selected recommendation, not MCR's implicit report default.
56
+
57
+ ```sh
58
+ npx mcr test "./test/**/*.test.js" -r v8,console-summary
59
+ npx mcr test "./test/unit/**/*.test.js" -c mcr.config.mjs -- --test-reporter=spec
60
+ ```
61
+
62
+ Or put targets in config:
63
+
64
+ ```js
65
+ export default {
66
+ test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
67
+ reports: ['v8', 'console-summary'],
68
+ outputDir: './coverage-reports'
69
+ };
70
+ ```
71
+
72
+ - `mcr test` invokes Node's `--test`; it is not a replacement entry for Jest/Mocha/Playwright tests.
73
+ - CLI positional targets **replace** config `test`. Arrays supply separate arguments; a string is one target argument, not a shell command.
74
+ - Quote globs for Node to interpret; options after `--` are forwarded to the Node test runner.
75
+ - With neither CLI targets nor config `test`, it **skips tests and generates no report**. Do not treat such a run as validated coverage.
76
+ - `test` is CLI-only and ignored by ordinary commands/API. Existing runners remain supported through `mcr npm run ...`.
77
+ - Test failure still attempts report generation and retains a nonzero exit status. A report-generation failure must fail CI too. Validate both paths.
78
+
79
+ ## Hooks and shutdown
80
+
81
+ | Hook | Scope / timing |
82
+ | --- | --- |
83
+ | `onStart(report)` | CLI child execution, before spawn |
84
+ | `onReady(report, nodeV8CoverageDir, subprocess)` | CLI, after child exits, before reading inputs |
85
+ | `onEntry(entry)` | V8 input processing; can mutate entry |
86
+ | `onEnd(results)` | After report generation; suitable for thresholds |
87
+
88
+ Hooks may be async. Use `onReady` for a concrete flush/readiness condition when necessary, not an arbitrary permanent delay. API integrations manage their own process lifecycle; they do not receive CLI-only hooks automatically. Preserve test/child failures even if cleanup or reporting also fails.
89
+
90
+ ## API use in CommonJS hosts
91
+
92
+ MCR 3.x uses ESM, but a CommonJS host can use dynamic import without converting its entire application:
93
+
94
+ ```js
95
+ // coverage.cjs
96
+ async function main() {
97
+ const { MCR } = await import('monocart-coverage-reports');
98
+ const report = MCR({ outputDir: './coverage-reports', cleanCache: true });
99
+ await report.addFromDir('./coverage-data');
100
+ if (!await report.generate()) throw new Error('No coverage generated');
101
+ }
102
+ main().catch((error) => {
103
+ console.error(error);
104
+ process.exitCode = 1;
105
+ });
106
+ ```
107
+
108
+ Use ESM-aware TypeScript module settings and verify emitted JavaScript. Secondary `/util` and `/converter` entries provide **named exports**, not defaults. These recipes do not cover other major versions; approved upgrades should follow the package's `migrate-to-3.md`.
@@ -0,0 +1,154 @@
1
+ # Coverage collection recipes
2
+
3
+ Examples use ESM (`.mjs`, or `.js` under `"type": "module"`) and the MCR 3.x runtime contract. Reuse installed browser/test tools; do not install every collector. MCR itself is a Node-side library, not a browser import.
4
+
5
+ ## Choose the input
6
+
7
+ | Runtime / existing data | Collection path | MCR input |
8
+ | --- | --- | --- |
9
+ | Node program or test command | `mcr <executable> <args>` | CLI collects and generates |
10
+ | Node raw V8 directory | `NODE_V8_COVERAGE` before process start | `addFromDir(dir)` or `dataDir` |
11
+ | Playwright Chromium | `page.coverage` | JS + CSS entry array |
12
+ | Puppeteer | Raw script coverage + source text | Converted JS + CSS entry array |
13
+ | Chromium/Node inspector | `CDPClient` | `stopCoverage()` / `stopJSCoverage()` result |
14
+ | Already Istanbul-instrumented program | `window.__coverage__` / `globalThis.__coverage__` | Istanbul map object |
15
+
16
+ Do not assume Chromium V8 coverage works in Firefox/WebKit or that it automatically covers every browser context, page, worker, or remote backend. Explicitly collect the runtimes/targets the task requires.
17
+
18
+ For test suites, prefer the [Playwright Test fixture and lifecycle in MCR best practices](best-practices.md#3-browser-playwright-test--mcr), or its `monocart-reporter` alternative. The standalone script below is for custom collection outside a runner.
19
+
20
+ ## Playwright Chromium: complete standalone script
21
+
22
+ Assumes the application's test server is already running at `APP_URL` (or the local default). Prefer the host's existing server lifecycle and readiness checks.
23
+
24
+ ```js
25
+ // collect-browser.mjs
26
+ import { chromium } from 'playwright';
27
+ import { MCR } from 'monocart-coverage-reports';
28
+
29
+ const report = MCR({
30
+ outputDir: './coverage-reports/browser',
31
+ reports: ['v8', 'console-summary'],
32
+ cleanCache: true
33
+ });
34
+ const browser = await chromium.launch();
35
+ try {
36
+ const page = await browser.newPage();
37
+ await Promise.all([
38
+ page.coverage.startJSCoverage({ resetOnNavigation: false }),
39
+ page.coverage.startCSSCoverage({ resetOnNavigation: false })
40
+ ]);
41
+ await page.goto(process.env.APP_URL || 'http://localhost:3000');
42
+ // Perform real assertions/interactions here before stopping coverage.
43
+ const [js, css] = await Promise.all([
44
+ page.coverage.stopJSCoverage(),
45
+ page.coverage.stopCSSCoverage()
46
+ ]);
47
+ await report.add([...js, ...css]);
48
+ const results = await report.generate();
49
+ if (!results) throw new Error('No browser coverage generated');
50
+ } finally {
51
+ await browser.close();
52
+ }
53
+ ```
54
+
55
+ For test suites, start/stop in the host runner's fixtures/hooks, persist each test's data, and generate **once after all workers finish**. Do not create a `cleanCache: true` report per test against a shared directory. Collect before teardown/navigation destroys the execution target. On failure, retain coverage where possible without replacing the original test failure with a successful exit.
56
+
57
+ Anonymous scripts require deliberate URL/source handling; Playwright CSS coverage does not support anonymous styles without source URLs. Use CDP when that limitation matters.
58
+
59
+ ## Puppeteer: convert the JS data shape
60
+
61
+ Within an existing Puppeteer `page` lifecycle:
62
+
63
+ ```js
64
+ await Promise.all([
65
+ page.coverage.startJSCoverage({
66
+ resetOnNavigation: false,
67
+ includeRawScriptCoverage: true
68
+ }),
69
+ page.coverage.startCSSCoverage({ resetOnNavigation: false })
70
+ ]);
71
+ await page.goto(appUrl); // appUrl is the host application's test URL
72
+ // Exercise and assert the application here.
73
+ const [js, css] = await Promise.all([
74
+ page.coverage.stopJSCoverage(),
75
+ page.coverage.stopCSSCoverage()
76
+ ]);
77
+ const entries = [
78
+ ...js.map((entry) => ({
79
+ ...entry.rawScriptCoverage,
80
+ source: entry.text
81
+ })),
82
+ ...css
83
+ ];
84
+ await report.add(entries); // report is an MCR instance; generate after collection
85
+ ```
86
+
87
+ Do not pass Puppeteer's simplified JS coverage directly as raw V8 function coverage. Preserve `rawScriptCoverage.functions` and the exact matching source text. Close the browser in `finally` after coverage collection.
88
+
89
+ ## CDPClient
90
+
91
+ For Playwright use `await page.context().newCDPSession(page)`; for Puppeteer use `await page.target().createCDPSession()`. Pass the resulting session to MCR:
92
+
93
+ ```js
94
+ import { CDPClient } from 'monocart-coverage-reports';
95
+
96
+ const client = await CDPClient({ session });
97
+ if (!client) throw new Error('Cannot connect coverage client');
98
+ try {
99
+ await client.startCoverage(); // both JS and CSS on a browser target
100
+ // Navigate/exercise the target here.
101
+ const entries = await client.stopCoverage();
102
+ await report.add(entries);
103
+ } finally {
104
+ await client.close();
105
+ }
106
+ ```
107
+
108
+ For a Node debugger target, connect with `{ port: 9229 }` (start the program with `--inspect=9229`) and use `startJSCoverage()` / `stopJSCoverage()`, not CSS methods. Ensure the target remains alive during collection; restrict debugger exposure to trusted/local interfaces.
109
+
110
+ In 3.x, `timeout` controls discovery/connection (default 10000 ms), and `requestTimeout` controls built-in WebSocket commands (default 30000 ms). `0` disables the respective deadline. External Playwright/Puppeteer sessions manage their own command deadlines. Handle disconnects/rejected commands rather than waiting indefinitely.
111
+
112
+ ## Node raw directory
113
+
114
+ The easiest path is the CLI because it also registers source capture. For manual collection, set the environment **before** starting the program. Use a fresh directory per independent run. POSIX shell example:
115
+
116
+ ```sh
117
+ NODE_V8_COVERAGE=./coverage-data node --import monocart-coverage-reports/register ./app.js
118
+ node ./generate-coverage.mjs
119
+ ```
120
+
121
+ In Windows PowerShell, set `$env:NODE_V8_COVERAGE='./coverage-data'` before `node`; remove it afterward with `Remove-Item Env:NODE_V8_COVERAGE`. Or use the host's existing cross-platform environment tooling. Do not add a dependency solely for this example.
122
+
123
+ ```js
124
+ // generate-coverage.mjs
125
+ import { MCR } from 'monocart-coverage-reports';
126
+ const report = MCR({
127
+ outputDir: './coverage-reports/node',
128
+ reports: ['v8', 'lcovonly', 'console-summary'],
129
+ cleanCache: true
130
+ });
131
+ await report.addFromDir('./coverage-data');
132
+ const results = await report.generate();
133
+ if (!results) throw new Error('No Node coverage generated');
134
+ ```
135
+
136
+ Wait for the target/children to finish flushing. Long-lived servers can use `node:v8`'s `takeCoverage()` with `NODE_V8_COVERAGE`, or MCR's `client.writeCoverage()` through a debugger connection. Flushing still requires coordinated shutdown; do not kill the process before writing inputs.
137
+
138
+ A Node coverage JSON file contains a `result` array and may have sourcemap metadata. Prefer `addFromDir()` over manually discarding that metadata. Inspector collection requires fetching/attaching the actual script source. Do not create fake source strings for real application scripts.
139
+
140
+ ## Istanbul input
141
+
142
+ Instrument the tested build with the host's existing Istanbul plugin/tool first. Then collect before the page closes:
143
+
144
+ ```js
145
+ const data = await page.evaluate(() => globalThis.__coverage__);
146
+ if (!data || !Object.keys(data).length) {
147
+ throw new Error('Missing Istanbul coverage: verify instrumentation');
148
+ }
149
+ await report.add(data);
150
+ ```
151
+
152
+ For Node, read `globalThis.__coverage__` in the instrumented process. Use `reports: ['html', 'lcovonly', 'console-summary']` for Istanbul input; native V8 byte/CSS reports cannot be reconstructed from an Istanbul map. MCR does not apply its V8 input filters to Istanbul maps; configure instrumentation/runner scope appropriately.
153
+
154
+ Existing Jest/Vitest coverage should use their compatible MCR integration or raw export path. Do not assume a reporter for one framework can be installed as another framework's reporter, or that an arbitrary JSON report is an MCR raw export.
@@ -0,0 +1,170 @@
1
+ # Filtering, sourcemaps, and untested files
2
+
3
+ ## Choose a filtering strategy
4
+
5
+ For V8 input, choose according to the README's **Approach** comparison, rather than always selecting `outputFilter`. The following comparison is self-contained for package-only/offline use. Both approaches decide which files appear in the report; choose one for the intended scope, not both automatically.
6
+
7
+ | Approach | When and what it filters | Advantages | Trade-offs |
8
+ | --- | --- | --- | --- |
9
+ | `entryFilter` + `sourceFilter` (or `filter`) | Entry URLs during discovery, then source paths as sourcemaps are unpacked | Early exclusion avoids unnecessary downstream work; prefer when performance matters, especially for large projects/bundles | Requires entry/source knowledge; excluding a bundle also removes desired sources inside it |
10
+ | `outputFilter` (3.x only) | Final `sourcePath` after conversion, merging, and adding untested files (`all`) | One filter against the final file list; simpler configuration | Unwanted inputs are still processed first, increasing generation time and memory usage |
11
+
12
+ Use input filtering when performance matters. Choose output filtering when simpler final-file selection is more important and the earlier processing cost is acceptable. If switching strategies, remove old input filters for the same scope: output filtering cannot restore previously excluded inputs.
13
+
14
+ | Option | Receives / matches | Stage |
15
+ | --- | --- | --- |
16
+ | `entryFilter` | Entry object function (`entry.url`), or URL patterns | Before converting bundles |
17
+ | `sourceFilter` | Source path string, or path patterns | Sources unpacked from sourcemaps; also untested files |
18
+ | `filter` | Entry object or source path string | Shorthand for both input filters; not `outputFilter` |
19
+ | `outputFilter` | Final file object function (`entry.sourcePath`), or final path patterns | After sourcemap conversion, merging, and `all` |
20
+
21
+ Example input filtering for **directly executed source modules**, including entries without sourcemaps:
22
+
23
+ ```js
24
+ // input-filter.config.mjs
25
+ export default {
26
+ entryFilter: {
27
+ '**/node_modules/**': false,
28
+ '**/src/**': true
29
+ },
30
+ sourceFilter: {
31
+ '**/node_modules/**': false,
32
+ '**/src/**': true
33
+ }
34
+ };
35
+ ```
36
+
37
+ For a **bundled build**, allow the actual application bundle URLs instead (adapt `dist/` and vendor rules to the tested build):
38
+
39
+ ```js
40
+ // bundle-filter.config.mjs
41
+ export default {
42
+ entryFilter: {
43
+ '**/node_modules/**': false,
44
+ '**/vendor.js': false,
45
+ '**/dist/**': true
46
+ },
47
+ sourceFilter: {
48
+ '**/node_modules/**': false,
49
+ '**/src/**': true
50
+ }
51
+ };
52
+ ```
53
+
54
+ `sourceFilter` does not filter already collected entries without sourcemaps. For directly executed sources, restrict their URLs with `entryFilter`, or use 3.x `outputFilter` to limit the final file list. A permissive `entryFilter` plus `sourceFilter: '**/src/**'` can still include unmapped test/helper scripts.
55
+
56
+ An entry may be `dist/main.js` but contain the desired `src/*` sources. Filtering entries to `**/src/**` would discard that bundle before its sources can be recovered.
57
+
58
+ Alternative final-file selection:
59
+
60
+ ```js
61
+ // output-filter.config.mjs
62
+ export default {
63
+ outputFilter: {
64
+ '**/node_modules/**': false,
65
+ '**/src/**': true
66
+ }
67
+ };
68
+ // Or: outputFilter: (entry) => entry.sourcePath.startsWith('src/')
69
+ ```
70
+
71
+ - Ordered pattern objects use **first match wins**; unmatched files are excluded. Put exclusions before a broad inclusion. Quote CLI patterns to avoid shell expansion.
72
+ - Glob wildcards do not necessarily include dot-prefixed path segments (for example `.temp` or framework build directories). Inspect actual URLs/paths and use an explicit pattern for the hidden segment or a function filter when necessary; do not assume `**/*` includes every generated entry.
73
+ - `sourceFilter` functions receive a string; `outputFilter` functions receive an object. Do not interchange signatures.
74
+ - `outputFilter` matches paths **after `sourcePath` rewriting**, cannot recover entries removed by earlier filters, and does not avoid earlier conversion cost.
75
+ - `outputFilter` affects V8 reports and Istanbul reports converted from V8, but not raw exports or Istanbul input maps.
76
+ - When migrating a scope to `outputFilter`, remove old input filters for that same scope unless their intersection is intentional.
77
+
78
+ ## Build the tested code with usable maps
79
+
80
+ Generate sourcemaps for the exact build that tests execute, preferably with original source content available. Common coverage-build settings:
81
+
82
+ | Build tool | Settings |
83
+ | --- | --- |
84
+ | Vite | `build: { sourcemap: true, minify: false }` |
85
+ | webpack | `devtool: 'source-map'`, `mode: 'development'` |
86
+ | Rollup | `output.sourcemap: true`, `treeshake: false` |
87
+ | esbuild | `sourcemap: true`, `treeShaking: false`, `minify: false` |
88
+
89
+ Change only the intended test/coverage build, not production settings without approval. Minification/transformation can make branch mapping approximate; MCR cannot promise perfect mapping of arbitrary transformed code. Ensure external maps are reachable or attach matching `entry.sourceMap` data; do not substitute maps from a different build.
90
+
91
+ ## Normalize paths for merging
92
+
93
+ Use a shared `baseDir` when collection/generation working directories differ. Use `sourcePath` to remove known build-specific virtual prefixes, preserving unique business paths:
94
+
95
+ ```js
96
+ const options = {
97
+ sourcePath: (filePath) => {
98
+ const prefixes = ['unit-dist/', 'e2e-dist/'];
99
+ const prefix = prefixes.find((value) => filePath.startsWith(value));
100
+ return prefix ? filePath.slice(prefix.length) : filePath;
101
+ }
102
+ };
103
+ ```
104
+
105
+ Only rewrite prefixes actually observed in reports. Do not collapse everything to basename: unrelated `index.ts` files would collide. Normalize the same logical source consistently across server/client/shards, and verify that supposedly identical paths refer to the same source version.
106
+
107
+ ## Include untested files
108
+
109
+ By default, files never loaded are absent, not automatically 0%. Choose the real application directories:
110
+
111
+ ```js
112
+ const options = {
113
+ all: {
114
+ dir: ['./src'],
115
+ filter: {
116
+ '**/*.test.js': false,
117
+ '**/*.spec.js': false,
118
+ '**/*.js': true,
119
+ '**/*.css': 'css'
120
+ }
121
+ },
122
+ sourceFilter: '**/src/**'
123
+ };
124
+ ```
125
+
126
+ `all.filter` runs before `sourceFilter`. With 3.x final filtering, `outputFilter` also applies after untested files are added. Current checkout behavior can generate an all-untested V8 report with no collected entries; treat this separately from unexpected missing collection data.
127
+
128
+ For TS/JSX/Vue sources that the JS AST parser cannot directly parse, use an appropriate transformer already present in the host. Example for **TypeScript without JSX**, if `@swc/core` is installed:
129
+
130
+ ```js
131
+ // untested-typescript.config.mjs
132
+ import { fileURLToPath } from 'node:url';
133
+ import { transform } from '@swc/core';
134
+
135
+ export default {
136
+ all: {
137
+ dir: './src',
138
+ filter: '**/*.ts',
139
+ transformer: async (entry) => {
140
+ const { code, map } = await transform(entry.source, {
141
+ filename: fileURLToPath(entry.url),
142
+ sourceMaps: true,
143
+ isModule: true,
144
+ jsc: { parser: { syntax: 'typescript' } }
145
+ });
146
+ entry.source = code;
147
+ entry.sourceMap = JSON.parse(map);
148
+ }
149
+ }
150
+ };
151
+ ```
152
+
153
+ SWC returns its map as a JSON string; parse it before assigning `entry.sourceMap`. Preserve the full filename so different directories' `index.ts` files retain distinct source paths. Use a consistent `baseDir` when generating or merging.
154
+
155
+ Use the matching JSX/TSX or Vue compiler for those languages; this is not a universal `.vue` transformer. `all.transformer` handles untested sources; `onEntry` is the separate per-collected-V8-entry hook. Preserve sources/maps/offsets consistently when transforming collected data.
156
+
157
+ ## Intentional ignores
158
+
159
+ MCR supports `/* v8 ignore next */`, `/* v8 ignore next 3 */`, and `/* v8 ignore start */` / `/* v8 ignore stop */`. `v8Ignore` defaults to true. Verify that the comments survive the tested build. Do not add ignore comments or exclude difficult files merely to raise coverage; explain and agree on legitimate exceptions.
160
+
161
+ ## Debugging checklist
162
+
163
+ 1. Verify collection started before module import/navigation, and that entries include expected URLs and real source text.
164
+ 2. Temporarily inspect entry URLs and mapped source paths before tightening filters. A wrong stage/pattern is a common cause of empty reports.
165
+ 3. Rebuild without minification; verify maps and source content match the executing bundle.
166
+ 4. Use `{ logging: 'debug', sourceMap: true }` to retain cache/source/map files for investigation. Debug mode also retains generated dist entries in V8 results, so its file list can differ from normal mode.
167
+ 5. Compare generated versus original ranges and inspect a representative covered/uncovered branch. Never fix results by editing coverage counts.
168
+ 6. For large inputs, filter early and reduce unnecessary maps/entries before raising memory limits. The `gc` option is a heap threshold in MB; `NODE_OPTIONS=--max-old-space-size=8192` is a separate Node heap setting. Measure rather than promising either will solve OOM.
169
+
170
+ Debug/raw artifacts and inline HTML can contain source code, URLs, and internal paths. Apply the host's artifact-retention and publication rules; do not upload private sources to public services without permission.