@vitest-agent/plugin 5.1.2 → 5.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/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import "@effected/env";
1
2
  import { EnvironmentDetector, PlatformOptions, PlatformServices, SettingsInput, StackFrameInput } from "@vitest-agent/engine";
2
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
4
  import { Context, Effect, Layer, Option } from "effect";
@@ -693,6 +694,13 @@ export declare class AgentReporter {
693
694
  * @internal
694
695
  */
695
696
  private reporters;
697
+ /**
698
+ * The one close of the run-event channel and the reporters, started at
699
+ * Vitest's close (see `closeReporters`). Undefined until then.
700
+ *
701
+ * @internal
702
+ */
703
+ private closing;
696
704
  /**
697
705
  * Whether the user supplied a custom `reporter` factory. A custom
698
706
  * reporter may subscribe to the run-event channel in any console mode,
@@ -777,6 +785,18 @@ export declare class AgentReporter {
777
785
  * @param vitest - The Vitest instance
778
786
  */
779
787
  onInit(vitest: unknown): Promise<void>;
788
+ /**
789
+ * Close the reporters resolved at `onInit`, then end the run-event
790
+ * channel, once, at Vitest's close. Reporters first: a live view folds
791
+ * every event still queued in its subscription and commits its last
792
+ * frame in `close`, and `PubSub.shutdown` would drop whatever a
793
+ * subscriber has not taken yet. The shutdown after is harmless to a
794
+ * closed view and ends any other subscriber. A failing close is logged,
795
+ * never thrown — Vitest is shutting down.
796
+ *
797
+ * @internal
798
+ */
799
+ private closeReporters;
780
800
  /**
781
801
  * Resolve the reporter factory at run start.
782
802
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/plugin",
3
- "version": "5.1.2",
3
+ "version": "5.2.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,15 +43,18 @@
43
43
  "vitest-agent-mcp": "bin/vitest-agent-mcp.js"
44
44
  },
45
45
  "dependencies": {
46
- "@effect/platform-node": "4.0.0-rc.118",
47
- "@effected/glob": "^0.9.0",
48
- "@effected/workspaces": "^0.30.3",
49
- "@vitest-agent/cli": "3.2.7",
50
- "@vitest-agent/engine": "0.3.2",
51
- "@vitest-agent/mcp": "5.1.5",
52
- "@vitest-agent/reporter": "3.1.3",
53
- "@vitest-agent/sdk": "5.1.3",
54
- "effect": "4.0.0-rc.118",
46
+ "@effect/platform-node": "^4.0.0",
47
+ "@effected/cli": "^0.11.0",
48
+ "@effected/env": "^0.1.0",
49
+ "@effected/glob": "^0.10.0",
50
+ "@effected/walker": "^0.15.0",
51
+ "@effected/workspaces": "^0.31.0",
52
+ "@vitest-agent/cli": "4.0.0",
53
+ "@vitest-agent/engine": "0.4.0",
54
+ "@vitest-agent/mcp": "5.1.6",
55
+ "@vitest-agent/reporter": "4.0.0",
56
+ "@vitest-agent/sdk": "5.2.0",
57
+ "effect": "^4.0.0",
55
58
  "magic-string": "^1.4.2"
56
59
  },
57
60
  "peerDependencies": {
package/plugin.js CHANGED
@@ -20,9 +20,11 @@ import { execSync } from "node:child_process";
20
20
  import { mkdtempSync, rmSync } from "node:fs";
21
21
  import { tmpdir } from "node:os";
22
22
  import { join } from "node:path";
23
- import { EnvironmentDetector, EnvironmentDetectorLive, resolveLogLevel } from "@vitest-agent/engine";
23
+ import { CliLog } from "@effected/cli";
24
+ import { Audience, EnvOverride } from "@effected/env";
25
+ import { EnvironmentDetector, EnvironmentDetectorLive, ExecutorResolver, ExecutorResolverLive, LoggerLive, resolveLogLevel } from "@vitest-agent/engine";
24
26
  import { AgentConsoleMode, CiConsoleMode, CoverageLevel, HumanConsoleMode, SRC_DIR, TEST_DIR, formatFatalError, isTestFileName } from "@vitest-agent/sdk";
25
- import { Effect, Schema } from "effect";
27
+ import { Effect, Layer, Option } from "effect";
26
28
 
27
29
  //#region src/plugin.ts
28
30
  /**
@@ -41,29 +43,53 @@ import { Effect, Schema } from "effect";
41
43
  * the dedicated `ci-annotations` reporter is opt-in until the GHA
42
44
  * annotations writer ships).
43
45
  *
44
- * An invalid `VITEST_AGENT_CONSOLE` value is ignored with a diagnostic
45
- * line sent to `report` (default: `process.stderr`). `configureVitest`
46
- * passes the per-Vitest-instance dedupe sink so the line prints once per
47
- * run rather than once per project (issue #459).
46
+ * `VITEST_AGENT_CONSOLE` is read through `@effected/env`'s `EnvOverride`
47
+ * (case-insensitive, per-audience accepted literals), with the executor as
48
+ * the `Audience`. A value the executor does not accept is ignored with one
49
+ * `[vitest-agent:plugin]` diagnostic line, worded here from
50
+ * `EnvOverride.readResult`'s structured rejection and sent to `report`
51
+ * (default: `process.stderr`), never stdout. `configureVitest` passes the
52
+ * per-Vitest-instance dedupe sink so the line prints once per run rather than
53
+ * once per project (issue #459).
48
54
  *
49
55
  * @internal
50
56
  */
51
57
  function resolveConsoleMode(options, executor, _env, report = (line) => {
52
58
  process.stderr.write(line);
53
59
  }) {
54
- const override = process.env.VITEST_AGENT_CONSOLE;
55
- if (override !== void 0 && override !== "") {
56
- if (executor === "human" && Schema.is(HumanConsoleMode)(override)) return override;
57
- if (executor === "agent" && Schema.is(AgentConsoleMode)(override)) return override;
58
- if (executor !== "human" && executor !== "agent" && Schema.is(CiConsoleMode)(override)) return override;
59
- report(`[vitest-agent:plugin] ignoring invalid VITEST_AGENT_CONSOLE="${override}" for ${executor} executor; accepted for ${executor}: ${(executor === "human" ? HumanConsoleMode.literals : executor === "agent" ? AgentConsoleMode.literals : CiConsoleMode.literals).join(" | ")}\n`);
60
+ const { accepted, rejected } = Effect.runSync(readConsoleOverride.pipe(Effect.provideService(Audience, {
61
+ kind: executor,
62
+ source: "detected"
63
+ })));
64
+ if (Option.isSome(rejected)) {
65
+ const { value, audience, accepts } = rejected.value;
66
+ report(`[vitest-agent:plugin] ignoring VITEST_AGENT_CONSOLE=${value}: not accepted for the ${audience} audience (accepts ${accepts.join("|")})\n`);
60
67
  }
68
+ if (Option.isSome(accepted)) return accepted.value;
61
69
  const console = options.console;
62
70
  if (executor === "human") return console?.human ?? "passthrough";
63
71
  if (executor === "agent") return console?.agent ?? "agent";
64
72
  return console?.ci ?? "passthrough";
65
73
  }
66
74
  /**
75
+ * The `VITEST_AGENT_CONSOLE` override for the current `Audience`, without
76
+ * logging: `accepted` is the literal that audience accepts, `rejected` a set
77
+ * value it does not (with the value, the audience and its literals), both
78
+ * `None` when unset or empty. The accepted type is the union of every slot's
79
+ * literals, not the current audience's; the audience check is a runtime one.
80
+ *
81
+ * @internal
82
+ */
83
+ const readConsoleOverride = EnvOverride.readResult({
84
+ envVar: "VITEST_AGENT_CONSOLE",
85
+ source: process.env,
86
+ accepts: {
87
+ human: HumanConsoleMode.literals,
88
+ agent: AgentConsoleMode.literals,
89
+ ci: CiConsoleMode.literals
90
+ }
91
+ });
92
+ /**
67
93
  * The plugin owns stdout when the resolved console mode produces visible
68
94
  * output that would conflict with Vitest's own reporters. Passthrough lets
69
95
  * Vitest emit progress normally; silent strips everything; the other modes
@@ -143,19 +169,6 @@ const reportedConfigDiagnosticsByVitest = /* @__PURE__ */ new WeakMap();
143
169
  const TEST_FILE_DIR_RE = new RegExp(`/(?:${SRC_DIR}|${TEST_DIR})/`);
144
170
  const isTestFile = (id) => isTestFileName(id) && TEST_FILE_DIR_RE.test(id);
145
171
  /**
146
- * Map a detected {@link Environment} to its {@link Executor}. Inline copy
147
- * of the `ExecutorResolverLive` mapping so the plugin can compute it
148
- * synchronously inside `configureVitest` without spinning up an Effect
149
- * runtime.
150
- *
151
- * @internal
152
- */
153
- function envToExecutor(env) {
154
- if (env === "agent-shell") return "agent";
155
- if (env === "terminal") return "human";
156
- return "ci";
157
- }
158
- /**
159
172
  * Wraps `resolvedConfig.logger.warn` in place so the benign Vite
160
173
  * "Failed to load source map" / ENOENT `.js.map` noise (GitHub issue #110)
161
174
  * never reaches stdout, while every other warning still passes through
@@ -192,7 +205,10 @@ function installViteSourceMapWarningFilter(resolvedConfig) {
192
205
  function AgentPlugin(options = {}, _layer) {
193
206
  const layer = _layer ?? EnvironmentDetectorLive(process.env);
194
207
  const logLevel = resolveLogLevel(process.env);
195
- const log = logLevel !== void 0 && logLevel !== "None" ? (...args) => process.stderr.write(`[vitest-agent:plugin] ${args.map(String).join(" ")}\n`) : (..._args) => {};
208
+ const diagnostics = logLevel === "Debug" || logLevel === "Trace" || logLevel === "All" ? LoggerLive(logLevel, void 0, process.env) : void 0;
209
+ const log = diagnostics ? (...args) => {
210
+ Effect.runSync(Effect.logDebug(args.map(String).join(" ")).pipe(CliLog.component("vitest-agent:plugin"), Effect.provide(diagnostics)));
211
+ } : (..._args) => {};
196
212
  const discoverStrategyResolved = options.discoverStrategy === false ? null : options.discoverStrategy ?? new DefaultDiscoverStrategy();
197
213
  const classifyForCache = (id) => {
198
214
  if (!discoverStrategyResolved) return void 0;
@@ -216,8 +232,13 @@ function AgentPlugin(options = {}, _layer) {
216
232
  ctx.defineCacheKeyGenerator(makeTagCacheKeyGenerator(classifyForCache));
217
233
  log("registered fsModuleCache tag cache-key generator");
218
234
  }
219
- const env = await Effect.runPromise(Effect.provide(Effect.flatMap(EnvironmentDetector, (d) => d.detect()), layer));
220
- const executor = envToExecutor(env);
235
+ const { env, executor } = await Effect.runPromise(Effect.gen(function* () {
236
+ const env = yield* (yield* EnvironmentDetector).detect();
237
+ return {
238
+ env,
239
+ executor: yield* (yield* ExecutorResolver).resolve(env)
240
+ };
241
+ }).pipe(Effect.provide(Layer.merge(layer, ExecutorResolverLive))));
221
242
  const reportOption = options.report;
222
243
  const reportScope = reportOption === false ? void 0 : executor === "human" && reportOption === void 0 ? void 0 : reportOption?.scope ?? "vitest-agent";
223
244
  if (reportScope !== void 0) assertFlatScope(reportScope);
@@ -451,4 +472,4 @@ function makeDiscoverBuilder(options) {
451
472
  })(AgentPlugin || (AgentPlugin = {}));
452
473
 
453
474
  //#endregion
454
- export { AgentPlugin, CURRENT_PLUGIN_VERSION, makeDiscoverBuilder, resolveConsoleMode };
475
+ export { AgentPlugin, CURRENT_PLUGIN_VERSION, makeDiscoverBuilder, readConsoleOverride, resolveConsoleMode };
package/reporter.js CHANGED
@@ -15,7 +15,7 @@ import { DataReader, DataStore, DetailResolver, EnvironmentDetector, ExecutorRes
15
15
  import { buildAgentReport, coerceErrorField, computeTrend, formatFatalError, isTimeoutError, probeHostMetadataFromEnv } from "@vitest-agent/sdk";
16
16
  import { Effect, Option, PubSub } from "effect";
17
17
  import { randomUUID } from "node:crypto";
18
- import { NodeServices } from "@effect/platform-node";
18
+ import * as NodeServices from "@effect/platform-node/NodeServices";
19
19
  import { DefaultVitestAgentReporter } from "@vitest-agent/reporter";
20
20
 
21
21
  //#region src/reporter.ts
@@ -339,6 +339,13 @@ var AgentReporter = class {
339
339
  */
340
340
  reporters;
341
341
  /**
342
+ * The one close of the run-event channel and the reporters, started at
343
+ * Vitest's close (see `closeReporters`). Undefined until then.
344
+ *
345
+ * @internal
346
+ */
347
+ closing;
348
+ /**
342
349
  * Whether the user supplied a custom `reporter` factory. A custom
343
350
  * reporter may subscribe to the run-event channel in any console mode,
344
351
  * so the streaming hooks publish events whenever this is true.
@@ -492,6 +499,36 @@ var AgentReporter = class {
492
499
  await this.ensureDbPath();
493
500
  } catch {}
494
501
  await this.initReporters();
502
+ const onClose = vitest?.onClose;
503
+ if (typeof onClose === "function") onClose.call(vitest, () => this.closeReporters());
504
+ }
505
+ /**
506
+ * Close the reporters resolved at `onInit`, then end the run-event
507
+ * channel, once, at Vitest's close. Reporters first: a live view folds
508
+ * every event still queued in its subscription and commits its last
509
+ * frame in `close`, and `PubSub.shutdown` would drop whatever a
510
+ * subscriber has not taken yet. The shutdown after is harmless to a
511
+ * closed view and ends any other subscriber. A failing close is logged,
512
+ * never thrown — Vitest is shutting down.
513
+ *
514
+ * @internal
515
+ */
516
+ closeReporters() {
517
+ this.closing ??= (async () => {
518
+ await Promise.all((this.reporters ?? []).map(async (reporter) => {
519
+ try {
520
+ await reporter.close?.();
521
+ } catch (err) {
522
+ process.stderr.write(`vitest-agent: reporter close failed: ${formatFatalError(err)}\n`);
523
+ }
524
+ }));
525
+ try {
526
+ Effect.runSync(PubSub.shutdown(this.runEvents));
527
+ } catch (err) {
528
+ process.stderr.write(`vitest-agent: run-event channel shutdown threw: ${formatFatalError(err)}\n`);
529
+ }
530
+ })();
531
+ return this.closing;
495
532
  }
496
533
  /**
497
534
  * Resolve the reporter factory at run start.
@@ -1191,7 +1228,7 @@ var AgentReporter = class {
1191
1228
  return;
1192
1229
  }
1193
1230
  if (dbPath !== void 0 && persistDisabled === void 0) try {
1194
- await ensureMigrated(dbPath, logLevel, logFile);
1231
+ await ensureMigrated(dbPath, logLevel, logFile, process.env);
1195
1232
  } catch (err) {
1196
1233
  persistDisabled = formatFatalError(err);
1197
1234
  }
@@ -2,7 +2,9 @@ import { nodeWalkerFs } from "./walker-fs.js";
2
2
  import { findTestFiles } from "./find-test-files.js";
3
3
  import { Tag } from "./tag.js";
4
4
  import { join, sep } from "node:path";
5
+ import { RuntimeEnv } from "@effected/env";
5
6
  import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS } from "@vitest-agent/sdk";
7
+ import { Option } from "effect";
6
8
  import { configDefaults } from "vitest/config";
7
9
 
8
10
  //#region src/utils/discover-strategy.ts
@@ -80,12 +82,20 @@ var ConcreteDiscoverStrategy = class ConcreteDiscoverStrategy extends DiscoverSt
80
82
  return new ConcreteDiscoverStrategy(newTags, newClassifyLayers, newBuildProjectLayers);
81
83
  }
82
84
  };
83
- const DEFAULT_TAGS = [
85
+ /**
86
+ * Whether the process runs in CI, by `@effected/env`'s `RuntimeEnv` rule: a
87
+ * truthy `GITHUB_ACTIONS`, or a `CI` / `CONTINUOUS_INTEGRATION` that is set,
88
+ * non-empty, and not `false` / `0` (is-in-ci semantics). Read when a strategy is
89
+ * constructed rather than at module load, so the environment in force when
90
+ * `AgentPlugin.discover()` runs is the one that decides.
91
+ */
92
+ const runsInCi = () => Option.isSome(RuntimeEnv.fromRecord(process.env).ci);
93
+ const defaultTags = (inCi) => [
84
94
  Tag.make("unit"),
85
95
  Tag.make("int", { timeout: 6e4 }),
86
96
  Tag.make("e2e", {
87
97
  timeout: 12e4,
88
- retry: process.env.CI ? 2 : 0
98
+ retry: inCi ? 2 : 0
89
99
  })
90
100
  ];
91
101
  const E2E_RE = /\.e2e\.(test|spec)\.(ts|tsx|js|jsx)$/;
@@ -98,7 +108,7 @@ const INT_RE = /\.int\.(test|spec)\.(ts|tsx|js|jsx)$/;
98
108
  * @public
99
109
  */
100
110
  var DefaultDiscoverStrategy = class extends DiscoverStrategy {
101
- tags = DEFAULT_TAGS;
111
+ tags = defaultTags(runsInCi());
102
112
  get tagDefinitions() {
103
113
  return this.tags.map((t) => t.definition);
104
114
  }
package/version.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @public
10
10
  */
11
- const CURRENT_PLUGIN_VERSION = "5.1.2";
11
+ const CURRENT_PLUGIN_VERSION = "5.2.0";
12
12
 
13
13
  //#endregion
14
14
  export { CURRENT_PLUGIN_VERSION };