@vitest-agent/plugin 5.2.1 → 5.3.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 +6 -9
- package/layers/CoverageAnalyzerLive.js +108 -1
- package/layers/ReporterLive.js +4 -5
- package/package.json +7 -7
- package/reporter.js +2 -1
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import "@effected/env";
|
|
2
|
-
import { EnvironmentDetector, PlatformOptions, PlatformServices, SettingsInput, StackFrameInput } from "@vitest-agent/engine";
|
|
2
|
+
import { EnvironmentDetector, PlatformLiveError, PlatformOptions, PlatformServices, SettingsInput, StackFrameInput } from "@vitest-agent/engine";
|
|
3
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";
|
|
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";
|
|
7
7
|
import { WorkspacesSyncOptions } from "@effected/workspaces/node-sync";
|
|
8
8
|
import { SourceMap } from "magic-string";
|
|
9
|
-
import { MigrationError } from "effect/sql/Migrator";
|
|
10
|
-
import { SqlError } from "effect/sql/SqlError";
|
|
11
9
|
//#region src/utils/tag.d.ts
|
|
12
10
|
/**
|
|
13
11
|
* Options for a `Tag`, mirroring `TestTagDefinition` minus the `name` field.
|
|
@@ -1296,17 +1294,16 @@ export declare class CoverageAnalyzer extends CoverageAnalyzer_base {}
|
|
|
1296
1294
|
* `PlatformLive` (SQLite, migrations, Node platform services, logger and
|
|
1297
1295
|
* the shared service layers) plus the plugin-only `CoverageAnalyzer`.
|
|
1298
1296
|
*
|
|
1299
|
-
* The return type is spelled out so the emitted declaration names
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
* resolve.
|
|
1297
|
+
* The return type is spelled out so the emitted declaration names the
|
|
1298
|
+
* error through the engine's `PlatformLiveError` alias; left to inference
|
|
1299
|
+
* it is emitted via `@effected/store`, which this package does not depend
|
|
1300
|
+
* on and a root typecheck cannot resolve.
|
|
1304
1301
|
*
|
|
1305
1302
|
* @param options - forwarded to `PlatformLive`; the reporter passes
|
|
1306
1303
|
* `process.env` as `env`
|
|
1307
1304
|
* @public
|
|
1308
1305
|
*/
|
|
1309
|
-
export declare const ReporterLive: (options: PlatformOptions) => Layer.Layer<CoverageAnalyzer | PlatformServices,
|
|
1306
|
+
export declare const ReporterLive: (options: PlatformOptions) => Layer.Layer<CoverageAnalyzer | PlatformServices, PlatformLiveError>;
|
|
1310
1307
|
//#endregion
|
|
1311
1308
|
//#region src/layers/CoverageAnalyzerLive.d.ts
|
|
1312
1309
|
/**
|
|
@@ -17,6 +17,59 @@ function isBelowMetricThresholds(stats, thresholds) {
|
|
|
17
17
|
if (thresholds.statements !== void 0 && stats.statements < thresholds.statements) return true;
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
|
+
const METRICS = [
|
|
21
|
+
"statements",
|
|
22
|
+
"branches",
|
|
23
|
+
"functions",
|
|
24
|
+
"lines"
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Istanbul's own percentage: `covered / total` floored to two decimals, and
|
|
28
|
+
* 100 for an empty (`total === 0`) metric. Mirrors `istanbul-lib-coverage`'s
|
|
29
|
+
* `percent()` so an aggregate computed here equals what Vitest compares.
|
|
30
|
+
*/
|
|
31
|
+
function istanbulPercent(covered, total) {
|
|
32
|
+
return total > 0 ? Math.floor(1e4 * covered / total) / 100 : 100;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Aggregate a glob's matched files the way Vitest builds a glob coverage map:
|
|
36
|
+
* sum `covered` and `total` per metric across the files' summaries, then take
|
|
37
|
+
* the percentage of the sums (NOT a mean of per-file percentages). Returns
|
|
38
|
+
* `undefined` when any file's summary lacks counts, since no honest aggregate
|
|
39
|
+
* exists then.
|
|
40
|
+
*/
|
|
41
|
+
function aggregateSummaries(summaries) {
|
|
42
|
+
const sums = {
|
|
43
|
+
statements: {
|
|
44
|
+
covered: 0,
|
|
45
|
+
total: 0
|
|
46
|
+
},
|
|
47
|
+
branches: {
|
|
48
|
+
covered: 0,
|
|
49
|
+
total: 0
|
|
50
|
+
},
|
|
51
|
+
functions: {
|
|
52
|
+
covered: 0,
|
|
53
|
+
total: 0
|
|
54
|
+
},
|
|
55
|
+
lines: {
|
|
56
|
+
covered: 0,
|
|
57
|
+
total: 0
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
for (const s of summaries) for (const m of METRICS) {
|
|
61
|
+
const { covered, total } = s[m];
|
|
62
|
+
if (typeof covered !== "number" || typeof total !== "number") return void 0;
|
|
63
|
+
sums[m].covered += covered;
|
|
64
|
+
sums[m].total += total;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
statements: istanbulPercent(sums.statements.covered, sums.statements.total),
|
|
68
|
+
branches: istanbulPercent(sums.branches.covered, sums.branches.total),
|
|
69
|
+
functions: istanbulPercent(sums.functions.covered, sums.functions.total),
|
|
70
|
+
lines: istanbulPercent(sums.lines.covered, sums.lines.total)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
20
73
|
/**
|
|
21
74
|
* Runtime duck-type check for istanbul CoverageMap.
|
|
22
75
|
*/
|
|
@@ -86,6 +139,53 @@ function resolveEffectivePerFileThresholds(filePath, resolved) {
|
|
|
86
139
|
return setting;
|
|
87
140
|
}
|
|
88
141
|
/**
|
|
142
|
+
* Find the threshold globs whose aggregate coverage is below their own metric
|
|
143
|
+
* numbers (issue #391), matching Vitest 5: a glob's coverage map holds every
|
|
144
|
+
* file in the run that matches it (a file may belong to several globs), the
|
|
145
|
+
* aggregate is `sum(covered) / sum(total)` per metric, and it is enforced in
|
|
146
|
+
* addition to any object `perFile`. A glob with `perFile: true` is checked per
|
|
147
|
+
* file only, so it has no aggregate to fail.
|
|
148
|
+
*
|
|
149
|
+
* Choices (document before changing):
|
|
150
|
+
* - Bare-zero files COUNT toward the aggregate, regardless of
|
|
151
|
+
* `includeBareZero`. That option only decides whether such files are listed
|
|
152
|
+
* per file; Vitest's aggregate includes them, and dropping them would hide
|
|
153
|
+
* exactly the shortfall they cause.
|
|
154
|
+
* - Scoped runs never produce shortfalls (the caller skips this): a partial
|
|
155
|
+
* coverage map says nothing about a glob's full aggregate, mirroring how
|
|
156
|
+
* scoped runs never flag threshold violations.
|
|
157
|
+
* - Only non-negative numbers are compared (minimum percentages). Vitest's
|
|
158
|
+
* negative "max uncovered count" form is not evaluated here.
|
|
159
|
+
* - A glob that matches no file, or whose matched summaries lack
|
|
160
|
+
* covered/total counts, yields nothing.
|
|
161
|
+
*/
|
|
162
|
+
function computeGlobShortfalls(files, resolved) {
|
|
163
|
+
const shortfalls = [];
|
|
164
|
+
for (const [pattern, metrics] of resolved.patterns ?? []) {
|
|
165
|
+
if (metrics.perFile === true) continue;
|
|
166
|
+
const thresholds = {
|
|
167
|
+
...metrics.lines !== void 0 ? { lines: metrics.lines } : {},
|
|
168
|
+
...metrics.functions !== void 0 ? { functions: metrics.functions } : {},
|
|
169
|
+
...metrics.branches !== void 0 ? { branches: metrics.branches } : {},
|
|
170
|
+
...metrics.statements !== void 0 ? { statements: metrics.statements } : {}
|
|
171
|
+
};
|
|
172
|
+
if (METRICS.every((m) => thresholds[m] === void 0)) continue;
|
|
173
|
+
const matched = files.filter((f) => matchGlob(f.matchPath, pattern));
|
|
174
|
+
if (matched.length === 0) continue;
|
|
175
|
+
const aggregate = aggregateSummaries(matched.map((f) => f.summary));
|
|
176
|
+
if (aggregate === void 0) continue;
|
|
177
|
+
if (METRICS.some((m) => {
|
|
178
|
+
const min = thresholds[m];
|
|
179
|
+
return min !== void 0 && min >= 0 && aggregate[m] < min;
|
|
180
|
+
})) shortfalls.push({
|
|
181
|
+
pattern,
|
|
182
|
+
summary: aggregate,
|
|
183
|
+
thresholds
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return shortfalls;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
89
189
|
* Internal coverage processing logic. Shared by both `process` and `processScoped`.
|
|
90
190
|
*
|
|
91
191
|
* @param coverageMap - The value received by `onCoverage`; duck-typed at runtime
|
|
@@ -110,10 +210,15 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
110
210
|
const belowTarget = [];
|
|
111
211
|
const { root } = options;
|
|
112
212
|
const matchKey = (filePath) => toPosixPath(root === void 0 ? filePath : relative(root, filePath));
|
|
213
|
+
const mapSummaries = [];
|
|
113
214
|
for (const filePath of coverageMap.files()) {
|
|
114
215
|
const matchPath = matchKey(filePath);
|
|
115
216
|
const fileCoverage = coverageMap.fileCoverageFor(filePath);
|
|
116
217
|
const fileSummary = fileCoverage.toSummary();
|
|
218
|
+
mapSummaries.push({
|
|
219
|
+
matchPath,
|
|
220
|
+
summary: fileSummary
|
|
221
|
+
});
|
|
117
222
|
const fileStats = {
|
|
118
223
|
statements: fileSummary.statements.pct,
|
|
119
224
|
branches: fileSummary.branches.pct,
|
|
@@ -134,7 +239,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
134
239
|
continue;
|
|
135
240
|
}
|
|
136
241
|
if (options.targets) {
|
|
137
|
-
if (isBelowMetricThresholds(fileStats, resolveEffectiveThresholds(matchPath, options.targets))) {
|
|
242
|
+
if (isBelowMetricThresholds(fileStats, resolveEffectivePerFileThresholds(matchPath, options.targets) ?? resolveEffectiveThresholds(matchPath, options.targets))) {
|
|
138
243
|
const uncoveredLines = compressLines(fileCoverage.getUncoveredLines());
|
|
139
244
|
belowTarget.push({
|
|
140
245
|
file: filePath,
|
|
@@ -144,6 +249,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
144
249
|
}
|
|
145
250
|
}
|
|
146
251
|
}
|
|
252
|
+
const globShortfalls = scoped ? [] : computeGlobShortfalls(mapSummaries, options.thresholds);
|
|
147
253
|
lowCoverage.sort((a, b) => a.summary.lines - b.summary.lines);
|
|
148
254
|
belowTarget.sort((a, b) => a.summary.lines - b.summary.lines);
|
|
149
255
|
return {
|
|
@@ -165,6 +271,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
165
271
|
...scoped && options.totalFiles !== void 0 ? { totalFiles: options.totalFiles } : {},
|
|
166
272
|
lowCoverage,
|
|
167
273
|
lowCoverageFiles: lowCoverage.map((f) => f.file),
|
|
274
|
+
...globShortfalls.length > 0 ? { globShortfalls } : {},
|
|
168
275
|
...options.targets ? {
|
|
169
276
|
belowTarget,
|
|
170
277
|
belowTargetFiles: belowTarget.map((f) => f.file)
|
package/layers/ReporterLive.js
CHANGED
|
@@ -8,11 +8,10 @@ import { Layer } from "effect";
|
|
|
8
8
|
* `PlatformLive` (SQLite, migrations, Node platform services, logger and
|
|
9
9
|
* the shared service layers) plus the plugin-only `CoverageAnalyzer`.
|
|
10
10
|
*
|
|
11
|
-
* The return type is spelled out so the emitted declaration names
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* resolve.
|
|
11
|
+
* The return type is spelled out so the emitted declaration names the
|
|
12
|
+
* error through the engine's `PlatformLiveError` alias; left to inference
|
|
13
|
+
* it is emitted via `@effected/store`, which this package does not depend
|
|
14
|
+
* on and a root typecheck cannot resolve.
|
|
16
15
|
*
|
|
17
16
|
* @param options - forwarded to `PlatformLive`; the reporter passes
|
|
18
17
|
* `process.env` as `env`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/plugin",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.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": [
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@effect/platform-node": "^4.0.0",
|
|
47
|
-
"@effected/cli": "^0.
|
|
47
|
+
"@effected/cli": "^0.12.0",
|
|
48
48
|
"@effected/env": "^0.1.0",
|
|
49
49
|
"@effected/glob": "^0.10.0",
|
|
50
50
|
"@effected/walker": "^0.15.0",
|
|
51
51
|
"@effected/workspaces": "^0.31.0",
|
|
52
|
-
"@vitest-agent/cli": "4.0.
|
|
53
|
-
"@vitest-agent/engine": "0.
|
|
54
|
-
"@vitest-agent/mcp": "5.1.
|
|
55
|
-
"@vitest-agent/reporter": "4.
|
|
56
|
-
"@vitest-agent/sdk": "6.
|
|
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
57
|
"effect": "^4.0.0",
|
|
58
58
|
"magic-string": "^1.4.2"
|
|
59
59
|
},
|
package/reporter.js
CHANGED
|
@@ -1287,7 +1287,8 @@ var AgentReporter = class {
|
|
|
1287
1287
|
})),
|
|
1288
1288
|
...coverageReport.scoped ? { scoped: coverageReport.scoped } : {},
|
|
1289
1289
|
...coverageReport.scopedFiles !== void 0 ? { scopedFiles: coverageReport.scopedFiles.length } : {},
|
|
1290
|
-
...coverageReport.totalFiles !== void 0 ? { totalFiles: coverageReport.totalFiles } : {}
|
|
1290
|
+
...coverageReport.totalFiles !== void 0 ? { totalFiles: coverageReport.totalFiles } : {},
|
|
1291
|
+
...coverageReport.globShortfalls !== void 0 ? { globShortfalls: coverageReport.globShortfalls } : {}
|
|
1291
1292
|
});
|
|
1292
1293
|
if (!coverageReport.scoped) for (const metric of [
|
|
1293
1294
|
"lines",
|