@vitest-agent/plugin 5.3.0 → 5.4.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/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, OutputFormat, ResolvedReporterConfig, ResolvedThresholds, RunEvent, Transport, VitestAgentReporterFactory, resolveCoverageInput, validateCoverageConfig } from "@vitest-agent/sdk";
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.0",
3
+ "version": "5.4.0",
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.0",
47
- "@effected/cli": "^0.12.0",
46
+ "@effect/platform-node": "^4.0.2",
47
+ "@effected/cli": "^0.15.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.31.0",
52
- "@vitest-agent/cli": "4.0.3",
53
- "@vitest-agent/engine": "0.6.1",
54
- "@vitest-agent/mcp": "5.1.9",
55
- "@vitest-agent/reporter": "4.1.1",
56
- "@vitest-agent/sdk": "6.1.0",
57
- "effect": "^4.0.0",
58
- "magic-string": "^1.4.2"
51
+ "@effected/workspaces": "^0.32.0",
52
+ "@vitest-agent/cli": "4.0.4",
53
+ "@vitest-agent/engine": "1.0.0",
54
+ "@vitest-agent/mcp": "5.2.0",
55
+ "@vitest-agent/reporter": "5.0.0",
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, "| format:", format, "| mcp (auto):", mcp);
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, FormatSelector, HistoryTracker, OutputPipelineLive, PathResolutionLive, ensureMigrated, historyKey, resolveDataPath, resolveLogFile, resolveLogLevel } from "@vitest-agent/engine";
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, format, detail, noColor } = await Effect.runPromise(initProgram.pipe(Effect.provide(OutputPipelineLive(process.env)), Effect.provide(NodeServices.layer)));
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
  };
@@ -15,7 +15,6 @@ const buildReporterKit = (input) => {
15
15
  includeBareZero: false,
16
16
  githubActions: input.githubActions,
17
17
  githubSummary,
18
- format: input.format,
19
18
  detail: input.detail,
20
19
  noColor: input.noColor,
21
20
  coverageMode: input.coverageMode,
@@ -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 };
package/version.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @public
10
10
  */
11
- const CURRENT_PLUGIN_VERSION = "5.3.0";
11
+ const CURRENT_PLUGIN_VERSION = "5.4.0";
12
12
 
13
13
  //#endregion
14
14
  export { CURRENT_PLUGIN_VERSION };