@vitest-agent/plugin 5.3.0 → 5.4.1
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/index.d.ts +18 -2
- package/package.json +11 -11
- package/plugin.js +7 -21
- package/reporter.js +47 -18
- package/utils/build-reporter-kit.js +0 -1
- package/utils/stray-output-capture.js +177 -0
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@effected/env";
|
|
2
2
|
import { EnvironmentDetector, PlatformLiveError, PlatformOptions, PlatformServices, SettingsInput, StackFrameInput } from "@vitest-agent/engine";
|
|
3
|
-
import { AgentPluginOptions, AgentReporterOptions, ConsoleMode, CoverageBaselines, CoverageInput, CoverageLevel, CoverageLevelName, CoverageLevelName as CoverageLevelName$1, CoverageReport,
|
|
3
|
+
import { AgentPluginOptions, AgentReporterOptions, ConsoleMode, CoverageBaselines, CoverageInput, CoverageLevel, CoverageLevelName, CoverageLevelName as CoverageLevelName$1, CoverageReport, ResolvedReporterConfig, ResolvedThresholds, RunEvent, Transport, VitestAgentReporterFactory, resolveCoverageInput, validateCoverageConfig } from "@vitest-agent/sdk";
|
|
4
4
|
import { Context, Effect, Layer, Option } from "effect";
|
|
5
5
|
import { TestProjectInlineConfiguration, TestTagDefinition } from "vitest/config";
|
|
6
6
|
import { ResolvedConfig, VitestPluginContext } from "vitest/node";
|
|
@@ -561,7 +561,6 @@ interface AgentReporterConstructorOptions extends AgentReporterOptions {
|
|
|
561
561
|
*/
|
|
562
562
|
coverageMode?: "full" | "ui-only";
|
|
563
563
|
consoleMode?: ConsoleMode;
|
|
564
|
-
format?: OutputFormat;
|
|
565
564
|
mcp?: boolean;
|
|
566
565
|
githubActions?: boolean;
|
|
567
566
|
/**
|
|
@@ -699,6 +698,14 @@ export declare class AgentReporter {
|
|
|
699
698
|
* @internal
|
|
700
699
|
*/
|
|
701
700
|
private closing;
|
|
701
|
+
/**
|
|
702
|
+
* The stray-output capture the plugin installed on this Vitest
|
|
703
|
+
* instance's Logger (`configureVitest`, only when the plugin owns the
|
|
704
|
+
* console), found at `onInit`. Undefined otherwise.
|
|
705
|
+
*
|
|
706
|
+
* @internal
|
|
707
|
+
*/
|
|
708
|
+
private strayCapture;
|
|
702
709
|
/**
|
|
703
710
|
* Whether the user supplied a custom `reporter` factory. A custom
|
|
704
711
|
* reporter may subscribe to the run-event channel in any console mode,
|
|
@@ -795,6 +802,15 @@ export declare class AgentReporter {
|
|
|
795
802
|
* @internal
|
|
796
803
|
*/
|
|
797
804
|
private closeReporters;
|
|
805
|
+
/**
|
|
806
|
+
* Find the stray-output capture on `vitest.logger` and, when a reporter
|
|
807
|
+
* resolved at `onInit` prints stray lines (a live view), route whole
|
|
808
|
+
* lines to it so they print above its drawing. The first reporter that
|
|
809
|
+
* takes a line wins.
|
|
810
|
+
*
|
|
811
|
+
* @internal
|
|
812
|
+
*/
|
|
813
|
+
private routeStrayOutput;
|
|
798
814
|
/**
|
|
799
815
|
* Resolve the reporter factory at run start.
|
|
800
816
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/plugin",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vitest plugin for the vitest-agent ecosystem: owns persistence, classification, baselines, trends, and dispatches rendering to a configurable reporter.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"vitest-agent-mcp": "bin/vitest-agent-mcp.js"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@effect/platform-node": "^4.0.
|
|
47
|
-
"@effected/cli": "^0.
|
|
46
|
+
"@effect/platform-node": "^4.0.2",
|
|
47
|
+
"@effected/cli": "^0.16.0",
|
|
48
48
|
"@effected/env": "^0.1.0",
|
|
49
49
|
"@effected/glob": "^0.10.0",
|
|
50
50
|
"@effected/walker": "^0.15.0",
|
|
51
|
-
"@effected/workspaces": "^0.
|
|
52
|
-
"@vitest-agent/cli": "4.0.
|
|
53
|
-
"@vitest-agent/engine": "0.
|
|
54
|
-
"@vitest-agent/mcp": "5.1
|
|
55
|
-
"@vitest-agent/reporter": "
|
|
56
|
-
"@vitest-agent/sdk": "
|
|
57
|
-
"effect": "^4.0.
|
|
58
|
-
"magic-string": "^1.4.
|
|
51
|
+
"@effected/workspaces": "^0.32.0",
|
|
52
|
+
"@vitest-agent/cli": "4.0.5",
|
|
53
|
+
"@vitest-agent/engine": "1.0.1",
|
|
54
|
+
"@vitest-agent/mcp": "5.2.1",
|
|
55
|
+
"@vitest-agent/reporter": "5.0.1",
|
|
56
|
+
"@vitest-agent/sdk": "7.0.0",
|
|
57
|
+
"effect": "^4.0.2",
|
|
58
|
+
"magic-string": "^1.4.3"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@vitest/coverage-istanbul": "^5.0.0",
|
package/plugin.js
CHANGED
|
@@ -3,6 +3,7 @@ import { resolveThresholds } from "./utils/resolve-thresholds.js";
|
|
|
3
3
|
import { ConfigValidationLive } from "./layers/ConfigValidationLive.js";
|
|
4
4
|
import { ConfigurationError } from "./utils/configuration-error.js";
|
|
5
5
|
import { assertFlatScope } from "./utils/report-writer.js";
|
|
6
|
+
import { installStrayOutputCapture } from "./utils/stray-output-capture.js";
|
|
6
7
|
import { AgentReporter } from "./reporter.js";
|
|
7
8
|
import { buildModuleInfo } from "./utils/build-module-info.js";
|
|
8
9
|
import { DefaultDiscoverStrategy } from "./utils/discover-strategy.js";
|
|
@@ -101,24 +102,6 @@ function ownsStdout(mode) {
|
|
|
101
102
|
return mode !== "passthrough";
|
|
102
103
|
}
|
|
103
104
|
/**
|
|
104
|
-
* Map the resolved {@link ConsoleMode} to the legacy {@link OutputFormat}
|
|
105
|
-
* the existing reporter factories switch on. The new event-sourced renderer
|
|
106
|
-
* does not consult this — it dispatches directly on `kit.config.consoleMode`
|
|
107
|
-
* — but the bundled markdown/terminal/silent/ci-annotations reporters still
|
|
108
|
-
* need a format value to pick which formatter to invoke.
|
|
109
|
-
*
|
|
110
|
-
* @internal
|
|
111
|
-
*/
|
|
112
|
-
function resolveFormat(mode) {
|
|
113
|
-
switch (mode) {
|
|
114
|
-
case "stream":
|
|
115
|
-
case "agent": return "terminal";
|
|
116
|
-
case "ci-annotations": return "ci-annotations";
|
|
117
|
-
case "silent": return "silent";
|
|
118
|
-
case "passthrough": return "vitest-bypass";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
105
|
* Set to the Vitest object reference once we've pushed an aggregating
|
|
123
106
|
* reporter for that Vitest instance. The flag is module-scoped (rather
|
|
124
107
|
* than closure-scoped on the plugin) because Vitest can construct the
|
|
@@ -253,9 +236,8 @@ function AgentPlugin(options = {}, _layer) {
|
|
|
253
236
|
process.stderr.write(line);
|
|
254
237
|
};
|
|
255
238
|
const consoleMode = resolveConsoleMode(options, executor, env, reportOnce);
|
|
256
|
-
const format = resolveFormat(consoleMode);
|
|
257
239
|
const mcp = executor === "agent";
|
|
258
|
-
log("env:", env, "| executor:", executor, "| consoleMode:", consoleMode, "|
|
|
240
|
+
log("env:", env, "| executor:", executor, "| consoleMode:", consoleMode, "| mcp (auto):", mcp);
|
|
259
241
|
if (ownsStdout(consoleMode)) {
|
|
260
242
|
log("stripping console reporters (consoleMode owns stdout)");
|
|
261
243
|
const stripped = stripConsoleReporters(vitest.config.reporters);
|
|
@@ -265,6 +247,11 @@ function AgentPlugin(options = {}, _layer) {
|
|
|
265
247
|
log("suppressing native coverage text reporter");
|
|
266
248
|
coverageCfg.reporter = [];
|
|
267
249
|
}
|
|
250
|
+
const logger = vitest.logger;
|
|
251
|
+
if (logger?.outputStream !== void 0 && logger.errorStream !== void 0) installStrayOutputCapture(logger, {
|
|
252
|
+
stdout: process.stdout,
|
|
253
|
+
stderr: process.stderr
|
|
254
|
+
});
|
|
268
255
|
}
|
|
269
256
|
if (env === "ci-github" && consoleMode !== "silent") {
|
|
270
257
|
log("normalizing the github-actions reporter entry");
|
|
@@ -326,7 +313,6 @@ function AgentPlugin(options = {}, _layer) {
|
|
|
326
313
|
...coverageThresholds !== void 0 ? { coverageThresholds } : {},
|
|
327
314
|
...coverageTargets !== void 0 ? { coverageTargets } : {},
|
|
328
315
|
coverageMode,
|
|
329
|
-
format,
|
|
330
316
|
consoleMode,
|
|
331
317
|
mcp,
|
|
332
318
|
githubActions,
|
package/reporter.js
CHANGED
|
@@ -9,10 +9,11 @@ import { processFailure } from "./utils/process-failure.js";
|
|
|
9
9
|
import { assertReportCapable, createReportWriter } from "./utils/report-writer.js";
|
|
10
10
|
import { resolveNoColor } from "./utils/resolve-no-color.js";
|
|
11
11
|
import { routeRenderedOutput } from "./utils/route-rendered-output.js";
|
|
12
|
+
import { strayOutputCaptureOf } from "./utils/stray-output-capture.js";
|
|
12
13
|
import { stringifyFailureValue } from "./utils/stringify-failure-value.js";
|
|
13
14
|
import { mkdirSync, statSync } from "node:fs";
|
|
14
15
|
import { dirname } from "node:path";
|
|
15
|
-
import { DataReader, DataStore, DetailResolver, EnvironmentDetector, ExecutorResolver,
|
|
16
|
+
import { DataReader, DataStore, DetailResolver, EnvironmentDetector, ExecutorResolver, HistoryTracker, OutputPipelineLive, PathResolutionLive, ensureMigrated, historyKey, resolveDataPath, resolveLogFile, resolveLogLevel } from "@vitest-agent/engine";
|
|
16
17
|
import { buildAgentReport, coerceErrorField, computeTrend, formatFatalError, isTimeoutError, probeHostMetadataFromEnv } from "@vitest-agent/sdk";
|
|
17
18
|
import { Effect, Option, PubSub } from "effect";
|
|
18
19
|
import { randomUUID } from "node:crypto";
|
|
@@ -41,6 +42,14 @@ import { DefaultVitestAgentReporter } from "@vitest-agent/reporter";
|
|
|
41
42
|
*/
|
|
42
43
|
const dbPathCache = /* @__PURE__ */ new Map();
|
|
43
44
|
/**
|
|
45
|
+
* The run's stray output on every project report: it arrives on one stream
|
|
46
|
+
* shared by every project, so no report owns it more than another.
|
|
47
|
+
*/
|
|
48
|
+
const withStrayOutput = (reports, strayOutput) => strayOutput === void 0 ? reports : reports.map((report) => ({
|
|
49
|
+
...report,
|
|
50
|
+
strayOutput
|
|
51
|
+
}));
|
|
52
|
+
/**
|
|
44
53
|
* Safely read a raw Vitest error object's `stacks` array. The property
|
|
45
54
|
* access itself can throw on hostile shapes (live getters — the same
|
|
46
55
|
* ConfigError class of failure `coerceErrorField` guards); frames are
|
|
@@ -347,6 +356,14 @@ var AgentReporter = class {
|
|
|
347
356
|
*/
|
|
348
357
|
closing;
|
|
349
358
|
/**
|
|
359
|
+
* The stray-output capture the plugin installed on this Vitest
|
|
360
|
+
* instance's Logger (`configureVitest`, only when the plugin owns the
|
|
361
|
+
* console), found at `onInit`. Undefined otherwise.
|
|
362
|
+
*
|
|
363
|
+
* @internal
|
|
364
|
+
*/
|
|
365
|
+
strayCapture;
|
|
366
|
+
/**
|
|
350
367
|
* Whether the user supplied a custom `reporter` factory. A custom
|
|
351
368
|
* reporter may subscribe to the run-event channel in any console mode,
|
|
352
369
|
* so the streaming hooks publish events whenever this is true.
|
|
@@ -423,7 +440,6 @@ var AgentReporter = class {
|
|
|
423
440
|
const resolvedTargets = rawTargets ? "global" in rawTargets ? rawTargets : resolveThresholds(rawTargets) : void 0;
|
|
424
441
|
const consoleMode = options.consoleMode ?? "passthrough";
|
|
425
442
|
const consoleOutput = consoleMode === "silent" ? "silent" : "failures";
|
|
426
|
-
const derivedFormat = options.format ?? (consoleMode === "silent" ? "silent" : consoleMode === "stream" || consoleMode === "agent" ? "terminal" : consoleMode === "ci-annotations" ? "ci-annotations" : void 0);
|
|
427
443
|
const githubActions = options.githubActions ?? false;
|
|
428
444
|
const base = {
|
|
429
445
|
...options.cacheDir !== void 0 ? { cacheDir: options.cacheDir } : {},
|
|
@@ -437,7 +453,6 @@ var AgentReporter = class {
|
|
|
437
453
|
githubSummary: githubActions,
|
|
438
454
|
githubSummaryFile: void 0,
|
|
439
455
|
...options.reportScope !== void 0 ? { reportScope: options.reportScope } : {},
|
|
440
|
-
...derivedFormat !== void 0 ? { format: derivedFormat } : {},
|
|
441
456
|
consoleMode,
|
|
442
457
|
...options.mcp !== void 0 ? { mcp: options.mcp } : {},
|
|
443
458
|
...options.projectFilter !== void 0 ? { projectFilter: options.projectFilter } : {},
|
|
@@ -470,7 +485,6 @@ var AgentReporter = class {
|
|
|
470
485
|
includeBareZero: opts.includeBareZero,
|
|
471
486
|
githubActions: opts.githubActions,
|
|
472
487
|
githubSummary: opts.githubSummary,
|
|
473
|
-
format: opts.format ?? "vitest-bypass",
|
|
474
488
|
detail: opts.detail ?? "standard",
|
|
475
489
|
noColor: false,
|
|
476
490
|
coverageMode: opts.coverageMode,
|
|
@@ -500,6 +514,7 @@ var AgentReporter = class {
|
|
|
500
514
|
await this.ensureDbPath();
|
|
501
515
|
} catch {}
|
|
502
516
|
await this.initReporters();
|
|
517
|
+
this.routeStrayOutput(vitest);
|
|
503
518
|
const onClose = vitest?.onClose;
|
|
504
519
|
if (typeof onClose === "function") onClose.call(vitest, () => this.closeReporters());
|
|
505
520
|
}
|
|
@@ -516,6 +531,7 @@ var AgentReporter = class {
|
|
|
516
531
|
*/
|
|
517
532
|
closeReporters() {
|
|
518
533
|
this.closing ??= (async () => {
|
|
534
|
+
this.strayCapture?.route(void 0);
|
|
519
535
|
await Promise.all((this.reporters ?? []).map(async (reporter) => {
|
|
520
536
|
try {
|
|
521
537
|
await reporter.close?.();
|
|
@@ -532,6 +548,25 @@ var AgentReporter = class {
|
|
|
532
548
|
return this.closing;
|
|
533
549
|
}
|
|
534
550
|
/**
|
|
551
|
+
* Find the stray-output capture on `vitest.logger` and, when a reporter
|
|
552
|
+
* resolved at `onInit` prints stray lines (a live view), route whole
|
|
553
|
+
* lines to it so they print above its drawing. The first reporter that
|
|
554
|
+
* takes a line wins.
|
|
555
|
+
*
|
|
556
|
+
* @internal
|
|
557
|
+
*/
|
|
558
|
+
routeStrayOutput(vitest) {
|
|
559
|
+
this.strayCapture = strayOutputCaptureOf(vitest?.logger);
|
|
560
|
+
const printers = (this.reporters ?? []).flatMap((reporter) => reporter.printStrayLine !== void 0 ? [reporter.printStrayLine] : []);
|
|
561
|
+
if (this.strayCapture === void 0 || printers.length === 0) return;
|
|
562
|
+
this.strayCapture.route((stream, line) => {
|
|
563
|
+
for (const print of printers) try {
|
|
564
|
+
if (print(stream, line)) return true;
|
|
565
|
+
} catch {}
|
|
566
|
+
return false;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
535
570
|
* Resolve the reporter factory at run start.
|
|
536
571
|
*
|
|
537
572
|
* The factory is invoked here — before any streaming hook fires — so a
|
|
@@ -553,14 +588,12 @@ var AgentReporter = class {
|
|
|
553
588
|
const initProgram = Effect.gen(function* () {
|
|
554
589
|
const detector = yield* EnvironmentDetector;
|
|
555
590
|
const executorResolver = yield* ExecutorResolver;
|
|
556
|
-
const formatSelector = yield* FormatSelector;
|
|
557
591
|
const detailResolver = yield* DetailResolver;
|
|
558
592
|
const env = yield* detector.detect();
|
|
559
593
|
const executor = yield* executorResolver.resolve(env);
|
|
560
594
|
return {
|
|
561
595
|
env,
|
|
562
596
|
executor,
|
|
563
|
-
format: yield* formatSelector.select(executor, opts.format, env),
|
|
564
597
|
detail: yield* detailResolver.resolve(executor, {
|
|
565
598
|
hasFailures: false,
|
|
566
599
|
belowTargets: false,
|
|
@@ -569,11 +602,10 @@ var AgentReporter = class {
|
|
|
569
602
|
noColor: yield* resolveNoColor
|
|
570
603
|
};
|
|
571
604
|
});
|
|
572
|
-
const { env, executor,
|
|
605
|
+
const { env, executor, detail, noColor } = await Effect.runPromise(initProgram.pipe(Effect.provide(OutputPipelineLive(process.env)), Effect.provide(NodeServices.layer)));
|
|
573
606
|
const kit = buildReporterKit({
|
|
574
607
|
env,
|
|
575
608
|
executor,
|
|
576
|
-
format,
|
|
577
609
|
detail,
|
|
578
610
|
noColor,
|
|
579
611
|
consoleMode: opts.consoleMode ?? "passthrough",
|
|
@@ -647,6 +679,7 @@ var AgentReporter = class {
|
|
|
647
679
|
*/
|
|
648
680
|
onTestRunStart(_specifications) {
|
|
649
681
|
this.startedSpecCount = _specifications.length;
|
|
682
|
+
this.strayCapture?.reset();
|
|
650
683
|
if (this.neutralizedThresholdSnapshot.size > 0) {
|
|
651
684
|
try {
|
|
652
685
|
const thresholds = (this._vitest?.coverageProvider)?.options?.thresholds;
|
|
@@ -1058,6 +1091,8 @@ var AgentReporter = class {
|
|
|
1058
1091
|
if (this.rendered) return;
|
|
1059
1092
|
this.rendered = true;
|
|
1060
1093
|
const errors = unhandledErrors;
|
|
1094
|
+
this.strayCapture?.flush();
|
|
1095
|
+
const strayOutput = this.strayCapture?.snapshot();
|
|
1061
1096
|
if (this.wantsRunEvents() && this.currentRunId !== null) {
|
|
1062
1097
|
let pass = 0;
|
|
1063
1098
|
let fail = 0;
|
|
@@ -1088,7 +1123,8 @@ var AgentReporter = class {
|
|
|
1088
1123
|
durationMs: totalDuration,
|
|
1089
1124
|
// @vitest-agent/ui, which both populate `collectedModules` on their
|
|
1090
1125
|
collectedModules: testModules.length,
|
|
1091
|
-
...errors.length > 0 && { unhandledErrors: errors }
|
|
1126
|
+
...errors.length > 0 && { unhandledErrors: errors },
|
|
1127
|
+
...strayOutput !== void 0 && { strayOutput }
|
|
1092
1128
|
});
|
|
1093
1129
|
}
|
|
1094
1130
|
const modules = testModules;
|
|
@@ -1161,11 +1197,9 @@ var AgentReporter = class {
|
|
|
1161
1197
|
const uiProgram = Effect.gen(function* () {
|
|
1162
1198
|
const detector = yield* EnvironmentDetector;
|
|
1163
1199
|
const executorResolver = yield* ExecutorResolver;
|
|
1164
|
-
const formatSelector = yield* FormatSelector;
|
|
1165
1200
|
const detailResolver = yield* DetailResolver;
|
|
1166
1201
|
const env = yield* detector.detect();
|
|
1167
1202
|
const executor = yield* executorResolver.resolve(env);
|
|
1168
|
-
const format = yield* formatSelector.select(executor, opts.format, env);
|
|
1169
1203
|
const health = {
|
|
1170
1204
|
hasFailures: uiReports.some((r) => r.failedFiles.length > 0 || r.unhandledErrors.length > 0),
|
|
1171
1205
|
belowTargets: false,
|
|
@@ -1176,7 +1210,6 @@ var AgentReporter = class {
|
|
|
1176
1210
|
const kit = buildReporterKit({
|
|
1177
1211
|
env,
|
|
1178
1212
|
executor,
|
|
1179
|
-
format,
|
|
1180
1213
|
detail,
|
|
1181
1214
|
noColor: yield* resolveNoColor,
|
|
1182
1215
|
consoleMode: opts.consoleMode ?? "passthrough",
|
|
@@ -1193,7 +1226,7 @@ var AgentReporter = class {
|
|
|
1193
1226
|
});
|
|
1194
1227
|
const reporters = preBuiltReporters ?? normalizeReporters(opts.reporter(kit));
|
|
1195
1228
|
const renderInput = {
|
|
1196
|
-
reports: uiReports,
|
|
1229
|
+
reports: withStrayOutput(uiReports, strayOutput),
|
|
1197
1230
|
classifications: /* @__PURE__ */ new Map()
|
|
1198
1231
|
};
|
|
1199
1232
|
const allOutputs = reporters.flatMap((r) => r.render(renderInput, kit));
|
|
@@ -1706,11 +1739,9 @@ var AgentReporter = class {
|
|
|
1706
1739
|
const { reports, classifications, trendSummary } = renderInputData;
|
|
1707
1740
|
const detector = yield* EnvironmentDetector;
|
|
1708
1741
|
const executorResolver = yield* ExecutorResolver;
|
|
1709
|
-
const formatSelector = yield* FormatSelector;
|
|
1710
1742
|
const detailResolver = yield* DetailResolver;
|
|
1711
1743
|
const env = yield* detector.detect();
|
|
1712
1744
|
const executor = yield* executorResolver.resolve(env);
|
|
1713
|
-
const format = yield* formatSelector.select(executor, opts.format, env);
|
|
1714
1745
|
const health = {
|
|
1715
1746
|
hasFailures: reports.some((r) => r.failedFiles.length > 0 || r.unhandledErrors.length > 0),
|
|
1716
1747
|
belowTargets: reports.some((r) => {
|
|
@@ -1722,14 +1753,12 @@ var AgentReporter = class {
|
|
|
1722
1753
|
yield* Effect.logDebug("pipeline resolved").pipe(Effect.annotateLogs({
|
|
1723
1754
|
env,
|
|
1724
1755
|
executor,
|
|
1725
|
-
format,
|
|
1726
1756
|
detail
|
|
1727
1757
|
}));
|
|
1728
1758
|
const githubSummaryFile = process.env.GITHUB_STEP_SUMMARY;
|
|
1729
1759
|
const kit = buildReporterKit({
|
|
1730
1760
|
env,
|
|
1731
1761
|
executor,
|
|
1732
|
-
format,
|
|
1733
1762
|
detail,
|
|
1734
1763
|
noColor: yield* resolveNoColor,
|
|
1735
1764
|
consoleMode: opts.consoleMode ?? "passthrough",
|
|
@@ -1746,7 +1775,7 @@ var AgentReporter = class {
|
|
|
1746
1775
|
});
|
|
1747
1776
|
const reporters = preBuiltReporters ?? normalizeReporters(opts.reporter(kit));
|
|
1748
1777
|
const renderInput = {
|
|
1749
|
-
reports,
|
|
1778
|
+
reports: withStrayOutput(reports, strayOutput),
|
|
1750
1779
|
classifications,
|
|
1751
1780
|
...trendSummary !== void 0 && { trendSummary }
|
|
1752
1781
|
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { STRAY_OUTPUT_SOURCE, isEscapeOnly, makeStrayOutputRecorder } from "@vitest-agent/sdk";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { StringDecoder } from "node:string_decoder";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/stray-output-capture.ts
|
|
6
|
+
/**
|
|
7
|
+
* Stray-output capture: a pass-through over Vitest's `Logger.outputStream` /
|
|
8
|
+
* `errorStream`.
|
|
9
|
+
*
|
|
10
|
+
* Why those two streams (vitest\@5.0.0, `src/node/pools/workers/`): the forks
|
|
11
|
+
* and vmForks pools fork each worker with `stdio: "pipe"` and pipe its stdout
|
|
12
|
+
* and stderr into `project.vitest.logger.outputStream` / `errorStream`,
|
|
13
|
+
* captured when the worker is constructed, so every raw byte a worker writes
|
|
14
|
+
* (a grandchild process that inherits the worker's fds included, such as an
|
|
15
|
+
* `execFileSync("git", …)` with default stdio) arrives on them. The threads
|
|
16
|
+
* and vmThreads pools pipe a worker thread's JS-level `process.stdout` /
|
|
17
|
+
* `stderr` the same way, but a child process spawned from a thread inherits
|
|
18
|
+
* the main process's real fds 1/2, so its bytes reach the terminal directly
|
|
19
|
+
* and nothing here can see them. Workers are constructed lazily by the pool,
|
|
20
|
+
* at run time, long after `configureVitest`, so installing there is early
|
|
21
|
+
* enough. The `Logger`'s own `console` was built from the ORIGINAL streams in
|
|
22
|
+
* its constructor, so Vitest's logging bypasses the wrapper; its few direct
|
|
23
|
+
* `outputStream.write` calls are escape-only (cursor show, clear screen),
|
|
24
|
+
* which pass straight through unrecorded.
|
|
25
|
+
*
|
|
26
|
+
* Every byte is recorded (bounded: counts, a few sample lines). While a
|
|
27
|
+
* printer is routed (the reporter's live view is up) and the stream's
|
|
28
|
+
* original destination is the process terminal, bytes are line-buffered and
|
|
29
|
+
* each whole line goes to the printer, which prints it above the live frame;
|
|
30
|
+
* otherwise bytes pass through to the original stream unchanged. The
|
|
31
|
+
* original is whatever stream object the Logger held: under MCP's
|
|
32
|
+
* `run_tests` that is the server's null sink, so worker output still never
|
|
33
|
+
* reaches the JSON-RPC stdout, and it is never routed.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* The most text a stream holds back waiting for a newline. Past it, the held
|
|
37
|
+
* text is routed as a line of its own, so a child that never writes a newline
|
|
38
|
+
* cannot grow the buffer without bound.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
const MAX_HELD_CHARS = 8192;
|
|
43
|
+
const isCapture = (value) => typeof value === "object" && value !== null && "route" in value && "snapshot" in value;
|
|
44
|
+
/**
|
|
45
|
+
* The capture installed on `logger`, if any.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
const strayOutputCaptureOf = (logger) => {
|
|
50
|
+
if (typeof logger !== "object" || logger === null) return void 0;
|
|
51
|
+
const value = logger[STRAY_OUTPUT_SOURCE];
|
|
52
|
+
return isCapture(value) ? value : void 0;
|
|
53
|
+
};
|
|
54
|
+
const DELEGATED = [
|
|
55
|
+
"isTTY",
|
|
56
|
+
"columns",
|
|
57
|
+
"rows",
|
|
58
|
+
"getColorDepth",
|
|
59
|
+
"hasColors",
|
|
60
|
+
"getWindowSize"
|
|
61
|
+
];
|
|
62
|
+
var StrayStream = class extends Writable {
|
|
63
|
+
original;
|
|
64
|
+
take;
|
|
65
|
+
constructor(original, take) {
|
|
66
|
+
super({ decodeStrings: false });
|
|
67
|
+
this.original = original;
|
|
68
|
+
this.take = take;
|
|
69
|
+
for (const key of DELEGATED) {
|
|
70
|
+
if (!(key in original)) continue;
|
|
71
|
+
Object.defineProperty(this, key, {
|
|
72
|
+
configurable: true,
|
|
73
|
+
enumerable: true,
|
|
74
|
+
get: () => {
|
|
75
|
+
const value = original[key];
|
|
76
|
+
return typeof value === "function" ? value.bind(original) : value;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
_write(chunk, encoding, callback) {
|
|
82
|
+
try {
|
|
83
|
+
this.take(chunk, encoding);
|
|
84
|
+
callback();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Wrap `logger`'s output and error streams with the stray-output capture, once
|
|
92
|
+
* per Logger (a second call returns the first capture). The capture is
|
|
93
|
+
* published on the Logger under `STRAY_OUTPUT_SOURCE`, so MCP's `run_tests`
|
|
94
|
+
* (which cannot import the plugin) reads the run's stray output with the
|
|
95
|
+
* sdk's `readStrayOutput`.
|
|
96
|
+
*
|
|
97
|
+
* @param logger - the Vitest instance's `logger`
|
|
98
|
+
* @param terminal - the process's own stdout and stderr
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
const installStrayOutputCapture = (logger, terminal) => {
|
|
103
|
+
const existing = strayOutputCaptureOf(logger);
|
|
104
|
+
if (existing !== void 0) return existing;
|
|
105
|
+
const recorder = makeStrayOutputRecorder();
|
|
106
|
+
const originals = {
|
|
107
|
+
stdout: logger.outputStream,
|
|
108
|
+
stderr: logger.errorStream
|
|
109
|
+
};
|
|
110
|
+
const decoders = {
|
|
111
|
+
stdout: new StringDecoder("utf8"),
|
|
112
|
+
stderr: new StringDecoder("utf8")
|
|
113
|
+
};
|
|
114
|
+
const held = {
|
|
115
|
+
stdout: "",
|
|
116
|
+
stderr: ""
|
|
117
|
+
};
|
|
118
|
+
let printer;
|
|
119
|
+
const flushHeld = (stream) => {
|
|
120
|
+
const text = held[stream];
|
|
121
|
+
if (text === "") return;
|
|
122
|
+
held[stream] = "";
|
|
123
|
+
originals[stream].write(text);
|
|
124
|
+
};
|
|
125
|
+
const take = (stream) => (chunk, encoding) => {
|
|
126
|
+
const original = originals[stream];
|
|
127
|
+
if (typeof chunk === "string" && isEscapeOnly(chunk)) {
|
|
128
|
+
original.write(chunk, encoding);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const text = typeof chunk === "string" ? chunk : decoders[stream].write(chunk);
|
|
132
|
+
recorder.write(stream, text);
|
|
133
|
+
const route = printer !== void 0 && original === terminal[stream] ? printer : void 0;
|
|
134
|
+
if (route === void 0) {
|
|
135
|
+
original.write(chunk, encoding);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const lines = (held[stream] + text).split("\n");
|
|
139
|
+
const rest = lines.pop() ?? "";
|
|
140
|
+
if (rest.length > 8192) {
|
|
141
|
+
lines.push(rest);
|
|
142
|
+
held[stream] = "";
|
|
143
|
+
} else held[stream] = rest;
|
|
144
|
+
for (const line of lines) if (!route(stream, line)) original.write(`${line}\n`);
|
|
145
|
+
};
|
|
146
|
+
const flushHeldThrough = (stream) => {
|
|
147
|
+
const text = held[stream];
|
|
148
|
+
if (text === "") return;
|
|
149
|
+
held[stream] = "";
|
|
150
|
+
if (printer === void 0 || !printer(stream, text)) originals[stream].write(text);
|
|
151
|
+
};
|
|
152
|
+
logger.outputStream = new StrayStream(originals.stdout, take("stdout"));
|
|
153
|
+
logger.errorStream = new StrayStream(originals.stderr, take("stderr"));
|
|
154
|
+
const capture = {
|
|
155
|
+
snapshot: recorder.snapshot,
|
|
156
|
+
reset: recorder.reset,
|
|
157
|
+
route: (next) => {
|
|
158
|
+
if (next === void 0) {
|
|
159
|
+
flushHeld("stdout");
|
|
160
|
+
flushHeld("stderr");
|
|
161
|
+
}
|
|
162
|
+
printer = next;
|
|
163
|
+
},
|
|
164
|
+
flush: () => {
|
|
165
|
+
flushHeldThrough("stdout");
|
|
166
|
+
flushHeldThrough("stderr");
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
Object.defineProperty(logger, STRAY_OUTPUT_SOURCE, {
|
|
170
|
+
value: capture,
|
|
171
|
+
configurable: true
|
|
172
|
+
});
|
|
173
|
+
return capture;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
export { MAX_HELD_CHARS, installStrayOutputCapture, strayOutputCaptureOf };
|