monocart-coverage-reports 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -2
- package/README.zh-Hans.md +98 -2
- package/lib/cli.js +25 -60
- package/lib/converter/ast.js +23 -2
- package/lib/converter/collect-source-maps.js +33 -12
- package/lib/converter/converter.js +104 -39
- package/lib/converter/css-coverage.js +76 -0
- package/lib/converter/find-original-range.js +211 -3
- package/lib/converter/untested.js +4 -6
- package/lib/default/options.js +5 -0
- package/lib/generate.js +3 -2
- package/lib/index.d.ts +67 -0
- package/lib/packages/monocart-coverage-assets.js +1 -1
- package/lib/packages/monocart-coverage-converter.js +11 -11
- package/lib/packages/monocart-coverage-vendor.js +2 -1
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +69 -9
- package/lib/platform/share.js +7 -0
- package/lib/test/test-reporter.js +191 -0
- package/lib/test/test-results.js +104 -0
- package/lib/test/test-runner.js +115 -0
- package/lib/utils/source-path.js +21 -6
- package/lib/utils/util.js +6 -1
- package/migrate-to-3.md +4 -0
- package/package.json +29 -21
- package/skills/monocart-coverage-reports/SKILL.md +2 -2
- package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
- package/skills/monocart-coverage-reports/references/cli.md +5 -0
- package/skills/monocart-coverage-reports/references/collection.md +2 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +2 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +7 -3
- package/lib/packages/chunks/chunk-RVOSNMVU.js +0 -2
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
> A JavaScript code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
14
14
|
|
|
15
15
|
* [Usage](#usage)
|
|
16
|
+
* [Best Practices](#best-practices)
|
|
16
17
|
* [Options](#options)
|
|
17
18
|
* [Available Reports](#available-reports)
|
|
18
19
|
* [Compare Reports](#compare-reports)
|
|
@@ -103,8 +104,22 @@ mcr node my-app.js -r v8,console-details
|
|
|
103
104
|
```
|
|
104
105
|
For more information, see [Command Line](#command-line)
|
|
105
106
|
|
|
107
|
+
## Best Practices
|
|
108
|
+
|
|
109
|
+
For new test setups, prefer native coverage collection without an instrumentation step:
|
|
110
|
+
|
|
111
|
+
| Target | Recommended setup | Responsibility |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| Node.js | **Node.js native test runner + `mcr test`** | `node:test` runs assertions; MCR automatically collects native V8 JavaScript coverage and generates reports. |
|
|
114
|
+
| Browser | **Playwright Test + MCR** | Playwright runs assertions; automatic fixtures collect Chromium JS/CSS coverage; MCR merges it and generates reports. |
|
|
115
|
+
| Browser with integrated test reporting | **Playwright Test + [monocart-reporter](https://github.com/cenfun/monocart-reporter#code-coverage-report)** | The reporter already integrates MCR; configure its coverage option and submit coverage from fixtures for unified test and coverage reporting. |
|
|
116
|
+
|
|
117
|
+
For detailed setup instructions and examples, see the [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md).
|
|
118
|
+
|
|
106
119
|
## AI Agent Skill
|
|
107
120
|
|
|
121
|
+
For AI-assisted development in a source checkout, start with [AGENTS.md](https://github.com/cenfun/monocart-coverage-reports/blob/main/AGENTS.md). It describes the source layout, development commands, validation, and safety boundaries. `.github/copilot-instructions.md` provides a self-contained summary of the core rules for GitHub Copilot. These checkout-only guides are not included in the npm package and are separate from the reusable integration skill below.
|
|
122
|
+
|
|
108
123
|
The reusable [Monocart Coverage Reports skill](skills/monocart-coverage-reports/SKILL.md) helps AI coding agents collect coverage, configure the CLI, debug sourcemaps and filters, merge raw reports, and enforce CI thresholds. It uses a main `SKILL.md` with task-specific references loaded on demand, targeting MCR 3.x only and checking its runtime requirements.
|
|
109
124
|
|
|
110
125
|
The npm package includes `skills/monocart-coverage-reports/`. Copy this **entire directory**, including `references/`, into your agent's supported skills location, or ask the agent to read `SKILL.md` directly. Installing MCR does not automatically register the skill or modify agent settings. The instructions are in English; requests and responses can use any language.
|
|
@@ -354,11 +369,18 @@ const [jsCoverage, cssCoverage] = await Promise.all([
|
|
|
354
369
|
const coverageData = [... jsCoverage, ... cssCoverage];
|
|
355
370
|
|
|
356
371
|
```
|
|
357
|
-
|
|
372
|
+
For Playwright Test automatic fixtures, see [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js) in the complete [playwright-coverage example](https://github.com/cenfun/playwright-coverage). The [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md#complete-project-example) explains the integration.
|
|
358
373
|
For more examples, see [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
|
|
359
374
|
|
|
360
375
|
|
|
361
376
|
### Collecting Raw V8 Coverage Data with Puppeteer
|
|
377
|
+
|
|
378
|
+
> Depending on your package manager, Puppeteer may not download a browser during installation. If no browser is available, install one manually:
|
|
379
|
+
>
|
|
380
|
+
> ```sh
|
|
381
|
+
> npx puppeteer browsers install chrome
|
|
382
|
+
> ```
|
|
383
|
+
|
|
362
384
|
```js
|
|
363
385
|
await Promise.all([
|
|
364
386
|
page.coverage.startJSCoverage({
|
|
@@ -716,6 +738,8 @@ const coverageOptions = {
|
|
|
716
738
|
}
|
|
717
739
|
};
|
|
718
740
|
```
|
|
741
|
+
Built-in report path normalization is best-effort; it cannot fully reconcile every nonstandard, virtual, or foreign-platform path. Use `sourcePath` to correct these special cases rather than relying on automatic inference. The callback receives the normalized `filePath`; for V8 entries and sourcemap sources, `info.url` provides the original URL before report path normalization (including the sourcemap's `sourceRoot`, when present). This rewrites report paths, not the filesystem paths used to load missing source content. When `sourcesContent` is missing, local sources are loaded relative to the external map's URL (or the generated file for inline maps), with `sourceRoot` applied as a directory.
|
|
742
|
+
|
|
719
743
|
It also supports simple key/value replacement:
|
|
720
744
|
```js
|
|
721
745
|
const coverageOptions = {
|
|
@@ -741,7 +765,7 @@ const coverageOptions = {
|
|
|
741
765
|
```
|
|
742
766
|
|
|
743
767
|
## Adding Empty Coverage for Untested Files
|
|
744
|
-
|
|
768
|
+
Untested files are excluded by default. Use `all` to include them with 0% coverage, even when no coverage data was collected. For the same original source, real V8 coverage takes precedence over empty entries.
|
|
745
769
|
```js
|
|
746
770
|
const coverageOptions = {
|
|
747
771
|
all: './src'
|
|
@@ -844,6 +868,53 @@ const coverageOptions = {
|
|
|
844
868
|
};
|
|
845
869
|
```
|
|
846
870
|
|
|
871
|
+
### `onTestEnd` — `mcr test` only
|
|
872
|
+
|
|
873
|
+
`onTestEnd(testResults)` runs once after the test runner exits and its JSON results are collected, before publishing the test results cache, `onReady`, and coverage generation. It is awaited even when assertions fail. Other commands, API calls, and runs skipped because no targets were supplied do not call it. The existing `onEnd(coverageResults)` is unchanged.
|
|
874
|
+
|
|
875
|
+
`mcr test` always collects structured test results using an internal JSON reporter, preserving terminal output and user-specified reporter/destination pairs. `onTestEnd` is optional; omitting it does not disable result collection. To retain results for later use, enable `raw` reports or debug logging; normal cache cleanup still applies.
|
|
876
|
+
|
|
877
|
+
```js
|
|
878
|
+
// mcr.config.js
|
|
879
|
+
export default {
|
|
880
|
+
test: './test/**/*.test.js',
|
|
881
|
+
onTestEnd: (testResults) => {
|
|
882
|
+
console.log(testResults.summary);
|
|
883
|
+
for (const test of testResults.tests) {
|
|
884
|
+
console.log(test.name, test.status, test.error);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
- `id`: a collection ID generated with the same `Util.uid()` mechanism as coverage cache data.
|
|
891
|
+
- `framework`: `'node:test'`.
|
|
892
|
+
- `complete`: whether the final runner summary was received, not whether tests passed.
|
|
893
|
+
- `summary`: `{ tests, suites, passed, failed, cancelled, skipped, todo, duration, success }`; `null` if incomplete. Counts come from the runner's final summary; `tests` excludes suites. Durations are in milliseconds.
|
|
894
|
+
- `tests`: completed test and suite records, including nested tests: `{ name, type, nesting, testNumber, file?, line?, column?, duration, status, skip?, todo?, error? }`. Status is `passed`, `failed`, `cancelled`, `skipped`, or `todo`. Errors always include string `name` and `message` (defaults: `'Error'` and `''`). Other own enumerable data properties are copied only when their values are strings, booleans, finite numbers, or `null`; BigInts become decimal strings. Getters/setters, objects/arrays, functions, symbols, `undefined`, `NaN`, and infinities are omitted without recursive traversal. Apart from the two fixed fields, inherited/non-enumerable properties are not collected, so normal `stack` is omitted and fields inside `cause` are not flattened. Explicitly enumerable string `stack` is copied like any other string. Reserved keys `__proto__`, `constructor`, and `prototype` are excluded; the fixed fields cannot be overwritten by extra properties.
|
|
895
|
+
|
|
896
|
+
Test failures, incomplete/missing/invalid reports, and hook errors keep the command nonzero. An incomplete report is delivered with `complete: false`; a missing or invalid JSON report cannot be delivered and is logged as a collection error. A nonzero `process.exitCode` set by `onTestEnd` cannot be cleared by later coverage hooks. Hook/collection errors do not prevent MCR from attempting coverage generation. If the hook throws, test results are not published or exported, rather than falling back to possibly unredacted data. Test results are independent of coverage, so this hook can run even if coverage generation later fails.
|
|
897
|
+
|
|
898
|
+
Test results use the same ID and filename mechanism as coverage cache data: `outputDir/.cache/test-results-<id>.json`, with the same `id` stored in the JSON. The reporter streams records into `.json.tmp` with its collection ID already included. A small `.meta.tmp` completion record verifies the final byte count. Without a hook, MCR reads only this completion record and renames the JSON, without loading or rewriting the full report. With a hook, MCR reads the full result object and publishes its mutations after the hook completes. Temporary output and metadata are removed before coverage generation. Finalized JSON follows the coverage cache lifecycle: normally cleaned after generation, retained in debug mode, or exported by `raw` (including ZIP). It is not embedded in HTML/coverage JSON reports, and importing raw data does not yet merge test results. Save results yourself if needed; files written into `outputDir` by this hook may be removed by the subsequent coverage cleanup when `clean: true`.
|
|
899
|
+
|
|
900
|
+
**Sensitive data:** Primitive does not mean non-sensitive: a string `token`, `password`, or response body is also copied. Messages, test names, and paths may contain credentials or personal data; large strings can also increase report size. MCR does not redact them automatically. Keep the output directory private and review raw/debug artifacts before uploading them. Mutate results in `onTestEnd` to sanitize the persisted JSON; this does not redact Node's terminal output or data already written to temporary files. For example, adapt the following rule to your application's data:
|
|
901
|
+
|
|
902
|
+
```js
|
|
903
|
+
export default {
|
|
904
|
+
onTestEnd: (results) => {
|
|
905
|
+
for (const test of results.tests) {
|
|
906
|
+
if (test.error) {
|
|
907
|
+
for (const [key, value] of Object.entries(test.error)) {
|
|
908
|
+
if (typeof value === 'string') {
|
|
909
|
+
test.error[key] = value.replace(/(Bearer\s+)\S+/gi, '$1[REDACTED]');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
```
|
|
917
|
+
|
|
847
918
|
### `onStart` / `onReady` — CLI-only
|
|
848
919
|
Only available when using `mcr <command>`:
|
|
849
920
|
- `onStart(coverageReport)` — runs before the child process is spawned.
|
|
@@ -961,6 +1032,7 @@ The options below are only available when using the CLI (`mcr <command>`):
|
|
|
961
1032
|
| `--require` | `string` | `null` | Deprecated alias for `--import`; uses ESM loading, not CommonJS resolution |
|
|
962
1033
|
| `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). |
|
|
963
1034
|
| `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` |
|
|
1035
|
+
| `onTestEnd` (config only) | `(testResults) => void \| Promise<void>` | `null` | Only `mcr test`: runs after test result collection, before `onReady` and coverage generation; see [Hooks](#hooks) |
|
|
964
1036
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
|
|
965
1037
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
|
|
966
1038
|
|
|
@@ -1079,6 +1151,9 @@ The following usage scenarios may require merging coverage reports:
|
|
|
1079
1151
|
### Manual Merging
|
|
1080
1152
|
If the reports cannot be merged automatically, then here is how to manually merge the reports.
|
|
1081
1153
|
First, using the `raw` report to export the original coverage data to the specified directory.
|
|
1154
|
+
|
|
1155
|
+
**Before uploading raw/debug artifacts (3.2.0+):** `mcr test` also exports `test-results-<id>.json` in raw reports (including ZIP) and retains it in debug caches, even without a hook. Test names, paths and error strings may contain secrets or personal data. Review artifacts and use [`onTestEnd`](#ontestend--mcr-test-only) to redact persisted test results before publication; this does not redact terminal output or temporary files already written. Raw imports merge coverage only, not test results.
|
|
1156
|
+
|
|
1082
1157
|
- For example, we have `raw` coverage data from `unit test`, which is output to `./coverage-reports/unit/raw`. Unit test examples:
|
|
1083
1158
|
- `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
|
|
1084
1159
|
- `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
|
|
@@ -1243,6 +1318,27 @@ const coverageOptions = {
|
|
|
1243
1318
|
process.env.MCR_LOG_TIME = true;
|
|
1244
1319
|
```
|
|
1245
1320
|
|
|
1321
|
+
### CSS coverage issues
|
|
1322
|
+
|
|
1323
|
+
CSS coverage from multiple bundles is merged as a union for each original source.
|
|
1324
|
+
|
|
1325
|
+
For preprocessor syntax that `all` cannot parse directly, use `all.transformer` to compile to CSS, set `entry.type = 'css'`, and preserve the sourcemap.
|
|
1326
|
+
|
|
1327
|
+
#### Sass mixins and `@include`
|
|
1328
|
+
|
|
1329
|
+
Sass `@include` can map a generated rule to non-contiguous source locations. When a rule cannot be reliably mapped, MCR reports the **whole bundle as generated CSS** instead of the preprocessor sources. That bundle's coverage is not merged into the original-source coverage.
|
|
1330
|
+
|
|
1331
|
+
#### Line coverage of untested preprocessor sources
|
|
1332
|
+
|
|
1333
|
+
Untested files included through `all` have `0%` byte coverage, including sourcemap originals. Lines outside parsed or mapped rules (such as variables or unused mixins) may appear covered; use **bytes** coverage for these files.
|
|
1334
|
+
|
|
1335
|
+
#### Source map can be inaccurate after minification
|
|
1336
|
+
CSS minification can produce imprecise sourcemaps and trigger fallback to generated CSS. If source mapping is inaccurate, try **disabling CSS minification**.
|
|
1337
|
+
|
|
1338
|
+
#### V8 covered the whole block
|
|
1339
|
+
|
|
1340
|
+
Chromium may mark an entire media query or nested CSS block as covered. Consider [postcss-atomic-selectors](https://www.npmjs.com/package/postcss-atomic-selectors) as a potential workaround.
|
|
1341
|
+
|
|
1246
1342
|
## Integration with Any Testing Framework
|
|
1247
1343
|
- API
|
|
1248
1344
|
- Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter. `await mcr.add(coverageData)`
|
package/README.zh-Hans.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
> JS代码覆盖率工具,用来生成原生的[V8](https://v8.dev/blog/javascript-code-coverage)或者[Istanbul](https://istanbul.js.org/)代码覆盖率报告
|
|
14
14
|
|
|
15
15
|
* [用法](#usage)
|
|
16
|
+
* [最佳实践](#best-practices)
|
|
16
17
|
* [选项配置](#options)
|
|
17
18
|
* [所有支持的报告类型](#available-reports)
|
|
18
19
|
* [比较两种报告](#compare-reports)
|
|
@@ -103,8 +104,22 @@ mcr node my-app.js -r v8,console-details
|
|
|
103
104
|
```
|
|
104
105
|
参见 [命令行](#command-line)
|
|
105
106
|
|
|
107
|
+
## Best Practices
|
|
108
|
+
|
|
109
|
+
为新项目建立测试方案时,优先使用无需插桩的原生覆盖率采集:
|
|
110
|
+
|
|
111
|
+
| 测试目标 | 推荐方案 | 职责划分 |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| Node.js 端 | **Node.js 原生 test runner + `mcr test`** | `node:test` 执行测试断言;MCR 自动采集 JavaScript 原生 V8 覆盖率并生成报告。 |
|
|
114
|
+
| 浏览器端 | **Playwright Test + MCR** | Playwright 执行测试断言;自动 fixture 采集 Chromium JS/CSS 覆盖率;MCR 合并并生成报告。 |
|
|
115
|
+
| 浏览器端,需要统一测试报告 | **Playwright Test + [monocart-reporter](https://github.com/cenfun/monocart-reporter#code-coverage-report)** | reporter 已集成 MCR;配置其 coverage 选项,并由 fixture 提交覆盖率数据,统一管理测试报告和覆盖率报告。 |
|
|
116
|
+
|
|
117
|
+
详细配置步骤和示例见[最佳实践指南](skills/monocart-coverage-reports/references/best-practices.md)。
|
|
118
|
+
|
|
106
119
|
## AI Agent Skill
|
|
107
120
|
|
|
121
|
+
使用 AI 辅助开发本仓库源码时,请先阅读 [AGENTS.md](https://github.com/cenfun/monocart-coverage-reports/blob/main/AGENTS.md),了解源码结构、开发命令、验证流程和安全边界。`.github/copilot-instructions.md` 为 GitHub Copilot 提供可独立使用的核心规则摘要。这些指南仅用于源码仓库,不随 npm 包发布,与下面可复用的集成技能相互独立。
|
|
122
|
+
|
|
108
123
|
项目提供可复用的 [Monocart Coverage Reports 技能](skills/monocart-coverage-reports/SKILL.md),帮助 AI coding agent 采集覆盖率、配置 CLI、排查 sourcemap 和过滤问题、合并 raw 报告及设置 CI 阈值。结构为主 `SKILL.md` 加按需加载的专题参考文档,仅面向 MCR 3.x,并检查其运行环境要求。
|
|
109
124
|
|
|
110
125
|
npm 包包含 `skills/monocart-coverage-reports/`。请将**整个目录**(包括 `references/`)复制到 agent 支持的 skills 目录,或直接让 agent 读取 `SKILL.md`。安装 MCR 不会自动注册技能或修改 agent 配置。技能说明使用英文,但提问和回复可以使用中文或其他语言。
|
|
@@ -356,11 +371,18 @@ const [jsCoverage, cssCoverage] = await Promise.all([
|
|
|
356
371
|
const coverageData = [... jsCoverage, ... cssCoverage];
|
|
357
372
|
|
|
358
373
|
```
|
|
359
|
-
|
|
374
|
+
Playwright Test 自动 fixture 参见完整 [playwright-coverage 示例](https://github.com/cenfun/playwright-coverage)中的 [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js),接入方式见[最佳实践指南](skills/monocart-coverage-reports/references/best-practices.md#complete-project-example)。
|
|
360
375
|
参见例子 [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
|
|
361
376
|
|
|
362
377
|
|
|
363
378
|
### Collecting Raw V8 Coverage Data with Puppeteer
|
|
379
|
+
|
|
380
|
+
> 取决于包管理器配置,Puppeteer 可能不会在安装依赖时下载浏览器。如果没有可用浏览器,请手动安装:
|
|
381
|
+
>
|
|
382
|
+
> ```sh
|
|
383
|
+
> npx puppeteer browsers install chrome
|
|
384
|
+
> ```
|
|
385
|
+
|
|
364
386
|
使用Puppeteer的覆盖接口收集覆盖率数据,注意Puppeteer默认不会提供原生V8的覆盖率数据,需要设置`includeRawScriptCoverage`
|
|
365
387
|
```js
|
|
366
388
|
await Promise.all([
|
|
@@ -719,6 +741,8 @@ const coverageOptions = {
|
|
|
719
741
|
}
|
|
720
742
|
};
|
|
721
743
|
```
|
|
744
|
+
内置报告路径规范化采用尽力而为的原则,无法完全兼容所有非标准、虚拟或来自其他操作系统的路径。此类特殊情况应通过 `sourcePath` 自行修正,而不是依赖自动推断。回调接收规范化后的 `filePath`;对于 V8 入口和 sourcemap 源文件,`info.url` 提供报告路径规范化前的原始 URL(sourcemap 存在 `sourceRoot` 时包含该前缀)。它修改的是报告路径,不是读取缺失源码时使用的文件系统路径。缺少 `sourcesContent` 时,本地源码相对于外部 map 的 URL 加载(内联 map 则相对于生成文件),并将 `sourceRoot` 作为目录应用。
|
|
745
|
+
|
|
722
746
|
它也支持简单key/value的替换:
|
|
723
747
|
```js
|
|
724
748
|
const coverageOptions = {
|
|
@@ -744,7 +768,7 @@ const coverageOptions = {
|
|
|
744
768
|
```
|
|
745
769
|
|
|
746
770
|
## Adding Empty Coverage for Untested Files
|
|
747
|
-
|
|
771
|
+
默认不包含未测试文件。使用 `all` 可将它们以 0% 覆盖率加入报告,即使没有采集到任何覆盖率数据也可生成报告。同一原始源码的真实 V8 覆盖率优先于空覆盖率条目。
|
|
748
772
|
```js
|
|
749
773
|
const coverageOptions = {
|
|
750
774
|
all: './src'
|
|
@@ -847,6 +871,53 @@ const coverageOptions = {
|
|
|
847
871
|
};
|
|
848
872
|
```
|
|
849
873
|
|
|
874
|
+
### `onTestEnd` — 仅 `mcr test` 可用
|
|
875
|
+
|
|
876
|
+
`onTestEnd(testResults)` 在测试运行器退出、JSON 测试结果采集完成后触发一次,早于测试结果缓存发布、`onReady` 和覆盖率报告生成。支持异步等待,测试断言失败时也会触发。其他命令、API 调用,以及因未提供目标而跳过的测试,不触发该 hook。现有 `onEnd(coverageResults)` 保持不变。
|
|
877
|
+
|
|
878
|
+
`mcr test` 始终通过内部 JSON reporter 采集结构化测试结果,同时保留终端输出和用户指定的 reporter/destination 配对。`onTestEnd` 为可选回调,不配置也不会关闭采集。如需留待后续使用,请启用 `raw` 报告或 debug 日志;默认的缓存清理规则仍然生效。
|
|
879
|
+
|
|
880
|
+
```js
|
|
881
|
+
// mcr.config.js
|
|
882
|
+
export default {
|
|
883
|
+
test: './test/**/*.test.js',
|
|
884
|
+
onTestEnd: (testResults) => {
|
|
885
|
+
console.log(testResults.summary);
|
|
886
|
+
for (const test of testResults.tests) {
|
|
887
|
+
console.log(test.name, test.status, test.error);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
- `id`:采用与覆盖率缓存数据相同的 `Util.uid()` 机制生成的采集 ID。
|
|
894
|
+
- `framework`:`'node:test'`。
|
|
895
|
+
- `complete`:是否收到运行器最终汇总,不代表测试是否通过。
|
|
896
|
+
- `summary`:`{ tests, suites, passed, failed, cancelled, skipped, todo, duration, success }`,报告不完整时为 `null`。计数采用运行器最终汇总,`tests` 不包含 suite,耗时单位为毫秒。
|
|
897
|
+
- `tests`:已完成的测试及 suite 记录,包含嵌套测试:`{ name, type, nesting, testNumber, file?, line?, column?, duration, status, skip?, todo?, error? }`。状态为 `passed`、`failed`、`cancelled`、`skipped` 或 `todo`。错误固定包含字符串 `name`、`message`,不可用或非字符串时分别使用 `'Error'`、`''`。其余只复制自有、可枚举的数据属性,值仅限字符串、布尔值、有限数字和 `null`;BigInt 转为十进制字符串。不递归遍历,跳过 getter/setter、对象/数组、函数、Symbol、`undefined`、`NaN` 和无穷数。除两个固定字段外,不采集继承或不可枚举属性,因此通常不包含 `stack`,也不展开 `cause` 内的字段;明确设为可枚举的字符串 `stack` 仍按普通字符串复制。过滤 `__proto__`、`constructor`、`prototype` 等特殊键,额外属性不会覆盖固定字段。
|
|
898
|
+
|
|
899
|
+
测试失败、报告不完整/缺失/损坏或 hook 抛错,均保持非零退出状态。不完整报告以 `complete: false` 交付;缺失或无效的 JSON 无法交付,会明确记录采集错误。`onTestEnd` 设置的非零 `process.exitCode` 不会被后续覆盖率 hook 清零。hook 或采集失败后仍尝试生成覆盖率报告。hook 抛错时不会发布或导出测试结果,避免回退导出可能尚未脱敏的数据。测试结果独立于覆盖率,因此即使后续覆盖率生成失败,此 hook 仍可执行。
|
|
900
|
+
|
|
901
|
+
测试结果复用覆盖率缓存的 ID 和文件命名机制:`outputDir/.cache/test-results-<id>.json`,JSON 内容保存同一个 `id`。reporter 将记录逐条写入 `.json.tmp`,输出时即包含采集 ID,并通过很小的 `.meta.tmp` 完成记录校验最终字节数。没有 hook 时,MCR 只读取完成记录并重命名 JSON,不会加载或重写完整报告;配置 hook 时,读取完整结果对象,在 hook 完成后将修改后的数据发布为正式缓存。临时输出和元数据均在生成覆盖率前清理。正式 JSON 遵循覆盖率缓存生命周期:正常生成后清理、调试模式保留,或由 `raw` 报告导出(包括 ZIP)。它不会嵌入 HTML/覆盖率 JSON 报告,导入 raw 数据目前也不会合并测试结果。如需持久化,请自行保存;当 `clean: true` 时,此 hook 写入 `outputDir` 的文件可能被后续覆盖率清理流程删除。
|
|
902
|
+
|
|
903
|
+
**敏感信息提醒:** 基础类型不等于非敏感数据,字符串 `token`、`password` 或响应正文同样会被复制。错误消息、测试名称和路径也可能包含凭据或个人信息,大字符串仍可能增大报告体积,MCR 不会自动脱敏。请保护输出目录,并在上传 raw/debug 产物前审查内容。可在 `onTestEnd` 中修改结果,对正式保存的 JSON 脱敏;这不会处理 Node 的终端输出或已经写入临时文件的数据。以下规则仅为示例,请按业务数据调整:
|
|
904
|
+
|
|
905
|
+
```js
|
|
906
|
+
export default {
|
|
907
|
+
onTestEnd: (results) => {
|
|
908
|
+
for (const test of results.tests) {
|
|
909
|
+
if (test.error) {
|
|
910
|
+
for (const [key, value] of Object.entries(test.error)) {
|
|
911
|
+
if (typeof value === 'string') {
|
|
912
|
+
test.error[key] = value.replace(/(Bearer\s+)\S+/gi, '$1[REDACTED]');
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
```
|
|
920
|
+
|
|
850
921
|
### `onStart` / `onReady` — 仅CLI可用
|
|
851
922
|
只在使用 `mcr <command>` 时有效:
|
|
852
923
|
- `onStart(coverageReport)` — 子进程启动前触发
|
|
@@ -964,6 +1035,7 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
964
1035
|
| `--require` | `string` | `null` | `--import` 的已弃用别名;使用 ESM 加载,不使用 CommonJS 解析 |
|
|
965
1036
|
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env`)。|
|
|
966
1037
|
| `test`(仅配置文件) | `string \| string[] \| null` | `null` | 未指定命令行目标时,原样传给 `node --test` 的默认测试文件路径或 glob,**仅 `mcr test` 使用**;其他命令和 API 忽略。需要 Node.js `>= 22.15.0` |
|
|
1038
|
+
| `onTestEnd`(仅配置文件) | `(testResults) => void \| Promise<void>` | `null` | 仅 `mcr test`:测试结果采集完成后、`onReady` 和覆盖率生成前触发;参见 [Hooks](#hooks) |
|
|
967
1039
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | 子进程启动前触发;参见 [Hooks](#hooks) |
|
|
968
1040
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | 子进程退出后、MCR 读取覆盖率前触发;参见 [Hooks](#hooks) |
|
|
969
1041
|
|
|
@@ -1081,6 +1153,9 @@ npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=sp
|
|
|
1081
1153
|
|
|
1082
1154
|
### Manual Merging
|
|
1083
1155
|
手动合并覆盖率报告需要使用`raw`报告来导出原始的覆盖率数据到指定的目录
|
|
1156
|
+
|
|
1157
|
+
**上传 raw/debug 产物前请注意(3.2.0+):** 即使未配置 hook,`mcr test` 也会在 raw 报告(包括 ZIP)中导出 `test-results-<id>.json`,并在 debug 缓存中保留该文件。测试名称、路径及错误字符串可能包含凭据或个人信息。请先审查产物,并通过 [`onTestEnd`](#ontestend--仅-mcr-test-可用) 在发布前对正式保存的测试结果脱敏;这不会处理终端输出或已写入的临时文件。导入 raw 数据只合并覆盖率,不会合并测试结果。
|
|
1158
|
+
|
|
1084
1159
|
- 比如,单元测试保存到`./coverage-reports/unit/raw`,见例子
|
|
1085
1160
|
- `Jest` + [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage)
|
|
1086
1161
|
- `Vitest` + [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage)
|
|
@@ -1246,6 +1321,27 @@ const coverageOptions = {
|
|
|
1246
1321
|
process.env.MCR_LOG_TIME = true;
|
|
1247
1322
|
```
|
|
1248
1323
|
|
|
1324
|
+
### CSS 覆盖率与 sourcemap
|
|
1325
|
+
|
|
1326
|
+
多个 bundle 映射到同一原始文件时,CSS 覆盖率取并集。
|
|
1327
|
+
|
|
1328
|
+
对于 `all` 无法直接解析的预处理器语法,请使用 `all.transformer` 编译为 CSS,设置 `entry.type = 'css'`,并保留 sourcemap。
|
|
1329
|
+
|
|
1330
|
+
#### Sass mixin 与 `@include`
|
|
1331
|
+
|
|
1332
|
+
Sass `@include` 可能将一条生成规则映射到不连续的源码位置。规则无法可靠映射时,MCR 会将**整个 bundle 按生成的 CSS 展示**,而不是预处理器源文件;该 bundle 的覆盖率也不会合并到原始源码的覆盖率中。
|
|
1333
|
+
|
|
1334
|
+
#### 未测试预处理源文件的行覆盖率
|
|
1335
|
+
|
|
1336
|
+
通过 `all` 加入的未测试文件,其字节覆盖率为 `0%`,包括 sourcemap 还原的原始文件。解析或映射规则之外的行(如变量、未使用的 mixin)可能显示为已覆盖;请以 **bytes** 覆盖率为准。
|
|
1337
|
+
|
|
1338
|
+
#### 压缩后 sourcemap 可能不准确
|
|
1339
|
+
CSS 压缩可能产生不准确的 sourcemap,导致报告回退到生成的 CSS。遇到映射不准确时,可尝试**禁用 CSS 压缩**。
|
|
1340
|
+
|
|
1341
|
+
#### V8 会将整个 block 视为已覆盖
|
|
1342
|
+
|
|
1343
|
+
Chromium 可能将整个 media query 或嵌套 CSS block 标记为已覆盖。可考虑使用 [postcss-atomic-selectors](https://www.npmjs.com/package/postcss-atomic-selectors) 作为临时解决方案。
|
|
1344
|
+
|
|
1249
1345
|
## Integration with Any Testing Framework
|
|
1250
1346
|
通用集成方案
|
|
1251
1347
|
- 通过API接口在程序集成
|
package/lib/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { program } from 'commander';
|
|
|
9
9
|
import { execa } from 'execa';
|
|
10
10
|
|
|
11
11
|
import { MCR } from './index.js';
|
|
12
|
+
import { getTestCommand, createTestRunner } from './test/test-runner.js';
|
|
12
13
|
import { Util } from './utils/util.js';
|
|
13
14
|
import pkg from '../package.json' with { type: 'json' };
|
|
14
15
|
|
|
@@ -85,17 +86,6 @@ const mergeCoverage = async (cliOptions) => {
|
|
|
85
86
|
await coverageReport.generate();
|
|
86
87
|
};
|
|
87
88
|
|
|
88
|
-
// Only check whether targets were supplied. Node owns all target parsing,
|
|
89
|
-
// discovery and validation; preserve each argument exactly as provided.
|
|
90
|
-
const getTestTargets = (test) => {
|
|
91
|
-
if (!test || (Array.isArray(test) && !test.length)) {
|
|
92
|
-
Util.logInfo('No test targets configured; skipping tests.');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return Array.isArray(test) ? test : [test];
|
|
97
|
-
};
|
|
98
|
-
|
|
99
89
|
// Preserve signals and failing commands even if a hook sets exitCode=0.
|
|
100
90
|
const getExitStatus = (code, signal) => signal || code || process.exitCode || 0;
|
|
101
91
|
|
|
@@ -177,24 +167,19 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
177
167
|
|
|
178
168
|
// console.log(options);
|
|
179
169
|
const coverageReport = MCR(cliOptions);
|
|
180
|
-
const isTest = command === 'test';
|
|
181
170
|
await coverageReport.loadConfig(cliOptions.config);
|
|
182
171
|
|
|
183
172
|
const coverageOptions = coverageReport.options;
|
|
184
|
-
let
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
if (!
|
|
173
|
+
let testRunner;
|
|
174
|
+
if (command === 'test') {
|
|
175
|
+
testRunner = createTestRunner(coverageOptions, testArgs);
|
|
176
|
+
if (!testRunner) {
|
|
188
177
|
return;
|
|
189
178
|
}
|
|
190
|
-
childArgs = ['--test', ... testArgs, ... targets];
|
|
191
179
|
}
|
|
192
180
|
coverageReport.cleanCache();
|
|
193
181
|
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
|
|
194
|
-
|
|
195
|
-
// Tests may spawn children with a different working directory.
|
|
196
|
-
process.env.NODE_V8_COVERAGE = path.resolve(nodeV8CoverageDir);
|
|
197
|
-
}
|
|
182
|
+
testRunner?.initCoverageDir(nodeV8CoverageDir);
|
|
198
183
|
|
|
199
184
|
// =========================================
|
|
200
185
|
// onStart hook
|
|
@@ -204,22 +189,26 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
204
189
|
}
|
|
205
190
|
// =========================================
|
|
206
191
|
|
|
207
|
-
const
|
|
192
|
+
const generateCoverage = async (subprocess) => {
|
|
193
|
+
// onReady hook before adding coverage data. Some children need
|
|
194
|
+
// additional time to finish writing their coverage files.
|
|
195
|
+
const onReady = coverageOptions.onReady;
|
|
196
|
+
if (typeof onReady === 'function') {
|
|
197
|
+
await onReady(coverageReport, nodeV8CoverageDir, subprocess);
|
|
198
|
+
}
|
|
199
|
+
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
200
|
+
return coverageReport.generate();
|
|
201
|
+
};
|
|
208
202
|
|
|
209
|
-
|
|
203
|
+
const onClose = async (code, signal, subprocess) => {
|
|
210
204
|
|
|
205
|
+
// Generate coverage even when the child failed (code != 0).
|
|
211
206
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
220
|
-
const report = await coverageReport.generate();
|
|
221
|
-
if (isTest && !report) {
|
|
222
|
-
throw new Error('No coverage report was generated');
|
|
207
|
+
const generate = () => generateCoverage(subprocess);
|
|
208
|
+
if (testRunner) {
|
|
209
|
+
await testRunner.onClose(generate);
|
|
210
|
+
} else {
|
|
211
|
+
await generate();
|
|
223
212
|
}
|
|
224
213
|
} catch (error) {
|
|
225
214
|
Util.logError(`Coverage report generation failed: ${error.message}`);
|
|
@@ -233,8 +222,8 @@ const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs =
|
|
|
233
222
|
return getExitStatus(code, signal);
|
|
234
223
|
};
|
|
235
224
|
|
|
236
|
-
if (
|
|
237
|
-
return executeSubprocess
|
|
225
|
+
if (testRunner) {
|
|
226
|
+
return testRunner.execute(executeSubprocess, onClose);
|
|
238
227
|
}
|
|
239
228
|
return executeSubprocess(command, commandArgs, onClose);
|
|
240
229
|
|
|
@@ -261,30 +250,6 @@ process.argv.forEach((it) => {
|
|
|
261
250
|
}
|
|
262
251
|
});
|
|
263
252
|
|
|
264
|
-
// Also recognize `mcr -c config -- test targets -- runner-options` without
|
|
265
|
-
// falling through to the shell's unrelated `test` command. Only MCR options
|
|
266
|
-
// before the first separator are parsed in that form.
|
|
267
|
-
const getTestCommand = (args, subArgs) => {
|
|
268
|
-
if (args[0] === 'test') {
|
|
269
|
-
return {
|
|
270
|
-
targets: args.slice(1), runnerArgs: subArgs.slice(1)
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (args.length || subArgs[1] !== 'test') {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
const remaining = subArgs.slice(2);
|
|
277
|
-
const index = remaining.indexOf('--');
|
|
278
|
-
if (index === -1) {
|
|
279
|
-
return {
|
|
280
|
-
targets: remaining, runnerArgs: []
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
targets: remaining.slice(0, index), runnerArgs: remaining.slice(index + 1)
|
|
285
|
-
};
|
|
286
|
-
};
|
|
287
|
-
|
|
288
253
|
program
|
|
289
254
|
.name('mcr')
|
|
290
255
|
.description('CLI to generate coverage reports')
|
package/lib/converter/ast.js
CHANGED
|
@@ -345,8 +345,29 @@ const getCssAstInfo = (item, coverageList) => {
|
|
|
345
345
|
endOffset: it.end
|
|
346
346
|
};
|
|
347
347
|
});
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
let ast;
|
|
349
|
+
try {
|
|
350
|
+
ast = parseCss(source);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
// Untested files may use preprocessor syntax that postcss cannot parse.
|
|
353
|
+
// Mirror the JS empty-file fallback: report the whole file as uncovered
|
|
354
|
+
// instead of aborting report generation.
|
|
355
|
+
if (empty) {
|
|
356
|
+
coverageList.push({
|
|
357
|
+
start: 0,
|
|
358
|
+
end: source.length,
|
|
359
|
+
count: 0
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
functions: [],
|
|
363
|
+
branches: [],
|
|
364
|
+
statements: []
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
throw new Error(`parse css error in ${item.sourcePath}`, {
|
|
368
|
+
cause: error
|
|
369
|
+
});
|
|
370
|
+
}
|
|
350
371
|
|
|
351
372
|
addCssRules(ast.nodes, coverageList, coveredRanges, empty);
|
|
352
373
|
|
|
@@ -121,27 +121,41 @@ const resolveMapUrl = (filename, url) => {
|
|
|
121
121
|
|
|
122
122
|
const resolveSourcesContent = (data, url) => {
|
|
123
123
|
|
|
124
|
-
const {
|
|
124
|
+
const {
|
|
125
|
+
sources, sourcesContent, sourceRoot
|
|
126
|
+
} = data;
|
|
127
|
+
// sourceRoot is a directory, even when its trailing slash is omitted.
|
|
128
|
+
let rootUrl;
|
|
129
|
+
if (typeof sourceRoot === 'string' && sourceRoot) {
|
|
130
|
+
rootUrl = Util.resolveUrl(sourceRoot.endsWith('/') ? sourceRoot : `${sourceRoot}/`, url);
|
|
131
|
+
}
|
|
125
132
|
|
|
126
133
|
// sources [1,2,3]
|
|
127
134
|
// sourcesContent could be [null, null, "content"]
|
|
128
135
|
// some of contents could be missed
|
|
129
136
|
|
|
130
137
|
let hasSourceContent = false;
|
|
138
|
+
const missingSources = [];
|
|
131
139
|
sources.forEach((file, i) => {
|
|
132
140
|
if (typeof sourcesContent[i] === 'string') {
|
|
133
141
|
hasSourceContent = true;
|
|
134
142
|
return;
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
const sourceUrl = Util.resolveUrl(file, url);
|
|
145
|
+
const sourceUrl = Util.resolveUrl(file, rootUrl?.href || url);
|
|
138
146
|
if (sourceUrl) {
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
let content;
|
|
148
|
+
try {
|
|
149
|
+
let sourcePath = sourceUrl.toString();
|
|
150
|
+
// Read actual local paths, not normalized virtual report paths.
|
|
151
|
+
if (sourceUrl.protocol === 'file:') {
|
|
152
|
+
sourcePath = fileURLToPath(sourceUrl);
|
|
153
|
+
}
|
|
154
|
+
content = Util.readFileSync(path.resolve(sourcePath));
|
|
155
|
+
} catch {
|
|
156
|
+
// A foreign or malformed file URL is just an unavailable source.
|
|
157
|
+
// Keep resolving the other sources instead of discarding the map.
|
|
143
158
|
}
|
|
144
|
-
const content = Util.readFileSync(path.resolve(sourcePath));
|
|
145
159
|
if (typeof content === 'string') {
|
|
146
160
|
sourcesContent[i] = content;
|
|
147
161
|
hasSourceContent = true;
|
|
@@ -149,13 +163,18 @@ const resolveSourcesContent = (data, url) => {
|
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
missingSources.push(i);
|
|
153
167
|
Util.logDebug(EC.red(`failed to load source content: ${file}`));
|
|
154
168
|
|
|
155
169
|
});
|
|
156
170
|
|
|
157
171
|
|
|
158
172
|
if (hasSourceContent) {
|
|
173
|
+
// Only commit placeholders for accepted maps. A rejected map may be
|
|
174
|
+
// cached by the resolver and must not look like valid empty sources later.
|
|
175
|
+
missingSources.forEach((i) => {
|
|
176
|
+
sourcesContent[i] = '';
|
|
177
|
+
});
|
|
159
178
|
return data;
|
|
160
179
|
}
|
|
161
180
|
};
|
|
@@ -233,7 +252,7 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
233
252
|
for (const item of v8list) {
|
|
234
253
|
|
|
235
254
|
const {
|
|
236
|
-
|
|
255
|
+
url, id, source, sourceMap
|
|
237
256
|
} = item;
|
|
238
257
|
|
|
239
258
|
// source and sourceMap will be saved as separated file (could be cached)
|
|
@@ -258,7 +277,7 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
258
277
|
|
|
259
278
|
// An explicitly supplied, usable map takes precedence over sourceMappingURL.
|
|
260
279
|
// Keep the original source so V8 offsets remain valid.
|
|
261
|
-
if (
|
|
280
|
+
if (!isUsableSourceMap(sourceData.sourceMap)) {
|
|
262
281
|
delete sourceData.sourceMap;
|
|
263
282
|
const candidates = getSourceMapCandidates(source);
|
|
264
283
|
if (candidates.length) {
|
|
@@ -279,7 +298,7 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
279
298
|
const { url } = sourceData;
|
|
280
299
|
for (const candidate of candidates) {
|
|
281
300
|
let data;
|
|
282
|
-
let sourceMapUrl;
|
|
301
|
+
let sourceMapUrl = url;
|
|
283
302
|
try {
|
|
284
303
|
if (candidate.inline) {
|
|
285
304
|
data = convertSourceMap.fromComment(candidate.comment).sourcemap;
|
|
@@ -293,7 +312,9 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
293
312
|
if (!isUsableSourceMap(data)) {
|
|
294
313
|
continue;
|
|
295
314
|
}
|
|
296
|
-
|
|
315
|
+
// Inline maps inherit the generated file's URL. External maps
|
|
316
|
+
// resolve their sources relative to the map, not the bundle.
|
|
317
|
+
const sourceMap = resolveSourceMap(data, sourceMapUrl);
|
|
297
318
|
if (!sourceMap) {
|
|
298
319
|
continue;
|
|
299
320
|
}
|