@vitest-agent/plugin 2.1.0 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/plugin",
3
- "version": "2.1.0",
3
+ "version": "2.2.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": [
@@ -41,11 +41,11 @@
41
41
  "dependencies": {
42
42
  "@effect/platform-node": "4.0.0-beta.107",
43
43
  "@effect/sql-sqlite-node": "4.0.0-beta.107",
44
- "@effected/workspaces": "^0.11.1",
45
- "@vitest-agent/cli": "2.0.17",
46
- "@vitest-agent/mcp": "2.1.2",
47
- "@vitest-agent/reporter": "2.0.17",
48
- "@vitest-agent/sdk": "2.1.0",
44
+ "@effected/workspaces": "^0.11.2",
45
+ "@vitest-agent/cli": "2.1.1",
46
+ "@vitest-agent/mcp": "2.1.4",
47
+ "@vitest-agent/reporter": "2.0.19",
48
+ "@vitest-agent/sdk": "2.2.1",
49
49
  "effect": "4.0.0-beta.107",
50
50
  "magic-string": "^1.1.0"
51
51
  },
package/plugin.js CHANGED
@@ -9,7 +9,7 @@ import { injectTags } from "./utils/inject-tags.js";
9
9
  import { isBenignViteSourceMapWarning } from "./utils/is-benign-vite-source-map-warning.js";
10
10
  import { stripConsoleReporters } from "./utils/strip-console-reporters.js";
11
11
  import { execSync } from "node:child_process";
12
- import { AgentConsoleMode, CiConsoleMode, CoverageLevel, EnvironmentDetector, EnvironmentDetectorLive, HumanConsoleMode, formatFatalError, resolveLogLevel } from "@vitest-agent/sdk";
12
+ import { AgentConsoleMode, CiConsoleMode, CoverageLevel, EnvironmentDetector, EnvironmentDetectorLive, HumanConsoleMode, SRC_DIR, TEST_DIR, formatFatalError, isTestFileName, resolveLogLevel } from "@vitest-agent/sdk";
13
13
  import { Effect, Schema } from "effect";
14
14
 
15
15
  //#region src/plugin.ts
@@ -98,10 +98,9 @@ const aggregatedReporterByVitest = /* @__PURE__ */ new WeakSet();
98
98
  *
99
99
  * @public
100
100
  */
101
- const CURRENT_PLUGIN_VERSION = "2.1.0";
102
- const TEST_FILE_SUFFIX_RE = /\.(?:test|spec)\.(?:ts|tsx|js|jsx)$/;
103
- const TEST_FILE_DIR_RE = /\/(?:src|__test__)\//;
104
- const isTestFile = (id) => TEST_FILE_SUFFIX_RE.test(id) && TEST_FILE_DIR_RE.test(id);
101
+ const CURRENT_PLUGIN_VERSION = "2.2.1";
102
+ const TEST_FILE_DIR_RE = new RegExp(`/(?:${SRC_DIR}|${TEST_DIR})/`);
103
+ const isTestFile = (id) => isTestFileName(id) && TEST_FILE_DIR_RE.test(id);
105
104
  /**
106
105
  * Map a detected {@link Environment} to its {@link Executor}. Inline copy
107
106
  * of the `ExecutorResolverLive` mapping so the plugin can compute it
@@ -1,5 +1,6 @@
1
1
  import { toPosixPath } from "./to-posix-path.js";
2
2
  import { DefaultDiscoverStrategy } from "./discover-strategy.js";
3
+ import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR } from "@vitest-agent/sdk";
3
4
  import { isAbsolute, join, normalize, relative } from "node:path";
4
5
  import { readdir, stat } from "node:fs/promises";
5
6
  import { findWorkspaceRootSync, getWorkspacePackagesSync } from "@effected/workspaces";
@@ -11,49 +12,6 @@ function recordDiscoveryScanTimestamp() {
11
12
  globalThis[DISCOVERY_LAST_SCAN_SYMBOL] = (/* @__PURE__ */ new Date()).toISOString();
12
13
  }
13
14
  const _cache = /* @__PURE__ */ new Map();
