monocart-coverage-reports 3.1.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.
@@ -4,19 +4,79 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
4
4
 
5
5
  The following inlined npm packages have license or notice files included below.
6
6
 
7
+ - @isaacs/nonrecursive-glob-match@1.0.2
7
8
  - balanced-match@4.0.4 (https://github.com/juliangruber/balanced-match)
8
- - brace-expansion@5.0.9 (https://github.com/juliangruber/brace-expansion)
9
+ - brace-expansion@5.0.12 (https://github.com/juliangruber/brace-expansion)
9
10
  - diff-sequences@29.6.3 (https://github.com/jestjs/jest)
10
11
  - find-up@8.0.0 (https://github.com/sindresorhus/find-up)
11
12
  - locate-path@8.0.0 (https://github.com/sindresorhus/locate-path)
12
- - minimatch@10.2.6 (https://github.com/isaacs/minimatch)
13
+ - minimatch@11.0.2 (https://github.com/isaacs/minimatch)
13
14
  - nanoid@3.3.20 (https://github.com/ai/nanoid)
14
15
  - picocolors@1.1.1 (https://github.com/alexeyraspopov/picocolors)
15
- - postcss@8.5.31 (https://github.com/postcss/postcss)
16
+ - postcss@8.5.32 (https://github.com/postcss/postcss)
16
17
  - source-map-js@1.2.2 (https://github.com/7rulnik/source-map-js)
17
18
  - supports-color@11.0.0 (https://github.com/chalk/supports-color)
18
19
  - unicorn-magic@0.3.0 (https://github.com/sindresorhus/unicorn-magic)
19
20
 
21
+ %% @isaacs/nonrecursive-glob-match@1.0.2 NOTICES AND INFORMATION BEGIN HERE
22
+ =========================================
23
+ # Blue Oak Model License
24
+
25
+ Version 1.0.0
26
+
27
+ ## Purpose
28
+
29
+ This license gives everyone as much permission to work with
30
+ this software as possible, while protecting contributors
31
+ from liability.
32
+
33
+ ## Acceptance
34
+
35
+ In order to receive this license, you must agree to its
36
+ rules. The rules of this license are both obligations
37
+ under that agreement and conditions to your license.
38
+ You must not do anything with this software that triggers
39
+ a rule that you cannot or will not follow.
40
+
41
+ ## Copyright
42
+
43
+ Each contributor licenses you to do everything with this
44
+ software that would otherwise infringe that contributor's
45
+ copyright in it.
46
+
47
+ ## Notices
48
+
49
+ You must ensure that everyone who gets a copy of
50
+ any part of this software from you, with or without
51
+ changes, also gets the text of this license or a link to
52
+ <https://blueoakcouncil.org/license/1.0.0>.
53
+
54
+ ## Excuse
55
+
56
+ If anyone notifies you in writing that you have not
57
+ complied with [Notices](#notices), you can keep your
58
+ license by taking all practical steps to comply within 30
59
+ days after the notice. If you do not do so, your license
60
+ ends immediately.
61
+
62
+ ## Patent
63
+
64
+ Each contributor licenses you to do everything with this
65
+ software that would otherwise infringe any patent claims
66
+ they can license or become able to license.
67
+
68
+ ## Reliability
69
+
70
+ No contributor can revoke this license.
71
+
72
+ ## No Liability
73
+
74
+ ***As far as the law allows, this software comes as is,
75
+ without any warranty or condition, and no contributor
76
+ will be liable to anyone for any damages related to this
77
+ software or this license, under any kind of legal claim.***
78
+ =========================================
79
+ END OF @isaacs/nonrecursive-glob-match@1.0.2 NOTICES AND INFORMATION
20
80
  %% balanced-match@4.0.4 NOTICES AND INFORMATION BEGIN HERE
21
81
  =========================================
22
82
  (MIT)
@@ -44,7 +104,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
104
  SOFTWARE.
45
105
  =========================================
46
106
  END OF balanced-match@4.0.4 NOTICES AND INFORMATION
47
- %% brace-expansion@5.0.9 NOTICES AND INFORMATION BEGIN HERE
107
+ %% brace-expansion@5.0.12 NOTICES AND INFORMATION BEGIN HERE
48
108
  =========================================
49
109
  MIT License
50
110
 
@@ -70,7 +130,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
70
130
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
71
131
  SOFTWARE.
72
132
  =========================================
73
- END OF brace-expansion@5.0.9 NOTICES AND INFORMATION
133
+ END OF brace-expansion@5.0.12 NOTICES AND INFORMATION
74
134
  %% diff-sequences@29.6.3 NOTICES AND INFORMATION BEGIN HERE
75
135
  =========================================
76
136
  MIT License
@@ -122,7 +182,7 @@ The above copyright notice and this permission notice shall be included in all c
122
182
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
123
183
  =========================================
124
184
  END OF locate-path@8.0.0 NOTICES AND INFORMATION
125
- %% minimatch@10.2.6 NOTICES AND INFORMATION BEGIN HERE
185
+ %% minimatch@11.0.2 NOTICES AND INFORMATION BEGIN HERE
126
186
  =========================================
127
187
  # Blue Oak Model License
128
188
 
@@ -180,7 +240,7 @@ without any warranty or condition, and no contributor
180
240
  will be liable to anyone for any damages related to this
181
241
  software or this license, under any kind of legal claim._**
182
242
  =========================================
183
- END OF minimatch@10.2.6 NOTICES AND INFORMATION
243
+ END OF minimatch@11.0.2 NOTICES AND INFORMATION
184
244
  %% nanoid@3.3.20 NOTICES AND INFORMATION BEGIN HERE
185
245
  =========================================
186
246
  The MIT License (MIT)
@@ -224,7 +284,7 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
224
284
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
225
285
  =========================================
226
286
  END OF picocolors@1.1.1 NOTICES AND INFORMATION
227
- %% postcss@8.5.31 NOTICES AND INFORMATION BEGIN HERE
287
+ %% postcss@8.5.32 NOTICES AND INFORMATION BEGIN HERE
228
288
  =========================================
229
289
  The MIT License (MIT)
230
290
 
@@ -247,7 +307,7 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
247
307
  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
248
308
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
249
309
  =========================================
250
- END OF postcss@8.5.31 NOTICES AND INFORMATION
310
+ END OF postcss@8.5.32 NOTICES AND INFORMATION
251
311
  %% source-map-js@1.2.2 NOTICES AND INFORMATION BEGIN HERE
252
312
  =========================================
253
313
  Copyright (c) 2009-2011, Mozilla Foundation and contributors
@@ -0,0 +1,191 @@
1
+ import fs from 'node:fs';
2
+ import { Transform } from 'node:stream';
3
+
4
+ const reporterOptions = new URL(import.meta.url).searchParams;
5
+
6
+ const getStatus = (type, data) => {
7
+ // Node also accepts an empty string as a skip/todo reason.
8
+ if (data.skip === true || typeof data.skip === 'string') {
9
+ return 'skipped';
10
+ }
11
+ if (data.todo === true || typeof data.todo === 'string') {
12
+ return 'todo';
13
+ }
14
+ const failureType = data.details.error?.failureType;
15
+ if (['cancelledByParent', 'testTimeoutFailure', 'testAborted'].includes(failureType)) {
16
+ return 'cancelled';
17
+ }
18
+ return type === 'test:pass' ? 'passed' : 'failed';
19
+ };
20
+
21
+ const reservedErrorKeys = new Set(['name', 'message', '__proto__', 'constructor', 'prototype']);
22
+
23
+ const getErrorString = (error, key, fallback) => {
24
+ // Standard Error names are usually inherited. Resolve only these fixed
25
+ // fields through descriptors, without invoking accessors or coercion hooks.
26
+ for (let current = error; current; current = Object.getPrototypeOf(current)) {
27
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
28
+ if (descriptor) {
29
+ return typeof descriptor.value === 'string' ? descriptor.value : fallback;
30
+ }
31
+ }
32
+ return fallback;
33
+ };
34
+
35
+ const getErrorPrimitive = (value) => {
36
+ if (value === null) {
37
+ return null;
38
+ }
39
+ switch (typeof value) {
40
+ case 'string':
41
+ case 'boolean':
42
+ return value;
43
+ case 'number':
44
+ if (Number.isFinite(value)) {
45
+ return value;
46
+ }
47
+ return;
48
+ case 'bigint':
49
+ return String(value);
50
+ default:
51
+ break;
52
+ }
53
+ };
54
+
55
+ const getError = (error) => {
56
+ if (!error) {
57
+ return;
58
+ }
59
+ const result = {
60
+ name: getErrorString(error, 'name', 'Error'),
61
+ message: getErrorString(error, 'message', '')
62
+ };
63
+ // Copy only own enumerable data properties, never getters or object graphs.
64
+ // Primitive fields may still contain secrets or large strings; redact them
65
+ // in onTestEnd before raw/debug export when appropriate.
66
+ for (const key of Object.keys(error)) {
67
+ if (reservedErrorKeys.has(key)) {
68
+ continue;
69
+ }
70
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
71
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) {
72
+ continue;
73
+ }
74
+ const value = getErrorPrimitive(descriptor.value);
75
+ if (typeof value !== 'undefined') {
76
+ result[key] = value;
77
+ }
78
+ }
79
+ return result;
80
+ };
81
+
82
+ const getTestResult = (type, data) => ({
83
+ name: data.name,
84
+ type: data.details.type || 'test',
85
+ nesting: data.nesting,
86
+ testNumber: data.testNumber,
87
+ file: data.file,
88
+ line: data.line,
89
+ column: data.column,
90
+ duration: data.details.duration_ms,
91
+ status: getStatus(type, data),
92
+ skip: data.skip,
93
+ todo: data.todo,
94
+ error: getError(data.details.error)
95
+ });
96
+
97
+ const getSummary = (data) => ({
98
+ tests: data.counts.tests,
99
+ suites: data.counts.suites,
100
+ passed: data.counts.passed,
101
+ failed: data.counts.failed,
102
+ cancelled: data.counts.cancelled,
103
+ skipped: data.counts.skipped,
104
+ todo: data.counts.todo,
105
+ duration: data.duration_ms,
106
+ success: data.success
107
+ });
108
+
109
+ // Node instantiates this stream without arguments. Per-collector options are
110
+ // passed through its module URL, not process.env, so collectors stay isolated.
111
+ export default class TestReporter extends Transform {
112
+ constructor(id = reporterOptions.get('id'), metadataPath = reporterOptions.get('meta')) {
113
+ super({
114
+ writableObjectMode: true
115
+ });
116
+ this.id = id;
117
+ this.metadataPath = metadataPath;
118
+ this.complete = false;
119
+ this.summary = null;
120
+ this.recordCount = 0;
121
+ this.pending = [];
122
+ this.pendingBytes = 0;
123
+ const header = `${JSON.stringify({
124
+ id, framework: 'node:test'
125
+ }).slice(0, -1)},"tests":[`;
126
+ this.byteLength = Buffer.byteLength(header);
127
+ this.push(header);
128
+ }
129
+
130
+ _transform(event, _encoding, callback) {
131
+ let chunk;
132
+ try {
133
+ const { type, data } = event;
134
+ if (type === 'test:summary' && !data.file) {
135
+ this.complete = true;
136
+ this.summary = getSummary(data);
137
+ } else if (type === 'test:pass' || type === 'test:fail') {
138
+ chunk = this.addRecord(getTestResult(type, data));
139
+ }
140
+ } catch (error) {
141
+ callback(error);
142
+ return;
143
+ }
144
+ callback(null, chunk);
145
+ }
146
+
147
+ addRecord(data) {
148
+ const record = `${this.recordCount ? ',' : ''}${JSON.stringify(data)}`;
149
+ const byteLength = Buffer.byteLength(record);
150
+ this.recordCount += 1;
151
+ this.byteLength += byteLength;
152
+ this.pending.push(record);
153
+ this.pendingBytes += byteLength;
154
+ // Bound memory while avoiding a separate filesystem write for every
155
+ // small record. Emit the first record without delay.
156
+ if (this.recordCount === 1 || this.pendingBytes >= 64 * 1024) {
157
+ return this.takePending();
158
+ }
159
+ }
160
+
161
+ takePending() {
162
+ const chunk = this.pending.join('');
163
+ this.pending = [];
164
+ this.pendingBytes = 0;
165
+ return chunk;
166
+ }
167
+
168
+ _flush(callback) {
169
+ let chunk;
170
+ try {
171
+ const { complete, summary } = this;
172
+ chunk = `],${JSON.stringify({
173
+ complete, summary
174
+ }).slice(1)}`;
175
+ this.byteLength += Buffer.byteLength(chunk);
176
+ chunk = this.takePending() + chunk;
177
+ if (this.metadataPath) {
178
+ // The parent checks this small completion record and the final
179
+ // byte count after child exit; no full JSON read is needed
180
+ // unless onTestEnd requests the complete result object.
181
+ fs.writeFileSync(this.metadataPath, JSON.stringify({
182
+ id: this.id, complete, byteLength: this.byteLength
183
+ }));
184
+ }
185
+ } catch (error) {
186
+ callback(error);
187
+ return;
188
+ }
189
+ callback(null, chunk);
190
+ }
191
+ }
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs';
2
+ import { Util } from '../utils/util.js';
3
+
4
+ // Only tokenize NODE_OPTIONS to count option names, keeping quoted values
5
+ // together. The original options and values are passed to Node untouched.
6
+ const splitNodeOptions = (value) => {
7
+ const tokens = value.match(/(?:[^\s"]|"(?:\\.|[^"\\])*")+/g) || [];
8
+ return tokens.map((token) => token.replaceAll('"', ''));
9
+ };
10
+
11
+ const getRunnerOptions = (args) => {
12
+ const index = args.indexOf('--');
13
+ return index === -1 ? args : args.slice(0, index);
14
+ };
15
+
16
+ // Node treats underscores and hyphens as equivalent in option names. Inspect
17
+ // only names here; the original arguments and their values remain untouched.
18
+ const countOption = (args, name) => args.filter((arg) => arg.split('=', 1)[0].replaceAll('_', '-') === name).length;
19
+
20
+ const getReporterArgs = (args) => {
21
+ const reporters = countOption(args, '--test-reporter');
22
+ const destinations = countOption(args, '--test-reporter-destination');
23
+ // Preserve Node's validation instead of accidentally fixing an invalid pair.
24
+ if ((reporters > 1 || destinations > 0) && reporters !== destinations) {
25
+ throw new Error('The number of --test-reporter options must match --test-reporter-destination options');
26
+ }
27
+ const extraArgs = [];
28
+ if (!reporters) {
29
+ // Node 22 defaults to TAP when stdout is not a TTY; Node 23+ uses spec.
30
+ const reporter = Number(process.versions.node.split('.')[0]) < 23 && !process.stdout.isTTY ? 'tap' : 'spec';
31
+ extraArgs.push(`--test-reporter=${reporter}`);
32
+ }
33
+ if (!destinations) {
34
+ extraArgs.push('--test-reporter-destination=stdout');
35
+ }
36
+ return extraArgs;
37
+ };
38
+
39
+ const readMetadata = (metadataPath, resultsPath, id) => {
40
+ // This is an internal completion record, not an external report input.
41
+ // Keep the no-hook path bounded even if an interrupted file is malformed.
42
+ if (fs.statSync(metadataPath).size > 1024) {
43
+ throw new Error('Invalid Node test report metadata');
44
+ }
45
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
46
+ if (metadata?.id !== id || typeof metadata.complete !== 'boolean' || !Number.isSafeInteger(metadata.byteLength) || metadata.byteLength <= 0) {
47
+ throw new Error('Invalid Node test report metadata');
48
+ }
49
+ if (metadata.byteLength !== fs.statSync(resultsPath).size) {
50
+ throw new Error('Incomplete Node test report output');
51
+ }
52
+ return metadata;
53
+ };
54
+
55
+ const validateResults = (results, id) => {
56
+ if (results?.id !== id || results.framework !== 'node:test' || typeof results.complete !== 'boolean' || !Array.isArray(results.tests)) {
57
+ throw new Error('Invalid Node test report');
58
+ }
59
+ };
60
+
61
+ const createTestResultsCollector = (runnerArgs, cacheDir) => {
62
+ const args = [... splitNodeOptions(process.env.NODE_OPTIONS || ''), ... getRunnerOptions(runnerArgs)];
63
+ const extraArgs = getReporterArgs(args);
64
+ fs.mkdirSync(cacheDir, {
65
+ recursive: true
66
+ });
67
+ const id = Util.uid();
68
+ const { cachePath } = Util.getCacheFileInfo('test-results', id, cacheDir);
69
+ const resultsPath = `${cachePath}.tmp`;
70
+ const metadataPath = `${cachePath}.meta.tmp`;
71
+ const reporterURL = new URL('./test-reporter.js', import.meta.url);
72
+ reporterURL.searchParams.set('id', id);
73
+ reporterURL.searchParams.set('meta', metadataPath);
74
+ extraArgs.push(`--test-reporter=${reporterURL.href}`, `--test-reporter-destination=${resultsPath}`);
75
+ return {
76
+ args: extraArgs,
77
+ readMetadata: () => readMetadata(metadataPath, resultsPath, id),
78
+ read: () => {
79
+ const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
80
+ validateResults(results, id);
81
+ return results;
82
+ },
83
+ publish: (results) => {
84
+ readMetadata(metadataPath, resultsPath, id);
85
+ if (results) {
86
+ validateResults(results, id);
87
+ // onTestEnd can redact messages/names before raw/debug export.
88
+ // Only configured hooks require a full read and rewrite.
89
+ fs.writeFileSync(resultsPath, JSON.stringify(results));
90
+ }
91
+ fs.renameSync(resultsPath, cachePath);
92
+ },
93
+ dispose: () => {
94
+ fs.rmSync(resultsPath, {
95
+ force: true
96
+ });
97
+ fs.rmSync(metadataPath, {
98
+ force: true
99
+ });
100
+ }
101
+ };
102
+ };
103
+
104
+ export { createTestResultsCollector, getRunnerOptions };
@@ -0,0 +1,115 @@
1
+ import path from 'node:path';
2
+ import { Util } from '../utils/util.js';
3
+ import { createTestResultsCollector, getRunnerOptions } from './test-results.js';
4
+
5
+ // Also recognize `mcr -c config -- test targets -- runner-options` without
6
+ // falling through to the shell's unrelated `test` command. Only MCR options
7
+ // before the first separator are parsed in that form.
8
+ const getTestCommand = (args, subArgs) => {
9
+ if (args[0] === 'test') {
10
+ return {
11
+ targets: args.slice(1), runnerArgs: subArgs.slice(1)
12
+ };
13
+ }
14
+ if (args.length || subArgs[1] !== 'test') {
15
+ return;
16
+ }
17
+ const remaining = subArgs.slice(2);
18
+ const index = remaining.indexOf('--');
19
+ if (index === -1) {
20
+ return {
21
+ targets: remaining, runnerArgs: []
22
+ };
23
+ }
24
+ return {
25
+ targets: remaining.slice(0, index), runnerArgs: remaining.slice(index + 1)
26
+ };
27
+ };
28
+
29
+ // Node owns target parsing, discovery and validation. Preserve each argument.
30
+ const getTestTargets = (test) => {
31
+ if (!test || (Array.isArray(test) && !test.length)) {
32
+ Util.logInfo('No test targets configured; skipping tests.');
33
+ return;
34
+ }
35
+ return Array.isArray(test) ? test : [test];
36
+ };
37
+
38
+ const initTestResultsCollector = (coverageOptions, runnerArgs, childArgs) => {
39
+ // Read results before coverage generation, which may remove the cache.
40
+ const testResultsCollector = createTestResultsCollector(runnerArgs, coverageOptions.cacheDir);
41
+ // Internal options must stay before Node's end-of-options marker.
42
+ childArgs.splice(1 + getRunnerOptions(runnerArgs).length, 0, ... testResultsCollector.args);
43
+ return testResultsCollector;
44
+ };
45
+
46
+ const runTestEnd = async (testResultsCollector, onTestEnd) => {
47
+ if (!testResultsCollector) {
48
+ return 0;
49
+ }
50
+ try {
51
+ const metadata = testResultsCollector.readMetadata();
52
+ const failed = !metadata.complete;
53
+ if (failed) {
54
+ Util.logError('Node test report is incomplete');
55
+ }
56
+ let testResults;
57
+ if (typeof onTestEnd === 'function') {
58
+ testResults = testResultsCollector.read();
59
+ await onTestEnd(testResults);
60
+ }
61
+ // Publish only after a successful hook, so redaction reaches raw/debug
62
+ // artifacts. Never fall back to unredacted data if the hook throws.
63
+ testResultsCollector.publish(testResults);
64
+ return process.exitCode || (failed ? 1 : 0);
65
+ } catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ Util.logError(`Node test report collection or onTestEnd failed: ${message}`);
68
+ return 1;
69
+ } finally {
70
+ // Do not export unfinished reporter output through coverage raw reports.
71
+ testResultsCollector.dispose();
72
+ }
73
+ };
74
+
75
+ const createTestRunner = (coverageOptions, runnerArgs = []) => {
76
+ const targets = getTestTargets(coverageOptions.test);
77
+ if (!targets) {
78
+ return;
79
+ }
80
+ const childArgs = ['--test', ... runnerArgs, ... targets];
81
+ let testResultsCollector;
82
+ let onTestEnd;
83
+ return {
84
+ initCoverageDir: (nodeV8CoverageDir) => {
85
+ // Tests may spawn children with a different working directory.
86
+ process.env.NODE_V8_COVERAGE = path.resolve(nodeV8CoverageDir);
87
+ },
88
+ onClose: async (generateCoverage) => {
89
+ const testExitCode = await runTestEnd(testResultsCollector, onTestEnd);
90
+ try {
91
+ const report = await generateCoverage();
92
+ if (!report) {
93
+ throw new Error('No coverage report was generated');
94
+ }
95
+ } finally {
96
+ // A later coverage hook must not mask a test hook/collection failure.
97
+ if (testExitCode) {
98
+ process.exitCode = process.exitCode || testExitCode;
99
+ }
100
+ }
101
+ },
102
+ execute: async (executeSubprocess, onClose) => {
103
+ // Always collect results; onStart may still configure the optional hook.
104
+ onTestEnd = coverageOptions.onTestEnd;
105
+ testResultsCollector = initTestResultsCollector(coverageOptions, runnerArgs, childArgs);
106
+ try {
107
+ return await executeSubprocess(process.execPath, childArgs, onClose);
108
+ } finally {
109
+ testResultsCollector?.dispose();
110
+ }
111
+ }
112
+ };
113
+ };
114
+
115
+ export { getTestCommand, createTestRunner };
package/migrate-to-3.md CHANGED
@@ -158,6 +158,10 @@ Custom reporter interfaces are unchanged: Istanbul reporters implement `execute(
158
158
  - `mcr test` is a new **optional** command; you do not have to replace your existing test runner.
159
159
  - `outputFilter` is a new **optional** filter, disabled by default. You do not have to replace `entryFilter` / `sourceFilter` with it.
160
160
 
161
+ ### Review test-result artifacts when upgrading to 3.2.0+
162
+
163
+ Starting in 3.2.0, `mcr test` always collects structured test results, even without `onTestEnd`. Raw exports (including ZIP) and debug caches can now include `test-results-<id>.json`, containing test names, paths and error strings that may be sensitive. Review existing automatic artifact uploads and retention policies. Use [`onTestEnd`](README.md#ontestend--mcr-test-only) to redact persisted results before publication; terminal output and temporary files already written are not redacted. Raw imports still merge coverage only, not test results. Other commands and API calls do not collect these test results.
164
+
161
165
  ## Migration checklist
162
166
 
163
167
  - [ ] Upgrade Node.js in local, CI, container and relevant child-process environments.