monocart-coverage-reports 2.12.10 → 2.12.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  * [Filtering Results](#filtering-results)
27
27
  * [Resolve `sourcePath` for the Source Files](#resolve-sourcepath-for-the-source-files)
28
28
  * [Adding Empty Coverage for Untested Files](#adding-empty-coverage-for-untested-files)
29
- * [onEnd Hook](#onend-hook)
29
+ * [Hooks](#hooks)
30
30
  * [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
31
31
  * [Multiprocessing Support](#multiprocessing-support)
32
32
  * [Command Line](#command-line)
@@ -89,6 +89,8 @@ import { CoverageReport } from 'monocart-coverage-reports';
89
89
  const mcr = new CoverageReport();
90
90
  await mcr.loadConfig();
91
91
  ```
92
+ > Both `MCR({...})` and `new CoverageReport()` create the same instance. `MCR()` is a shorthand factory function; use `new CoverageReport()` when you prefer class instantiation or need to call `loadConfig()` separately.
93
+
92
94
  For more information, see [Multiprocessing Support](#multiprocessing-support)
93
95
 
94
96
  - CLI
@@ -102,9 +104,38 @@ For more information, see [Command Line](#command-line)
102
104
  - Options declaration see `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
103
105
  - [Config file](#config-file)
104
106
 
107
+ | Option | Type | Default | Description |
108
+ | :-- | :-- | :-- | :-- |
109
+ | `logging` | `"off" \| "error" \| "info" \| "debug"` | `"info"` | Logging level. Use `"debug"` to keep raw cache and dump sourcemaps for troubleshooting. See [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap). |
110
+ | `name` | `string` | `"Coverage Report"` | Report title shown in the UI and summary reports. |
111
+ | `reports` | `string \| (string \| ReportDescription)[]` | `""` (auto) | Reports to generate. Defaults to `"v8"` for V8 data and `"html"` for Istanbul data. See [Available Reports](#available-reports). |
112
+ | `outputDir` | `string` | `"./coverage-reports"` | Output directory for all reports and the `.cache` folder. |
113
+ | `inputDir` | `string \| string[]` | `null` | Input directories of raw coverage data for [merging](#merge-coverage-reports). |
114
+ | `baseDir` | `string` | `process.cwd()` | Base dir used to normalize relative source paths. Set when the report is generated from a different working directory than the sources. |
115
+ | `dataDir` | `string` | `null` | Coverage data directory loaded automatically in `generate()` — alternative to `addFromDir()`. |
116
+ | `entryFilter` | `string \| object \| function` | `null` | (V8 only) Filter V8 entries by url. See [Filtering Results](#filtering-results). |
117
+ | `sourceFilter` | `string \| object \| function` | `null` | (V8 only) Filter sources unpacked from sourcemaps. |
118
+ | `filter` | `string \| object \| function` | `null` | Combined filter replacing both `entryFilter` and `sourceFilter`. |
119
+ | `sourcePath` | `object \| function` | `null` | Rewrite source paths; see [Resolve `sourcePath`](#resolve-sourcepath-for-the-source-files). |
120
+ | `all` | `string \| string[] \| object` | `null` | Include untested files as empty coverage. See [Adding Empty Coverage](#adding-empty-coverage-for-untested-files). |
121
+ | `outputFile` | `string` | `"index.html"` | (V8 only) Output `[sub dir/]filename` for the V8 report. |
122
+ | `inline` | `boolean` | `false` | (V8 only) Inline all assets into a single HTML file. |
123
+ | `assetsPath` | `string` | `"./assets"` | (V8 only) Assets directory when `inline` is false. |
124
+ | `lcov` | `boolean` | `false` | Also generate `lcov.info` (equivalent to adding the `lcovonly` report). |
125
+ | `v8Ignore` | `boolean` | `true` | (V8 only) Enable/disable comment-based ignoring (`v8 ignore …`). See [Ignoring Uncovered Codes](#ignoring-uncovered-codes). |
126
+ | `reportPath` | `string \| () => string` | `null` | Override the report entry path (defaults to `outputDir/index.html`). Useful when multiple reports share an `outputDir`. |
127
+ | `watermarks` | `[number, number] \| object` | `[50, 80]` | Low/high thresholds (percent). Accepts per-metric object like `{ bytes:[50,80], lines:[50,80] }`. |
128
+ | `clean` | `boolean` | `true` | Clean previous reports in `outputDir` before generating (the `.cache` dir is preserved unless `cleanCache` is also `true`). |
129
+ | `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). |
131
+ | `sourceMap` | `boolean` | `false` | Save source/sourcemap files to cache for debugging (requires `logging: "debug"`). |
132
+ | `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | Custom sourcemap loader (e.g. from a build cache). Call `defaultResolver(url)` to fall back. |
133
+ | `onEntry` | `(entry) => void \| Promise<void>` | `null` | (V8 only) Per-entry hook; see [Hooks](#hooks). |
134
+ | `onEnd` | `(coverageResults) => void \| Promise<void>` | `null` | Hook invoked after report generation; see [Hooks](#hooks). |
135
+
105
136
  ## Available Reports
106
137
 
107
- > V8 build-in reports (V8 data only):
138
+ > V8 built-in reports (V8 data only):
108
139
 
109
140
  - `v8`
110
141
  - Features:
@@ -124,7 +155,7 @@ For more information, see [Command Line](#command-line)
124
155
 
125
156
  ![](./assets/mcv.gif)
126
157
 
127
- > Istanbul build-in reports (both V8 and Istanbul data):
158
+ > Istanbul built-in reports (both V8 and Istanbul data):
128
159
 
129
160
  - `clover`
130
161
  - `cobertura`
@@ -144,7 +175,7 @@ For more information, see [Command Line](#command-line)
144
175
  - `text-lcov`
145
176
  - `text-summary`
146
177
 
147
- > Other build-in reports (both V8 and Istanbul data):
178
+ > Other built-in reports (both V8 and Istanbul data):
148
179
 
149
180
  - `codecov` Save coverage data to a json file with [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format) format (defaults to `codecov.json`), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports).
150
181
 
@@ -192,13 +223,18 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
192
223
  - V8 custom reporter
193
224
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
194
225
 
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`.
230
+
195
231
  ### Multiple Reports:
196
232
  ```js
197
233
  const MCR = require('monocart-coverage-reports');
198
234
  const coverageOptions = {
199
235
  outputDir: './coverage-reports',
200
236
  reports: [
201
- // build-in reports
237
+ // built-in reports
202
238
  ['console-summary'],
203
239
  ['v8'],
204
240
  ['html', {
@@ -464,7 +500,7 @@ export interface CoverageRange {
464
500
  * @functionName can be an empty string.
465
501
  * @ranges is always non-empty. The first range is called the "root range".
466
502
  * @isBlockCoverage indicates if the function has block coverage information.
467
- If this is false, it usually means that the functions was never called.
503
+ If this is false, it usually means that the function was never called.
468
504
  It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
469
505
  */
470
506
  export interface FunctionCoverage {
@@ -495,7 +531,22 @@ export type V8CoverageData = ScriptCoverage[];
495
531
  | Firefox (2%) | ❌ | |
496
532
  | Node.js | ✅ | |
497
533
  | Deno | ❌ | [issue](https://github.com/denoland/deno/issues/23359) |
498
- | Bun | ❌ | |
534
+ | Bun | ❌ | [issue](https://github.com/oven-sh/bun/issues/30968) |
535
+
536
+ > MCR extends each V8 coverage entry with additional fields:
537
+
538
+ | Field | Type | Description |
539
+ | :-- | :-- | :-- |
540
+ | `url` | `string` | Script URL or file path |
541
+ | `type` | `"js" \| "css"` | Entry type |
542
+ | `source` | `string` | (JS) Source code text |
543
+ | `sourceMap` | `object` | (JS) Parsed sourcemap object |
544
+ | `scriptOffset` | `number` | (JS) Byte offset for the script wrapper (e.g. in `vm` modules). This offset is automatically added to all range start/end values |
545
+ | `distFile` | `string` | (JS) The dist bundle file this source was unpacked from |
546
+ | `empty` | `boolean` | Marked as `true` when this entry was added via the `all` option (no real coverage data), showing 0% coverage |
547
+ | `fake` | `boolean` | Marked as `true` when the source code is not original (e.g. compiled from `.ts`/`.jsx`). The AST parser skips coverage extraction for fake sources |
548
+
549
+ These fields can be read/mutated in [`onEntry`](#hooks) hooks.
499
550
 
500
551
  ## Filtering Results
501
552
  ### Using `entryFilter` and `sourceFilter` to filter the results for V8 report
@@ -572,7 +623,7 @@ const coverageOptions = {
572
623
  filter: {
573
624
  '**/node_modules/**': false,
574
625
  '**/vendor.js': false,
575
- '**/src/**': true
626
+ '**/src/**': true,
576
627
  '**/**': true
577
628
  }
578
629
  };
@@ -672,8 +723,10 @@ const coverageOptions = {
672
723
  };
673
724
  ```
674
725
 
675
- ## onEnd Hook
676
- For example, checking thresholds:
726
+ ## Hooks
727
+
728
+ ### `onEnd` — after the report is generated
729
+ Typical use case: enforce coverage thresholds.
677
730
  ```js
678
731
  const EC = require('eight-colors');
679
732
  const coverageOptions = {
@@ -703,6 +756,29 @@ const coverageOptions = {
703
756
  }
704
757
  ```
705
758
 
759
+ ### `onEntry` — before each V8 entry is processed (V8 only)
760
+ 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
+ ```js
762
+ const coverageOptions = {
763
+ onEntry: async (entry) => {
764
+ // entry.source, entry.sourceMap, entry.fake, ...
765
+ }
766
+ };
767
+ ```
768
+
769
+ ### `onStart` / `onReady` — CLI-only
770
+ Only available when using `mcr <command>`:
771
+ - `onStart(coverageReport)` — runs before the child process is spawned.
772
+ - `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
+ ```js
774
+ // mcr.config.js
775
+ module.exports = {
776
+ onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
777
+ // wait for something, or pre-process the raw files
778
+ }
779
+ };
780
+ ```
781
+
706
782
  ## Ignoring Uncovered Codes
707
783
  To ignore codes, use the special comment which starts with `v8 ignore `:
708
784
  - Ignoring all until stop
@@ -715,9 +791,9 @@ function uncovered() {
715
791
  - Ignoring the next line or next N lines
716
792
  ```js
717
793
  /* v8 ignore next */
718
- const os = platform === 'wind32' ? 'Windows' : 'Other';
794
+ const os = platform === 'win32' ? 'Windows' : 'Other';
719
795
 
720
- const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
796
+ const os = platform === 'win32' ? 'Windows' /* v8 ignore next */ : 'Other';
721
797
 
722
798
  // v8 ignore next 3
723
799
  if (platform === 'linux') {
@@ -736,6 +812,7 @@ function uncovered() {
736
812
  }
737
813
  /* node:coverage enable */
738
814
  ```
815
+ - To disable comment-based ignoring entirely, set `v8Ignore: false`.
739
816
 
740
817
  ## Multiprocessing Support
741
818
  > 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)
@@ -790,13 +867,41 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
790
867
  ```
791
868
 
792
869
  - CLI Options
793
- see all options with running `mcr` or `mcr --help`
870
+
871
+ The options below are only available when using the CLI (`mcr <command>`):
872
+
873
+ | Option | Type | Default | Description |
874
+ | :-- | :-- | :-- | :-- |
875
+ | `-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` |
879
+ | `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
880
+ | `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
881
+
882
+ For other options (e.g. `--name`, `--reports`, `--outputDir`, `--lcov`, etc.), see the [Options](#options) table — they work the same way in CLI.
794
883
 
795
884
  - Use `--` to separate sub CLI args
796
885
  ```sh
797
886
  mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
798
887
  ```
799
888
 
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`.
890
+ ```sh
891
+ mcr --import tsx node ./test.ts
892
+ ```
893
+
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`.
895
+ ```sh
896
+ mcr --env .env.test node ./test.js
897
+ ```
898
+
899
+ - `merge`: merge raw coverage data from `inputDir`(s) and generate merged reports directly, without running a child process.
900
+ ```sh
901
+ mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
902
+ ```
903
+ See [Merge Coverage Reports](#merge-coverage-reports).
904
+
800
905
  - Examples
801
906
  - [Mocha](#mocha)
802
907
  - [TypeScript](#typescript)
@@ -811,7 +916,12 @@ Loading config file by priority:
811
916
  - `mcr.config.cjs`
812
917
  - `mcr.config.mjs`
813
918
  - `mcr.config.json` - json format
814
- - `mcr.config.ts` (requires preloading the ts execution module)
919
+ - `mcr.config.ts` (requires preloading the TypeScript execution module, for example with `tsx`):
920
+ ```sh
921
+ mcr --import tsx -c mcr.config.ts node ./test.js
922
+ ```
923
+
924
+ > 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.
815
925
 
816
926
  ## Merge Coverage Reports
817
927
  The following usage scenarios may require merging coverage reports:
@@ -890,7 +1000,7 @@ const coverageOptions = {
890
1000
  ],
891
1001
 
892
1002
  onEnd: () => {
893
- // remove the raw files if it useless
1003
+ // remove the raw files if no longer needed
894
1004
  // inputDir.forEach((p) => {
895
1005
  // fs.rmSync(p, {
896
1006
  // recursive: true,
@@ -926,8 +1036,8 @@ In the generated code, there is a position `p`, and we need to find out its corr
926
1036
  - Further understanding of sourcemap, try [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
927
1037
 
928
1038
  How `MCR` Works:
929
- - 1, Trying to fix the original position with string comparison and [`diff-sequences`](https://github.com/jestjs/jest/tree/main/packages/diff-sequences). However, for non-JS code, such as Vue template, JSX, etc., it might be hard to find a perfect solution.
930
- - 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). (There is a small issue is the V8 cannot provide effective branch coverage information for `AssignmentPattern`)
1039
+ 1. Tries to fix the original position with string comparison and [`diff-sequences`](https://github.com/jestjs/jest/tree/main/packages/diff-sequences). However, for non-JS code such as Vue templates or JSX, a perfect solution may not always be possible.
1040
+ 2. Finds all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). (One small caveat: V8 cannot provide effective branch coverage information for `AssignmentPattern`.)
931
1041
 
932
1042
 
933
1043
  ### Unparsable source
@@ -971,7 +1081,7 @@ const coverageOptions = {
971
1081
  ]
972
1082
  };
973
1083
  ```
974
- When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless.
1084
+ When `logging` is `debug`, the raw report data is preserved in `[outputDir]/.cache` (or `[outputDir]/raw` if the `raw` report is used). The dist file is also kept in the V8 list, so you can open the browser's devtools and visually verify the coverage data.
975
1085
  ![](./assets/debug-coverage.png)
976
1086
 
977
1087
  - Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/)
package/README.zh-Hans.md CHANGED
@@ -26,7 +26,7 @@
26
26
  * [过滤V8覆盖率数据](#filtering-results)
27
27
  * [使用 `sourcePath` 修改源文件路径](#resolve-sourcepath-for-the-source-files)
28
28
  * [为未测试的文件添加空的覆盖率报告](#adding-empty-coverage-for-untested-files)
29
- * [onEnd回调函数](#onend-hook)
29
+ * [回调钩子 Hooks](#hooks)
30
30
  * [如何忽略未覆盖的代码](#ignoring-uncovered-codes)
31
31
  * [多进程支持](#multiprocessing-support)
32
32
  * [如何使用CLI命令行](#command-line)
@@ -89,6 +89,8 @@ import { CoverageReport } from 'monocart-coverage-reports';
89
89
  const mcr = new CoverageReport();
90
90
  await mcr.loadConfig();
91
91
  ```
92
+ > `MCR({...})` 和 `new CoverageReport()` 创建的是同一个实例。`MCR()` 是简写工厂函数;`new CoverageReport()` 更适合需要单独调用 `loadConfig()` 的场景。
93
+
92
94
  参见 [多进程支持](#multiprocessing-support)
93
95
 
94
96
  - CLI
@@ -102,6 +104,35 @@ mcr node my-app.js -r v8,console-details
102
104
  - 选项的类型描述,见 `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
103
105
  - [配置文件](#config-file)
104
106
 
107
+ | 选项 | 类型 | 默认值 | 说明 |
108
+ | :-- | :-- | :-- | :-- |
109
+ | `logging` | `"off" \| "error" \| "info" \| "debug"` | `"info"` | 日志级别。设为 `"debug"` 会保留原始缓存并输出 sourcemap,便于排查问题。参见 [调试覆盖率和sourcemap](#debug-for-coverage-and-sourcemap)。 |
110
+ | `name` | `string` | `"Coverage Report"` | 报告标题,显示在 UI 以及 summary 报告中。 |
111
+ | `reports` | `string \| (string \| ReportDescription)[]` | `""` (自动) | 生成哪些报告。V8 数据默认 `"v8"`,Istanbul 数据默认 `"html"`。参见 [所有支持的报告类型](#available-reports)。 |
112
+ | `outputDir` | `string` | `"./coverage-reports"` | 所有报告以及 `.cache` 的输出目录。 |
113
+ | `inputDir` | `string \| string[]` | `null` | 导入的原始覆盖率数据目录(用于 [合并](#merge-coverage-reports))。 |
114
+ | `baseDir` | `string` | `process.cwd()` | 规范化相对源路径的基准目录。当生成报告的工作目录和源码目录不一致时需要设置。 |
115
+ | `dataDir` | `string` | `null` | `generate()` 阶段自动加载的覆盖率数据目录,作为 `addFromDir()` 的替代。 |
116
+ | `entryFilter` | `string \| object \| function` | `null` | (仅 V8)按 URL 过滤入口文件,参见 [过滤V8覆盖率数据](#filtering-results)。 |
117
+ | `sourceFilter` | `string \| object \| function` | `null` | (仅 V8)过滤从 sourcemap 解包出的源文件。 |
118
+ | `filter` | `string \| object \| function` | `null` | 合并 `entryFilter` 和 `sourceFilter` 的统一过滤器。 |
119
+ | `sourcePath` | `object \| function` | `null` | 修改源文件路径,参见 [使用 `sourcePath`](#resolve-sourcepath-for-the-source-files)。 |
120
+ | `all` | `string \| string[] \| object` | `null` | 为未测试文件添加空覆盖率,参见 [为未测试的文件添加空的覆盖率报告](#adding-empty-coverage-for-untested-files)。 |
121
+ | `outputFile` | `string` | `"index.html"` | (仅 V8)V8 报告的 `[子目录/]文件名`。 |
122
+ | `inline` | `boolean` | `false` | (仅 V8)将所有资源内联到单一 HTML 文件。 |
123
+ | `assetsPath` | `string` | `"./assets"` | (仅 V8)非内联模式下的资源目录。 |
124
+ | `lcov` | `boolean` | `false` | 额外生成 `lcov.info`,等同于添加 `lcovonly` 报告。 |
125
+ | `v8Ignore` | `boolean` | `true` | (仅 V8)启用/禁用基于注释的忽略(`v8 ignore …`),参见 [忽略未覆盖代码](#ignoring-uncovered-codes)。 |
126
+ | `reportPath` | `string \| () => string` | `null` | 覆盖默认的报告入口路径(默认 `outputDir/index.html`)。多个报告共用同一 `outputDir` 时很有用。 |
127
+ | `watermarks` | `[number, number] \| object` | `[50, 80]` | 覆盖率高/低阈值百分比。也支持按指标配置:`{ bytes:[50,80], lines:[50,80] }`。 |
128
+ | `clean` | `boolean` | `true` | 生成报告前清理 `outputDir` 中的旧报告(`.cache` 目录在 `cleanCache` 也为 `true` 时才会被清理)。 |
129
+ | `cleanCache` | `boolean` | `false` | 启动时清理缓存目录。当 `clean` 和 `cleanCache` 都为 `false` 时,之前运行的缓存数据会保留,可在多进程间复用。 |
130
+ | `gc` | `number` | `null` | 内存阈值(MB),在关键阶段若 RSS 超过阈值则强制触发 GC,适用于超大数据集,参见 [JavaScript heap out of memory](#javascript-heap-out-of-memory)。 |
131
+ | `sourceMap` | `boolean` | `false` | 将源文件和 sourcemap 也保存到 cache 便于调试(需要 `logging: "debug"`)。 |
132
+ | `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | 自定义 sourcemap 加载逻辑(例如从构建缓存中读取),可调用 `defaultResolver(url)` 回退到默认解析。 |
133
+ | `onEntry` | `(entry) => void \| Promise<void>` | `null` | (仅 V8)每个入口文件处理前触发,参见 [Hooks](#hooks)。 |
134
+ | `onEnd` | `(coverageResults) => void \| Promise<void>` | `null` | 报告生成完成后触发,参见 [Hooks](#hooks)。 |
135
+
105
136
  ## Available Reports
106
137
 
107
138
  > 内置V8报告(仅V8格式数据支持):
@@ -192,6 +223,11 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
192
223
  - V8自定义报告
193
224
  > 例子: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
194
225
 
226
+ `type` 选项决定报告接收的覆盖率数据格式:
227
+ - `'istanbul'` — 报告接收 Istanbul 格式的覆盖率数据(`coverageResults.istanbulData`)。
228
+ - `'v8'` — 报告接收原生 V8 格式的覆盖率数据(`coverageResults.v8Data`)。
229
+ - `'both'` — 报告同时接收两种格式,可通过 `coverageResults.v8Data` 和 `coverageResults.istanbulData` 访问。
230
+
195
231
  ### Multiple Reports:
196
232
  如何配置多个报告
197
233
  ```js
@@ -199,7 +235,7 @@ const MCR = require('monocart-coverage-reports');
199
235
  const coverageOptions = {
200
236
  outputDir: './coverage-reports',
201
237
  reports: [
202
- // build-in reports
238
+ // built-in reports
203
239
  ['console-summary'],
204
240
  ['v8'],
205
241
  ['html', {
@@ -468,7 +504,7 @@ export interface CoverageRange {
468
504
  * @functionName can be an empty string.
469
505
  * @ranges is always non-empty. The first range is called the "root range".
470
506
  * @isBlockCoverage indicates if the function has block coverage information.
471
- If this is false, it usually means that the functions was never called.
507
+ If this is false, it usually means that the function was never called.
472
508
  It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
473
509
  */
474
510
  export interface FunctionCoverage {
@@ -499,10 +535,25 @@ export type V8CoverageData = ScriptCoverage[];
499
535
  | Firefox (2%) | ❌ | |
500
536
  | Node.js | ✅ | |
501
537
  | Deno | ❌ | [issue](https://github.com/denoland/deno/issues/23359) |
502
- | Bun | ❌ | |
538
+ | Bun | ❌ | [issue](https://github.com/oven-sh/bun/issues/30968) |
539
+
540
+ > MCR 为每个 V8 覆盖率条目扩展了以下字段:
541
+
542
+ | 字段 | 类型 | 说明 |
543
+ | :-- | :-- | :-- |
544
+ | `url` | `string` | 脚本 URL 或文件路径 |
545
+ | `type` | `"js" \| "css"` | 条目类型 |
546
+ | `source` | `string` | (JS)源代码文本 |
547
+ | `sourceMap` | `object` | (JS)已解析的 sourcemap 对象 |
548
+ | `scriptOffset` | `number` | (JS)脚本包裹层的字节偏移(例如 `vm` 模块中)。此偏移会自动加到所有 range 的 start/end 值上 |
549
+ | `distFile` | `string` | (JS)该 source 解包自的 dist 打包文件 |
550
+ | `empty` | `boolean` | 当条目通过 `all` 选项添加(无真实覆盖率数据)时标记为 `true`,显示为 0% 覆盖 |
551
+ | `fake` | `boolean` | 当源代码非原始代码(例如从 `.ts`/`.jsx` 编译而来)时标记为 `true`。AST 解析器会跳过 fake 源的覆盖率提取 |
552
+
553
+ 这些字段可以在 [`onEntry`](#hooks) 钩子中读取或修改。
503
554
 
504
555
  ## Filtering Results
505
- ## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
556
+ ### Using `entryFilter` and `sourceFilter` to filter the results for V8 report
506
557
  当收集到V8的覆盖数据时,它实际上包含了所有的入口文件的覆盖率数据, 比如有以下3个文件:
507
558
 
508
559
  - *dist/main.js*
@@ -576,7 +627,7 @@ const coverageOptions = {
576
627
  filter: {
577
628
  '**/node_modules/**': false,
578
629
  '**/vendor.js': false,
579
- '**/src/**': true
630
+ '**/src/**': true,
580
631
  '**/**': true
581
632
  }
582
633
  };
@@ -675,8 +726,10 @@ const coverageOptions = {
675
726
  };
676
727
  ```
677
728
 
678
- ## onEnd Hook
679
- 结束回调可以用来自定义业务需求,比如检测覆盖率是否达标,对比每个指标的thresholds,如果低于要求的值则可以抛出一个错误退出
729
+ ## Hooks
730
+
731
+ ### `onEnd` — 报告生成后触发
732
+ 典型用法:检测覆盖率是否达标,低于 threshold 时抛错退出。
680
733
  ```js
681
734
  const EC = require('eight-colors');
682
735
  const coverageOptions = {
@@ -706,6 +759,29 @@ const coverageOptions = {
706
759
  }
707
760
  ```
708
761
 
762
+ ### `onEntry` — 每个V8入口在处理前触发(仅V8)
763
+ 可以在进入AST解析前改写 entry(例如对非标准语法的源码做预编译),参见 [Unparsable source](#unparsable-source)
764
+ ```js
765
+ const coverageOptions = {
766
+ onEntry: async (entry) => {
767
+ // entry.source, entry.sourceMap, entry.fake, ...
768
+ }
769
+ };
770
+ ```
771
+
772
+ ### `onStart` / `onReady` — 仅CLI可用
773
+ 只在使用 `mcr <command>` 时有效:
774
+ - `onStart(coverageReport)` — 子进程启动前触发
775
+ - `onReady(coverageReport, nodeV8CoverageDir, subprocess)` — 子进程退出后、MCR 读取 `nodeV8CoverageDir` 之前触发。用于子进程尚未写完覆盖数据需要等待的场景
776
+ ```js
777
+ // mcr.config.js
778
+ module.exports = {
779
+ onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
780
+ // 等待某个信号,或先对 raw 文件做一些预处理
781
+ }
782
+ };
783
+ ```
784
+
709
785
  ## Ignoring Uncovered Codes
710
786
  使用特定的注释,以`v8 ignore `开头可以忽略未覆盖的代码:
711
787
  - 忽略开始到结束
@@ -718,9 +794,9 @@ function uncovered() {
718
794
  - 忽略接下来一行或者多行
719
795
  ```js
720
796
  /* v8 ignore next */
721
- const os = platform === 'wind32' ? 'Windows' : 'Other';
797
+ const os = platform === 'win32' ? 'Windows' : 'Other';
722
798
 
723
- const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
799
+ const os = platform === 'win32' ? 'Windows' /* v8 ignore next */ : 'Other';
724
800
 
725
801
  // v8 ignore next 3
726
802
  if (platform === 'linux') {
@@ -739,6 +815,7 @@ function uncovered() {
739
815
  }
740
816
  /* node:coverage enable */
741
817
  ```
818
+ - 可以设置 `v8Ignore: false` 完全禁用基于注释的忽略机制。
742
819
 
743
820
  ## Multiprocessing Support
744
821
  > 多进程支持可以很好的解决异步并行的情况。所有的覆盖率数据会保存到`[outputDir]/.cache`,在报告生成之后,这些缓存数据会被清除。除非开启了[调试模式](#debug-for-coverage-and-sourcemap),或者使用了`raw`报告
@@ -792,14 +869,42 @@ npm i monocart-coverage-reports
792
869
  npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
793
870
  ```
794
871
 
795
- - 命令行参数
796
- 直接运行 `mcr` 或 `mcr --help` 查看所有CLI的参数
872
+ - CLI 选项
873
+
874
+ 以下选项仅在 CLI 模式(`mcr <command>`)下可用:
875
+
876
+ | 选项 | 类型 | 默认值 | 说明 |
877
+ | :-- | :-- | :-- | :-- |
878
+ | `-c, --config` | `string` | — | 自定义配置文件路径 |
879
+ | `--import` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 ESM 模块,如 `tsx`。需要 Node.js `>= 18.19` |
880
+ | `--require` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 CJS 模块 |
881
+ | `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env`)。需要 Node.js `>= 20.6` |
882
+ | `onStart` | `(coverageReport) => Promise<void>` | `null` | 子进程启动前触发;参见 [Hooks](#hooks) |
883
+ | `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | 子进程退出后、MCR 读取覆盖率前触发;参见 [Hooks](#hooks) |
884
+
885
+ 其他选项(如 `--name`、`--reports`、`--outputDir`、`--lcov` 等)参见 [选项配置](#options) 表格,它们在 CLI 中的用法相同。
797
886
 
798
887
  - 使用 `--` 可以隔离子程序参数,以免两种参数混淆
799
888
  ```sh
800
889
  mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
801
890
  ```
802
891
 
892
+ - `--import <module>` / `--require <module>`: 通过 `NODE_OPTIONS` 把一个预加载模块传给子进程。常用于 TypeScript/JSX 运行时(`tsx`、`ts-node` 等),也可用于加载 `mcr.config.ts`。要求 Node.js `>= 18.19`
893
+ ```sh
894
+ mcr --import tsx node ./test.ts
895
+ ```
896
+
897
+ - `--env [path]`: 子进程启动前从 dotenv 文件加载环境变量(默认 `.env`)。依赖 `process.loadEnvFile`,Node.js `20.6` 起支持
898
+ ```sh
899
+ mcr --env .env.test node ./test.js
900
+ ```
901
+
902
+ - `merge`: 直接合并 `inputDir` 中的原始覆盖率数据并生成合并报告,无需启动子进程。
903
+ ```sh
904
+ mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
905
+ ```
906
+ 参见 [合并覆盖率报告](#merge-coverage-reports)。
907
+
803
908
  - 参见例子
804
909
  - [Mocha](#mocha)
805
910
  - [TypeScript](#typescript)
@@ -814,7 +919,12 @@ mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
814
919
  - `mcr.config.cjs`
815
920
  - `mcr.config.mjs`
816
921
  - `mcr.config.json` - json format
817
- - `mcr.config.ts` (requires preloading the ts execution module)
922
+ - `mcr.config.ts` (需要预加载 TypeScript 执行模块,比如 `tsx`):
923
+ ```sh
924
+ mcr --import tsx -c mcr.config.ts node ./test.js
925
+ ```
926
+
927
+ > 未指定自定义路径时,MCR 会从当前工作目录向上查找默认配置文件(通过 [`find-up`](https://github.com/sindresorhus/find-up))。不在搜索路径内的文件会被忽略
818
928
 
819
929
  ## Merge Coverage Reports
820
930
  以下这些使用场景可能需要使用合并覆盖率报告:
@@ -892,7 +1002,7 @@ const coverageOptions = {
892
1002
  ],
893
1003
 
894
1004
  onEnd: () => {
895
- // remove the raw files if it useless
1005
+ // remove the raw files if no longer needed
896
1006
  // inputDir.forEach((p) => {
897
1007
  // fs.rmSync(p, {
898
1008
  // recursive: true,
package/lib/assets.js CHANGED
@@ -53,7 +53,7 @@ const Assets = {
53
53
  });
54
54
 
55
55
  // html content
56
- let htmlStr = '';
56
+ let htmlStr;
57
57
  const EOL = Util.getEOL();
58
58
  if (inline) {
59
59
  htmlStr = [
package/lib/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const path = require('path');
4
+ const { pathToFileURL } = require('url');
4
5
  const EC = require('eight-colors');
5
6
 
6
7
  const { program } = require('commander');
@@ -11,12 +12,18 @@ const Util = require('./utils/util.js');
11
12
 
12
13
  const version = require('../package.json').version;
13
14
 
14
- const getRegisterPath = (filename) => {
15
- const rel = Util.relativePath(path.resolve(__dirname, 'register', filename));
16
- if (rel.startsWith('.')) {
17
- return rel;
18
- }
19
- return `./${rel}`;
15
+ // quote NODE_OPTIONS argument if it contains whitespace so child processes
16
+ // inheriting the env var can parse it correctly
17
+ const quoteNodeOption = (v) => ((/\s/).test(v) ? `"${v}"` : v);
18
+
19
+ // absolute path so NODE_OPTIONS keeps resolving when child processes are
20
+ // spawned with a different cwd than the mcr parent process
21
+ const getRequirePath = () => {
22
+ return quoteNodeOption(path.resolve(__dirname, 'register', 'register.js'));
23
+ };
24
+
25
+ const getImportPath = () => {
26
+ return quoteNodeOption(pathToFileURL(path.resolve(__dirname, 'register', 'register.mjs')).href);
20
27
  };
21
28
 
22
29
  const getInitNodeOptions = async (cliOptions) => {
@@ -92,10 +99,10 @@ const initNodeOptions = async (cliOptions) => {
92
99
 
93
100
  // export source after
94
101
  if (preloadType === '--import') {
95
- const importPath = getRegisterPath('register.mjs');
102
+ const importPath = getImportPath();
96
103
  nodeOptions.unshift(`--import ${importPath}`);
97
104
  } else {
98
- const requirePath = getRegisterPath('register.js');
105
+ const requirePath = getRequirePath();
99
106
  nodeOptions.unshift(`--require ${requirePath}`);
100
107
  }
101
108
 
@@ -183,6 +190,7 @@ const executeCommand = async (command, cliOptions) => {
183
190
  process.on('uncaughtException', function(err) {
184
191
  Util.logError(`Process uncaughtException: ${err.message}`);
185
192
  console.log(err.stack);
193
+ process.exit(1);
186
194
  });
187
195
 
188
196
  // the -- separator