14
- const SIGNATURE_SKIP_DIRS = /* @__PURE__ */ new Set([
15
- "node_modules",
16
- ".git",
17
- "dist"
18
- ]);
19
- /**
20
- * Recursively finds every directory named `__test__` under `root` (issue
21
- * #184: nested `__test__` dirs like `lib/scripts/__test__/` must invalidate
22
- * the cache too, not just a package-root `__test__/`) and records every
23
- * nested `package.json` (path + mtime) as a traversal-boundary marker. Skips
24
- * `node_modules`, `.git`, and `dist` — see `SIGNATURE_SKIP_DIRS`. Stat-only:
25
- * entries and mtimes are read, never file content. The root's own
26
- * `package.json` is excluded — it is not a boundary for its own walk, and
27
- * version bumps would otherwise churn the signature.
28
- */
29
- async function findNestedTestDirs(root) {
30
- const testDirs = [];
31
- const boundaries = [];
32
- await walkForTestDirs(root, root, testDirs, boundaries);
33
- return {
34
- testDirs: testDirs.sort(),
35
- boundaries: boundaries.sort()
36
- };
37
- }
38
- async function walkForTestDirs(root, dir, testDirs, boundaries) {
39
- let entries;
40
- try {
41
- entries = await readdir(dir, { withFileTypes: true });
42
- } catch {
43
- return;
44
- }
45
- for (const ent of entries) {
46
- const fullPath = join(dir, ent.name);
47
- if (ent.isDirectory()) {
48
- if (SIGNATURE_SKIP_DIRS.has(ent.name)) continue;
49
- if (ent.name === "__test__") testDirs.push(fullPath);
50
- await walkForTestDirs(root, fullPath, testDirs, boundaries);
51
- } else if (ent.isFile() && ent.name === "package.json" && dir !== root) try {
52
- const mtimeMs = (await stat(fullPath)).mtimeMs;
53
- boundaries.push(`${toPosixPath(relative(root, fullPath))}:${mtimeMs}`);
54
- } catch {}
55
- }
56
- }
57
15
  /**
58
16
  * Computes a cheap signature (relative path + mtimeMs pairs, sorted) for every
59
17
  * file nested under `dirPath`. Used to detect added/removed/moved/renamed test
@@ -62,11 +20,10 @@ async function walkForTestDirs(root, dir, testDirs, boundaries) {
62
20
  * a stable, comparable signature contribution.
63
21
  *
64
22
  * Manual per-directory walk (not a single `readdir({ recursive: true })`
65
- * call) pruning `SIGNATURE_SKIP_DIRS` *before* recursing same reasoning as
66
- * `findNestedTestDirs`: a found `__test__` dir can itself contain a fixture
67
- * `node_modules` (e.g. a subprocess-e2e fixture that installs deps), and
68
- * Node's recursive `readdir` follows symlinked directories, so an unguarded
69
- * call would walk into it.
23
+ * call) pruning `NON_DISCOVERABLE_DIRS` *before* recursing: a `src/` or
24
+ * `__test__/` dir can itself contain a fixture `node_modules` (e.g. a
25
+ * subprocess-e2e fixture that installs deps), and Node's recursive `readdir`
26
+ * follows symlinked directories, so an unguarded call would walk into it.
70
27
  */
