@vitest-agent/reporter 2.1.10 → 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.
@@ -1,3 +1,4 @@
1
+ import { renderGithubLog, toDisplayPath } from "./githubLog.js";
1
2
  import { createLiveInk } from "./LiveInkRenderer.js";
2
3
  import { countSuiteFailures } from "@vitest-agent/sdk";
3
4
  import { classifyOutcome, classifyRunShape, dispatch, dispatcherTable, reduceRenderStateAll, synthesizeFromAgentReport } from "@vitest-agent/ui";
@@ -136,17 +137,82 @@ const renderHumanStringForReport = async (report, options = {}) => {
136
137
  if (cell.ink === void 0) return dispatch(inputs, opts);
137
138
  return renderToString(cell.ink(inputs, opts), { columns: options.width ?? 80 });
138
139
  };
139
- const renderGithubSummary = (input) => {
140
- const out = [];
141
- for (const report of input.reports) {
142
- const body = `${`## ${report.project ?? "Vitest Results"}`}\n\n${`${report.summary.passed}/${report.summary.total} passed, ${report.summary.failed} failed, ${report.summary.skipped} skipped`}\n`;
143
- out.push({
144
- target: "github-summary",
145
- content: body,
146
- contentType: "text/markdown"
147
- });
140
+ const NON_STABLE_SUMMARY_CLASSIFICATIONS = [
141
+ "new-failure",
142
+ "persistent",
143
+ "flaky",
144
+ "recovered"
145
+ ];
146
+ const MAX_SUMMARY_COVERAGE_ROWS = 10;
147
+ const renderClassificationsSection = (classifications) => {
148
+ const counts = /* @__PURE__ */ new Map();
149
+ for (const classification of classifications.values()) counts.set(classification, (counts.get(classification) ?? 0) + 1);
150
+ const rows = NON_STABLE_SUMMARY_CLASSIFICATIONS.filter((kind) => (counts.get(kind) ?? 0) > 0).map((kind) => `| ${kind} | ${counts.get(kind)} |`);
151
+ if (rows.length === 0) return null;
152
+ return [
153
+ "### Classifications",
154
+ "",
155
+ "| Classification | Count |",
156
+ "| --- | --- |",
157
+ ...rows
158
+ ].join("\n");
159
+ };
160
+ const formatCoveragePercent = (totals) => `stmts ${totals.statements}%, branches ${totals.branches}%, funcs ${totals.functions}%, lines ${totals.lines}%`;
161
+ const renderCoverageSection = (reports) => {
162
+ const belowTarget = reports.flatMap((r) => r.coverage?.belowTarget ?? []);
163
+ if (belowTarget.length === 0) return null;
164
+ const rows = belowTarget.slice(0, MAX_SUMMARY_COVERAGE_ROWS).map((f) => `| ${toDisplayPath(f.file)} | ${formatCoveragePercent(f.summary)} |`);
165
+ const lines = [
166
+ "### Coverage",
167
+ "",
168
+ `${belowTarget.length} file(s) below target.`,
169
+ "",
170
+ "| File | Coverage |",
171
+ "| --- | --- |",
172
+ ...rows
173
+ ];
174
+ if (belowTarget.length > MAX_SUMMARY_COVERAGE_ROWS) lines.push("", `(+${belowTarget.length - MAX_SUMMARY_COVERAGE_ROWS} more not shown)`);
175
+ return lines.join("\n");
176
+ };
177
+ const renderTrendSection = (trendSummary) => {
178
+ if (trendSummary === void 0) return null;
179
+ const lines = [
180
+ "### Trend",
181
+ "",
182
+ `Direction: ${trendSummary.direction}`,
183
+ `Run count: ${trendSummary.runCount}`
184
+ ];
185
+ const firstMetric = trendSummary.firstMetric;
186
+ if (firstMetric !== void 0) {
187
+ const targetSuffix = firstMetric.target !== void 0 ? ` (target: ${firstMetric.target})` : "";
188
+ lines.push(`${firstMetric.name}: ${firstMetric.from} → ${firstMetric.to}${targetSuffix}`);
148
189
  }
149
- return out;
190
+ return lines.join("\n");
191
+ };
192
+ /**
193
+ * Builds the vitest-agent GitHub step-summary payload — ONE `RenderedOutput`
194
+ * for the whole run, carrying only data Vitest's own built-in
195
+ * `github-actions` reporter cannot know: test classifications (new-failure /
196
+ * persistent / flaky / recovered), coverage-target shortfalls, and the
197
+ * coverage trend. Vitest's reporter already writes pass/fail/skip counts
198
+ * and a flaky-tests section to the same `$GITHUB_STEP_SUMMARY` file, so
199
+ * duplicating that per-project breakdown here would be redundant noise
200
+ * multiplied across every project in a workspace. Returns an empty array
201
+ * when all three sections would be empty — a clean run should not leave a
202
+ * bare heading in the job summary.
203
+ */
204
+ const renderGithubSummary = (input) => {
205
+ const sections = [
206
+ renderClassificationsSection(input.classifications),
207
+ renderCoverageSection(input.reports),
208
+ renderTrendSection(input.trendSummary)
209
+ ].filter((section) => section !== null);
210
+ if (sections.length === 0) return [];
211
+ return [{
212
+ target: "github-summary",
213
+ content: `\n${["## vitest-agent", ...sections].join("\n\n")}\n`,
214
+ contentType: "text/markdown"
215
+ }];
150
216
  };
151
217
  /**
152
218
  * Subscribe a live Ink mount to the kit's run-event channel.
@@ -207,7 +273,10 @@ const DefaultVitestAgentReporter = (kit) => {
207
273
  contentType: "text/plain"
208
274
  });
209
275
  }
210
- if (renderKit.config.githubActions === true) out.push(...renderGithubSummary(input));
276
+ if (renderKit.config.githubActions === true) {
277
+ out.push(...renderGithubSummary(input));
278
+ out.push(renderGithubLog(input, renderKit));
279
+ }
211
280
  return out;
212
281
  } };
213
282
  };
package/githubLog.js ADDED
@@ -0,0 +1,61 @@
1
+ import { relative } from "node:path";
2
+
3
+ //#region src/githubLog.ts
4
+ /**
5
+ * Light end-of-run log block for GitHub Actions.
6
+ *
7
+ * @privateRemarks
8
+ * Wraps a summary of the run in GitHub's collapsed-group log markers so it
9
+ * is collapsed by default in the Actions log viewer and never competes
10
+ * with Vitest's own console output. Pushed alongside `renderGithubSummary`
11
+ * whenever `kit.config.githubActions` is true. All data is derived from
12
+ * fields already on `ReporterRenderInput` / `ReporterKit` — no new
13
+ * plumbing. No ANSI escapes: GitHub renders raw log text.
14
+ *
15
+ * @internal
16
+ */
17
+ const MAX_NAMED_FILES = 3;
18
+ /**
19
+ * Renders a coverage-target file path relative to `process.cwd()` for
20
+ * display. Falls back to the original string unchanged when the relative
21
+ * path is empty or escapes the root (starts with `..`) rather than
22
+ * printing a `../../..` chain.
23
+ */
24
+ function toDisplayPath(file) {
25
+ const rel = relative(process.cwd(), file);
26
+ if (rel === "" || rel.startsWith("..")) return file;
27
+ return rel;
28
+ }
29
+ const NON_STABLE_CLASSIFICATIONS = [
30
+ "new-failure",
31
+ "persistent",
32
+ "flaky",
33
+ "recovered"
34
+ ];
35
+ function renderGithubLog(input, kit) {
36
+ const lines = ["::group::vitest-agent"];
37
+ for (const report of input.reports) {
38
+ const name = report.project ?? "default";
39
+ lines.push(`${name}: ${report.summary.passed}/${report.summary.total} passed, ${report.summary.failed} failed, ${report.summary.skipped} skipped`);
40
+ }
41
+ const belowTarget = input.reports.flatMap((r) => r.coverage?.belowTarget ?? []);
42
+ if (belowTarget.length > 0) {
43
+ const names = belowTarget.slice(0, MAX_NAMED_FILES).map((f) => toDisplayPath(f.file));
44
+ const suffix = belowTarget.length > MAX_NAMED_FILES ? `, +${belowTarget.length - MAX_NAMED_FILES} more` : "";
45
+ lines.push(`coverage: ${belowTarget.length} file(s) below target (${names.join(", ")}${suffix})`);
46
+ }
47
+ const classificationCounts = /* @__PURE__ */ new Map();
48
+ for (const classification of input.classifications.values()) classificationCounts.set(classification, (classificationCounts.get(classification) ?? 0) + 1);
49
+ const classificationParts = NON_STABLE_CLASSIFICATIONS.filter((kind) => (classificationCounts.get(kind) ?? 0) > 0).map((kind) => `${kind}: ${classificationCounts.get(kind)}`);
50
+ if (classificationParts.length > 0) lines.push(`classifications: ${classificationParts.join(", ")}`);
51
+ if (kit.config.dbPath !== void 0) lines.push(`db: ${kit.config.dbPath}`);
52
+ lines.push("::endgroup::");
53
+ return {
54
+ target: "stdout",
55
+ content: lines.join("\n"),
56
+ contentType: "text/plain"
57
+ };
58
+ }
59
+
60
+ //#endregion
61
+ export { renderGithubLog, toDisplayPath };
package/index.js CHANGED
@@ -9,7 +9,7 @@ import { DefaultVitestAgentReporter, buildDispatchInputs, renderAgentStringForRe
9
9
  *
10
10
  * @public
11
11
  */
12
- const CURRENT_REPORTER_VERSION = "2.1.10";
12
+ const CURRENT_REPORTER_VERSION = "2.2.0";
13
13
 
14
14
  //#endregion
15
15
  export { CURRENT_REPORTER_VERSION, DefaultVitestAgentReporter, createLiveInk as _createLiveInk, buildDispatchInputs, renderAgentStringForReport, renderHumanStringForReport, resolveCellOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/reporter",
3
- "version": "2.1.10",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "description": "Named VitestAgentReporterFactory implementations for the vitest-agent ecosystem.",
6
6
  "keywords": [
@@ -41,8 +41,8 @@
41
41
  "dependencies": {
42
42
  "@effect/platform-node": "4.0.0-rc.109",
43
43
  "@effect/sql-sqlite-node": "4.0.0-rc.109",
44
- "@vitest-agent/sdk": "2.4.9",
45
- "@vitest-agent/ui": "2.2.9",
44
+ "@vitest-agent/sdk": "2.4.11",
45
+ "@vitest-agent/ui": "2.2.11",
46
46
  "effect": "4.0.0-rc.109",
47
47
  "ink": "^7.1.1",
48
48
  "react": "^19.2.8"