@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 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
- * `MigrationError` through `effect/sql/Migrator`; left to
1301
- * inference it is emitted via `@effect/sql-sqlite-node/SqliteMigrator`,
1302
- * which this package does not depend on and a root typecheck cannot
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, MigrationError | SqlError>;
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)
@@ -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
- * `MigrationError` through `effect/sql/Migrator`; left to
13
- * inference it is emitted via `@effect/sql-sqlite-node/SqliteMigrator`,
14
- * which this package does not depend on and a root typecheck cannot
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.2.1",
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.11.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.1",
53
- "@vitest-agent/engine": "0.5.0",
54
- "@vitest-agent/mcp": "5.1.7",
55
- "@vitest-agent/reporter": "4.0.1",
56
- "@vitest-agent/sdk": "6.0.0",
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",
package/version.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @public
10
10
  */
11
- const CURRENT_PLUGIN_VERSION = "5.2.1";
11
+ const CURRENT_PLUGIN_VERSION = "5.3.0";
12
12
 
13
13
  //#endregion
14
14
  export { CURRENT_PLUGIN_VERSION };