@vitest-agent/plugin 2.1.0 → 2.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/package.json +5 -5
- package/plugin.js +4 -5
- package/utils/discover-projects.js +16 -62
- package/utils/discover-strategy.js +10 -14
- package/utils/find-test-files.js +2 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.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": [
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"@effect/platform-node": "4.0.0-beta.107",
|
|
43
43
|
"@effect/sql-sqlite-node": "4.0.0-beta.107",
|
|
44
44
|
"@effected/workspaces": "^0.11.1",
|
|
45
|
-
"@vitest-agent/cli": "2.0
|
|
46
|
-
"@vitest-agent/mcp": "2.1.
|
|
47
|
-
"@vitest-agent/reporter": "2.0.
|
|
48
|
-
"@vitest-agent/sdk": "2.
|
|
45
|
+
"@vitest-agent/cli": "2.1.0",
|
|
46
|
+
"@vitest-agent/mcp": "2.1.3",
|
|
47
|
+
"@vitest-agent/reporter": "2.0.18",
|
|
48
|
+
"@vitest-agent/sdk": "2.2.0",
|
|
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.
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const isTestFile = (id) => TEST_FILE_SUFFIX_RE.test(id) && TEST_FILE_DIR_RE.test(id);
|
|
101
|
+
const CURRENT_PLUGIN_VERSION = "2.2.0";
|
|
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 `
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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 (
|
|
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
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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,
|
|
114
|
-
const
|
|
115
|
-
|
|
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,
|
|
120
|
-
const allFiles = await findTestFiles(input.path, [
|
|
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, "
|
|
126
|
-
if (hasTestDirTests) include.push(join(input.path, "
|
|
127
|
-
const exclude =
|
|
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, "
|
|
130
|
-
...
|
|
131
|
-
]
|
|
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
|
-
|
|
135
|
+
exclude,
|
|
140
136
|
...setupFile ? { setupFiles: [join(input.path, setupFile)] } : {}
|
|
141
137
|
}
|
|
142
138
|
};
|
package/utils/find-test-files.js
CHANGED
|
@@ -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 (
|
|
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()) {
|