71
28
  async function computeDirSignature(dirPath) {
72
29
  const parts = [];
@@ -84,7 +41,7 @@ async function walkDirSignature(root, dir, parts) {
84
41
  for (const ent of entries) {
85
42
  const fullPath = join(dir, ent.name);
86
43
  if (ent.isDirectory()) {
87
- if (SIGNATURE_SKIP_DIRS.has(ent.name)) continue;
44
+ if (NON_DISCOVERABLE_DIRS.has(ent.name)) continue;
88
45
  await walkDirSignature(root, fullPath, parts);
89
46
  } else if (ent.isFile()) {
90
47
  let mtimeMs;
@@ -99,25 +56,22 @@ async function walkDirSignature(root, dir, parts) {
99
56
  }
100
57
  }
101
58
  /**
102
- * Computes a cheap whole-workspace directory signature by combining each
103
- * package's `src/` signature with every nested `__test__/` dir's signature
104
- * (issue #100, extended by issue #184 to see `__test__` dirs at any depth,
105
- * not just a package-root `__test__/`). Only entries + mtimes are read — no
106
- * file content — so this stays fast even for large monorepos. A changed
59
+ * Computes a cheap whole-workspace directory signature from each package's
60
+ * `src/` and `__test__/` directories (issue #100). Only entries + mtimes are
61
+ * read no file content so this stays fast for large monorepos. A changed
107
62
  * signature means a test file was added, removed, moved, or renamed since
108
63
  * the cached result was computed.
64
+ *
65
+ * Only the two anchored directories are fingerprinted. Discovery cannot see a
66
+ * test file anywhere else, so nothing else can change the emitted project list
67
+ * (issue #227).
109
68
  */
110
69
  async function computeWorkspaceSignature(packages) {
111
70
  const parts = [];
112
71
  for (const pkg of packages) {
113
- const srcSig = await computeDirSignature(join(pkg.path, "src"));
114
- const { testDirs, boundaries } = await findNestedTestDirs(pkg.path);
115
- const testDirParts = [];
116
- for (const dir of testDirs) {
117
- const sig = await computeDirSignature(dir);
118
- testDirParts.push(`${toPosixPath(relative(pkg.path, dir))}=${sig}`);
119
- }
120
- parts.push(`${pkg.path}::src=${srcSig}::__test__=${testDirParts.join(";")}::boundaries=${boundaries.join(";")}`);
72
+ const srcSig = await computeDirSignature(join(pkg.path, SRC_DIR));
73
+ const testSig = await computeDirSignature(join(pkg.path, TEST_DIR));
74
+ parts.push(`${pkg.path}::src=${srcSig}::__test__=${testSig}`);
121
75
  }
122
76
  return parts.join("\n");
123
77
  }
@@ -1,5 +1,6 @@
1
1
  import { findTestFiles } from "./find-test-files.js";
2
2
  import { Tag } from "./tag.js";
3
+ import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS } from "@vitest-agent/sdk";
3
4
  import { join, sep } from "node:path";
4
5
  import { stat } from "node:fs/promises";
5
6
  import { configDefaults } from "vitest/config";
@@ -11,11 +12,6 @@ const SETUP_EXTS = [
11
12
  "js",
12
13
  "jsx"
13
14
  ];
14
- const TEST_DIR_HELPER_DIRS = [
15
- "utils",
16
- "fixtures",
17
- "snapshots"
18
- ];
19
15
  async function isFile(p) {
20
16
  try {
21
17
  return (await stat(p)).isFile();
@@ -116,19 +112,19 @@ var DefaultDiscoverStrategy = class extends DiscoverStrategy {
116
112
  return ["unit"];
117
113
  }
118
114
  async buildProject(input) {
119
- const srcPrefix = join(input.path, "src");
120
- const allFiles = await findTestFiles(input.path, ["src/**/*.{test,spec}.{ts,tsx,js,jsx}", "**/__test__/**/*.{test,spec}.{ts,tsx,js,jsx}"]);
115
+ const srcPrefix = join(input.path, SRC_DIR);
116
+ const allFiles = await findTestFiles(input.path, [`${SRC_DIR}/**/${TEST_FILE_GLOB_SUFFIX}`, `${TEST_DIR}/**/${TEST_FILE_GLOB_SUFFIX}`]);
121
117
  if (allFiles.length === 0) return null;
122
118
  const hasSrcTests = allFiles.some((f) => f.startsWith(`${srcPrefix}${sep}`) || f === srcPrefix);
123
119
  const hasTestDirTests = allFiles.some((f) => !(f.startsWith(`${srcPrefix}${sep}`) || f === srcPrefix));
124
120
  const include = [];
125
- if (hasSrcTests) include.push(join(input.path, "src/**/*.{test,spec}.{ts,tsx,js,jsx}"));
126
- if (hasTestDirTests) include.push(join(input.path, "**/__test__/**/*.{test,spec}.{ts,tsx,js,jsx}"));
127
- const exclude = hasTestDirTests ? [
121
+ if (hasSrcTests) include.push(join(input.path, SRC_DIR, "**", TEST_FILE_GLOB_SUFFIX));
122
+ if (hasTestDirTests) include.push(join(input.path, TEST_DIR, "**", TEST_FILE_GLOB_SUFFIX));
123
+ const exclude = [
128
124
  ...configDefaults.exclude,
129
- join(input.path, "**/dist/**"),
130
- ...TEST_DIR_HELPER_DIRS.map((d) => join(input.path, `**/__test__/${d}/**`))
131
- ] : void 0;
125
+ ...[SRC_DIR, TEST_DIR].flatMap((root) => [...NON_DISCOVERABLE_DIRS].map((d) => join(input.path, root, "**", d, "**"))),
126
+ ...hasTestDirTests ? TEST_HELPER_DIRS.map((d) => join(input.path, TEST_DIR, "**", d, "**")) : []
127
+ ];
132
128
  const setupFile = await detectSetupFile(input.path);
133
129
  return {
134
130
  extends: true,
@@ -136,7 +132,7 @@ var DefaultDiscoverStrategy = class extends DiscoverStrategy {
136
132
  name: input.name,
137
133
  environment: "node",
138
134
  include,
139
- ...exclude ? { exclude } : {},
135
+ exclude,
140
136
  ...setupFile ? { setupFiles: [join(input.path, setupFile)] } : {}
141
137
  }
142
138
  };
@@ -1,13 +1,9 @@
1
1
  import { toPosixPath } from "./to-posix-path.js";
2
+ import { NON_DISCOVERABLE_DIRS } from "@vitest-agent/sdk";
2
3
  import { join, relative } from "node:path";
3
4
  import { readdir } from "node:fs/promises";
4
5
 
5
6
  //#region src/utils/find-test-files.ts
6
- const SKIP_DIRS = /* @__PURE__ */ new Set([
7
- "node_modules",
8
- ".git",
9
- "dist"
10
- ]);
11
7
  function globToRegex(pattern) {
12
8
  const alts = expandBraces(pattern).map(toRegexFragment);
13
9
  return new RegExp(`^(?:${alts.join("|")})$`);
@@ -90,7 +86,7 @@ async function walkDir(root, dir, matchers, results) {
90
86
  }
91
87
  if (dir !== root && entries.some((ent) => ent.isFile() && ent.name === "package.json")) return;
92
88
  for (const ent of entries) {
93
- if (SKIP_DIRS.has(ent.name)) continue;
89
+ if (NON_DISCOVERABLE_DIRS.has(ent.name)) continue;
94
90
  const fullPath = join(dir, ent.name);
95
91
  if (ent.isDirectory()) await walkDir(root, fullPath, matchers, results);
96
92
  else if (ent.isFile()) {