@vitest-agent/plugin 1.1.5 → 1.1.7
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/README.md +3 -3
- package/index.d.ts +4 -3
- package/package.json +7 -7
- package/plugin.js +1 -1
- package/utils/inject-tags.js +21 -77
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
|
|
7
|
-
Vitest plugin that turns your test suite into a live data source for LLM coding agents. Handles persistence, failure classification, coverage policy enforcement, project discovery and a configurable reporter chain — the CLI and MCP server
|
|
7
|
+
Vitest plugin that turns your test suite into a live data source for LLM coding agents. Handles persistence, failure classification, coverage policy enforcement, project discovery and a configurable reporter chain — the CLI and MCP server ship as dependencies and are wired automatically.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **`AgentPlugin`** — drop into `vitest.config.ts`; auto-detects human, agent and CI executors and adapts console output accordingly
|
|
12
|
-
- **Project discovery** — `AgentPlugin.discover()` scans workspace packages, returns `{ projects, tags }` ready for `test.projects` and `test.tags`
|
|
12
|
+
- **Project discovery** — `AgentPlugin.discover()` scans workspace packages, returns `{ projects, tags }` ready for `test.projects` and `test.tags`; classification tags apply at collection time, so every test declaration form inherits them, including wrapper testers like `@effect/vitest`'s `it.effect`
|
|
13
13
|
- **Coverage presets** — `COVERAGE_LEVELS` and `COVERAGE_LEVELS_PER_FILE` return dual-output `{ thresholds, coverageTargets }` objects; `COVERAGE_AUTOUPDATE` tolerance functions plug into Vitest's native `autoUpdate`
|
|
14
14
|
- **Failure classification** — persists per-test errors, computes failure signatures, classifies tests as stable, new-failure, persistent, flaky or recovered across runs
|
|
15
15
|
- **Custom reporters** — pass any `VitestAgentReporterFactory` as the `reporter` option; the default wires `DefaultVitestAgentReporter` from `@vitest-agent/reporter`
|
|
@@ -23,7 +23,7 @@ npm install --save-dev @vitest-agent/plugin
|
|
|
23
23
|
pnpm add -D @vitest-agent/plugin
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`@vitest-agent/cli` and `@vitest-agent/mcp` ship as regular dependencies of the plugin, so they install with it on every package manager.
|
|
27
27
|
|
|
28
28
|
## Quick start
|
|
29
29
|
|
package/index.d.ts
CHANGED
|
@@ -157,7 +157,7 @@ declare class DefaultDiscoverStrategy extends DiscoverStrategy {
|
|
|
157
157
|
* @public
|
|
158
158
|
*/
|
|
159
159
|
interface InjectTagsResult {
|
|
160
|
-
/** The transformed source code string with
|
|
160
|
+
/** The transformed source code string with the prepended tag prelude. */
|
|
161
161
|
code: string;
|
|
162
162
|
/** Source map correlating transformed positions back to the original source. */
|
|
163
163
|
map: SourceMap;
|
|
@@ -193,8 +193,9 @@ interface AgentPluginConstructorOptions extends AgentPluginOptions {
|
|
|
193
193
|
*/
|
|
194
194
|
onRunEvent?: (event: RunEvent) => void;
|
|
195
195
|
/**
|
|
196
|
-
* Controls the Vite transform hook that
|
|
197
|
-
*
|
|
196
|
+
* Controls the Vite transform hook that prepends a tag prelude to each
|
|
197
|
+
* classified test file, applying filename-derived tags at collection
|
|
198
|
+
* time so every declaration form inherits them. Pass a DiscoverStrategy
|
|
198
199
|
* instance to customize classification, or false to disable the
|
|
199
200
|
* transform entirely.
|
|
200
201
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
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": [
|
|
@@ -39,22 +39,22 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@effect/cluster": "^0.59.0",
|
|
42
|
+
"@effect/experimental": "^0.60.0",
|
|
42
43
|
"@effect/platform": "^0.96.2",
|
|
43
44
|
"@effect/platform-node": "^0.107.0",
|
|
44
45
|
"@effect/rpc": "^0.75.1",
|
|
45
46
|
"@effect/sql": "^0.51.1",
|
|
46
47
|
"@effect/sql-sqlite-node": "^0.52.0",
|
|
47
|
-
"@
|
|
48
|
-
"@vitest-agent/
|
|
49
|
-
"
|
|
50
|
-
"
|
|
48
|
+
"@effect/workflow": "^0.18.2",
|
|
49
|
+
"@vitest-agent/cli": "1.0.6",
|
|
50
|
+
"@vitest-agent/mcp": "1.3.4",
|
|
51
|
+
"@vitest-agent/reporter": "1.0.6",
|
|
52
|
+
"@vitest-agent/sdk": "1.3.2",
|
|
51
53
|
"effect": "^3.21.4",
|
|
52
54
|
"magic-string": "^0.30.21",
|
|
53
55
|
"workspaces-effect": "^2.0.2"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
|
-
"@vitest-agent/cli": "1.0.5",
|
|
57
|
-
"@vitest-agent/mcp": "1.3.3",
|
|
58
58
|
"@vitest/coverage-istanbul": "^4.1.0",
|
|
59
59
|
"@vitest/coverage-v8": "^4.1.0",
|
|
60
60
|
"vitest": "^4.1.0"
|
package/plugin.js
CHANGED
|
@@ -98,7 +98,7 @@ const aggregatedReporterByVitest = /* @__PURE__ */ new WeakSet();
|
|
|
98
98
|
*
|
|
99
99
|
* @public
|
|
100
100
|
*/
|
|
101
|
-
const CURRENT_PLUGIN_VERSION = "1.1.
|
|
101
|
+
const CURRENT_PLUGIN_VERSION = "1.1.7";
|
|
102
102
|
const TEST_FILE_SUFFIX_RE = /\.(?:test|spec)\.(?:ts|tsx|js|jsx)$/;
|
|
103
103
|
const TEST_FILE_DIR_RE = /\/(?:src|__test__)\//;
|
|
104
104
|
const isTestFile = (id) => TEST_FILE_SUFFIX_RE.test(id) && TEST_FILE_DIR_RE.test(id);
|
package/utils/inject-tags.js
CHANGED
|
@@ -1,92 +1,36 @@
|
|
|
1
|
-
import * as acorn from "acorn";
|
|
2
|
-
import { tsPlugin } from "acorn-typescript";
|
|
3
1
|
import MagicString from "magic-string";
|
|
4
2
|
|
|
5
3
|
//#region src/utils/inject-tags.ts
|
|
6
|
-
const Parser = acorn.Parser.extend(tsPlugin());
|
|
7
|
-
const TEST_NAMES = /* @__PURE__ */ new Set(["test", "it"]);
|
|
8
|
-
function rootIdentifier(n) {
|
|
9
|
-
if (n.type === "Identifier") return n;
|
|
10
|
-
if (n.type === "MemberExpression") return rootIdentifier(n.object);
|
|
11
|
-
if (n.type === "CallExpression") return rootIdentifier(n.callee);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
function isTestCallee(callee) {
|
|
15
|
-
const root = rootIdentifier(callee);
|
|
16
|
-
return !!root && TEST_NAMES.has(root.name);
|
|
17
|
-
}
|
|
18
|
-
function hasTagsField(objectExpression) {
|
|
19
|
-
const props = objectExpression.properties ?? [];
|
|
20
|
-
for (const p of props) {
|
|
21
|
-
if (p.type !== "Property") continue;
|
|
22
|
-
const key = p.key;
|
|
23
|
-
if (key.type === "Identifier" && key.name === "tags") return true;
|
|
24
|
-
if (key.type === "Literal" && key.value === "tags") return true;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
4
|
function tagsLiteral(tags) {
|
|
29
5
|
return `[${tags.map((t) => JSON.stringify(t)).join(", ")}]`;
|
|
30
6
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Builds the guarded prelude that applies the classified tags to the file
|
|
9
|
+
* task at collection time. `TestRunner.getCurrentSuite()` is a public
|
|
10
|
+
* static on the main vitest entry (>= 4.1, the plugin's peer floor); the
|
|
11
|
+
* `.suite ?? .file` fallback mirrors vitest's own parent-task resolution.
|
|
12
|
+
* The runner unions parent tags into every suite and test it registers,
|
|
13
|
+
* so tags set here are inherited by every declaration form in the file —
|
|
14
|
+
* native it/test, wrapper testers like @effect/vitest's it.effect, aliases
|
|
15
|
+
* from test.extend, and dynamically registered tests (issue #133). The
|
|
16
|
+
* namespace import plus optional chaining on `TestRunner` means environments
|
|
17
|
+
* whose "vitest" entry lacks the `TestRunner` export (e.g. vitest's browser-
|
|
18
|
+
* mode entry) degrade to untagged tests instead of a module-instantiation
|
|
19
|
+
* failure. If a future vitest changes the collector shape, the try/catch
|
|
20
|
+
* degrades to "tests carry no tags" — never a crash.
|
|
21
|
+
*/
|
|
22
|
+
function preludeFor(tags) {
|
|
23
|
+
return `import * as __vitestAgentVitest from "vitest";
|
|
24
|
+
try { const __vitestAgentCollector = __vitestAgentVitest.TestRunner?.getCurrentSuite?.(); const __vitestAgentTask = __vitestAgentCollector?.suite ?? __vitestAgentCollector?.file; if (__vitestAgentTask) { __vitestAgentTask.tags = [...new Set([...(__vitestAgentTask.tags ?? []), ...${tagsLiteral(tags)}])]; } } catch {}
|
|
25
|
+
`;
|
|
40
26
|
}
|
|
41
27
|
function injectTags(source, tags) {
|
|
42
28
|
if (tags.length === 0) return null;
|
|
43
|
-
let ast;
|
|
44
|
-
try {
|
|
45
|
-
ast = Parser.parse(source, {
|
|
46
|
-
ecmaVersion: "latest",
|
|
47
|
-
sourceType: "module",
|
|
48
|
-
locations: true
|
|
49
|
-
});
|
|
50
|
-
} catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
29
|
const ms = new MagicString(source);
|
|
54
|
-
|
|
55
|
-
walk(ast, (node) => {
|
|
56
|
-
if (node.type !== "CallExpression") return;
|
|
57
|
-
const callee = node.callee;
|
|
58
|
-
if (!isTestCallee(callee)) return;
|
|
59
|
-
const args = node.arguments ?? [];
|
|
60
|
-
if (args.length < 2) return;
|
|
61
|
-
const last = args[args.length - 1];
|
|
62
|
-
if (!(last.type === "FunctionExpression" || last.type === "ArrowFunctionExpression")) return;
|
|
63
|
-
const optsCandidate = args.length >= 3 ? args[args.length - 2] : null;
|
|
64
|
-
if (optsCandidate) {
|
|
65
|
-
if (optsCandidate.type === "ObjectExpression") {
|
|
66
|
-
if (hasTagsField(optsCandidate)) return;
|
|
67
|
-
const lastProp = (optsCandidate.properties ?? []).at(-1);
|
|
68
|
-
if (lastProp === void 0) {
|
|
69
|
-
const insertPoint = optsCandidate.end - 1;
|
|
70
|
-
ms.appendLeft(insertPoint, `tags: ${tagsLiteral(tags)} `);
|
|
71
|
-
} else ms.appendLeft(lastProp.end, `, tags: ${tagsLiteral(tags)}`);
|
|
72
|
-
mutated = true;
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const optsStart = optsCandidate.start;
|
|
76
|
-
const optsEnd = optsCandidate.end;
|
|
77
|
-
const origExpr = source.slice(optsStart, optsEnd);
|
|
78
|
-
ms.overwrite(optsStart, optsEnd, `{ ...(${origExpr}), tags: ${tagsLiteral(tags)} }`);
|
|
79
|
-
mutated = true;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const nameArg = args[0];
|
|
83
|
-
ms.appendRight(nameArg.end, `, { tags: ${tagsLiteral(tags)} }`);
|
|
84
|
-
mutated = true;
|
|
85
|
-
});
|
|
86
|
-
if (!mutated) return null;
|
|
30
|
+
ms.prepend(preludeFor(tags));
|
|
87
31
|
return {
|
|
88
32
|
code: ms.toString(),
|
|
89
|
-
map: ms.generateMap(
|
|
33
|
+
map: ms.generateMap()
|
|
90
34
|
};
|
|
91
35
|
}
|
|
92
36
|
|