@vitest-evals/github-reporter 0.9.0-beta.5
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/LICENSE +201 -0
- package/README.md +86 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +970 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +969 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +179 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.js +819 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +788 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +34 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
var import_promises = require("fs/promises");
|
|
6
|
+
|
|
7
|
+
// src/cli-options.ts
|
|
8
|
+
function parseCliArgs(args, env = process.env) {
|
|
9
|
+
const options = {
|
|
10
|
+
summaryPath: env.GITHUB_STEP_SUMMARY,
|
|
11
|
+
summaryEnabled: true,
|
|
12
|
+
annotations: env.GITHUB_ACTIONS === "true",
|
|
13
|
+
checkRun: false,
|
|
14
|
+
failOnCheckError: false,
|
|
15
|
+
help: false
|
|
16
|
+
};
|
|
17
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
18
|
+
const arg = args[index];
|
|
19
|
+
switch (arg) {
|
|
20
|
+
case "--json":
|
|
21
|
+
options.jsonPath = readValue(args, ++index, arg);
|
|
22
|
+
break;
|
|
23
|
+
case "--summary":
|
|
24
|
+
options.summaryPath = readValue(args, ++index, arg);
|
|
25
|
+
options.summaryEnabled = true;
|
|
26
|
+
break;
|
|
27
|
+
case "--no-summary":
|
|
28
|
+
options.summaryEnabled = false;
|
|
29
|
+
break;
|
|
30
|
+
case "--annotations":
|
|
31
|
+
options.annotations = true;
|
|
32
|
+
break;
|
|
33
|
+
case "--no-annotations":
|
|
34
|
+
options.annotations = false;
|
|
35
|
+
break;
|
|
36
|
+
case "--check-run":
|
|
37
|
+
options.checkRun = true;
|
|
38
|
+
break;
|
|
39
|
+
case "--fail-on-check-error":
|
|
40
|
+
options.failOnCheckError = true;
|
|
41
|
+
break;
|
|
42
|
+
case "--max-annotations":
|
|
43
|
+
options.maxAnnotations = readInteger(args, ++index, arg);
|
|
44
|
+
break;
|
|
45
|
+
case "--max-failures":
|
|
46
|
+
options.maxFailures = readInteger(args, ++index, arg);
|
|
47
|
+
break;
|
|
48
|
+
case "--check-run-id":
|
|
49
|
+
options.checkRunId = readInteger(args, ++index, arg);
|
|
50
|
+
options.checkRun = true;
|
|
51
|
+
break;
|
|
52
|
+
case "--check-name":
|
|
53
|
+
options.checkName = readValue(args, ++index, arg);
|
|
54
|
+
break;
|
|
55
|
+
case "--token":
|
|
56
|
+
options.token = readValue(args, ++index, arg);
|
|
57
|
+
break;
|
|
58
|
+
case "--repo":
|
|
59
|
+
options.repository = readValue(args, ++index, arg);
|
|
60
|
+
break;
|
|
61
|
+
case "--sha":
|
|
62
|
+
options.sha = readValue(args, ++index, arg);
|
|
63
|
+
break;
|
|
64
|
+
case "--workspace":
|
|
65
|
+
options.workspace = readValue(args, ++index, arg);
|
|
66
|
+
break;
|
|
67
|
+
case "--help":
|
|
68
|
+
case "-h":
|
|
69
|
+
options.help = true;
|
|
70
|
+
return withDefaultJsonPath(options, env);
|
|
71
|
+
default:
|
|
72
|
+
if (!arg.startsWith("-") && !options.jsonPath) {
|
|
73
|
+
options.jsonPath = arg;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return withDefaultJsonPath(options, env);
|
|
80
|
+
}
|
|
81
|
+
function withDefaultJsonPath(options, env) {
|
|
82
|
+
return {
|
|
83
|
+
...options,
|
|
84
|
+
jsonPath: options.jsonPath || env.VITEST_EVALS_JSON_REPORT || "vitest-results.json"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function readValue(args, index, flag) {
|
|
88
|
+
const value = args[index];
|
|
89
|
+
if (!value) {
|
|
90
|
+
throw new Error(`Missing value for ${flag}`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function readInteger(args, index, flag) {
|
|
95
|
+
const value = Number.parseInt(readValue(args, index, flag), 10);
|
|
96
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
97
|
+
throw new Error(`Invalid integer for ${flag}`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/utils.ts
|
|
103
|
+
var import_node_path = require("path");
|
|
104
|
+
function isRecord(value) {
|
|
105
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
106
|
+
}
|
|
107
|
+
function compactLine(value, maxLength) {
|
|
108
|
+
const line = value.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0);
|
|
109
|
+
if (!line) {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
return truncate(line, maxLength);
|
|
113
|
+
}
|
|
114
|
+
function truncate(value, maxLength) {
|
|
115
|
+
if (maxLength <= 0) {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
if (value.length <= maxLength) {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
return `${value.slice(0, Math.max(0, maxLength - 15)).trimEnd()}... [truncated]`;
|
|
122
|
+
}
|
|
123
|
+
function stringifyValue(value, maxLength) {
|
|
124
|
+
if (value === void 0) {
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
if (typeof value === "string") {
|
|
128
|
+
return truncate(value, maxLength);
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
return truncate(JSON.stringify(value, null, 2), maxLength);
|
|
132
|
+
} catch {
|
|
133
|
+
return truncate(String(value), maxLength);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function formatNumber(value) {
|
|
137
|
+
return new Intl.NumberFormat("en-US", {
|
|
138
|
+
maximumFractionDigits: 0
|
|
139
|
+
}).format(value);
|
|
140
|
+
}
|
|
141
|
+
function formatScore(value) {
|
|
142
|
+
if (value == null || !Number.isFinite(value)) {
|
|
143
|
+
return "n/a";
|
|
144
|
+
}
|
|
145
|
+
return value.toFixed(2);
|
|
146
|
+
}
|
|
147
|
+
function formatDuration(ms) {
|
|
148
|
+
if (ms === void 0 || !Number.isFinite(ms)) {
|
|
149
|
+
return "n/a";
|
|
150
|
+
}
|
|
151
|
+
if (ms < 1e3) {
|
|
152
|
+
return `${Math.round(ms)}ms`;
|
|
153
|
+
}
|
|
154
|
+
const seconds = ms / 1e3;
|
|
155
|
+
if (seconds < 60) {
|
|
156
|
+
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
157
|
+
}
|
|
158
|
+
const totalSeconds = Math.round(seconds);
|
|
159
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
160
|
+
const remainingSeconds = totalSeconds % 60;
|
|
161
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
162
|
+
}
|
|
163
|
+
function formatLocation(file, location) {
|
|
164
|
+
if (!location) {
|
|
165
|
+
return file;
|
|
166
|
+
}
|
|
167
|
+
return `${file}:${location.line}`;
|
|
168
|
+
}
|
|
169
|
+
function normalizePathForGitHub(path, workspace) {
|
|
170
|
+
const normalized = path.replace(/\\/g, "/");
|
|
171
|
+
if (!workspace) {
|
|
172
|
+
return normalized;
|
|
173
|
+
}
|
|
174
|
+
const workspacePath = workspace.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
175
|
+
if (normalized !== workspacePath && !normalized.startsWith(`${workspacePath}/`)) {
|
|
176
|
+
return normalized;
|
|
177
|
+
}
|
|
178
|
+
return import_node_path.posix.relative(workspacePath, normalized);
|
|
179
|
+
}
|
|
180
|
+
function escapeFence(value) {
|
|
181
|
+
return value.replace(/```/g, "'''");
|
|
182
|
+
}
|
|
183
|
+
function escapeHtml(value) {
|
|
184
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
185
|
+
}
|
|
186
|
+
function escapeCommandData(value) {
|
|
187
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
188
|
+
}
|
|
189
|
+
function escapeCommandProperty(value) {
|
|
190
|
+
return escapeCommandData(value).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/collect.ts
|
|
194
|
+
function collectEvalReport(input, options = {}) {
|
|
195
|
+
const cases = input.testResults.flatMap(
|
|
196
|
+
(file) => file.assertionResults.flatMap((assertion) => {
|
|
197
|
+
const evalCase = collectEvalCase(file, assertion, options);
|
|
198
|
+
return evalCase ? [evalCase] : [];
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
const failures = cases.filter((testCase) => testCase.status === "failed");
|
|
202
|
+
const evalScores = cases.map((testCase) => testCase.eval?.avgScore).filter((score) => isFiniteNumber(score));
|
|
203
|
+
const usage2 = sumUsage(cases);
|
|
204
|
+
const durationMs = resolveRunDuration(input);
|
|
205
|
+
return {
|
|
206
|
+
status: input.success && failures.length === 0 ? "passed" : "failed",
|
|
207
|
+
startedAt: input.startTime,
|
|
208
|
+
durationMs,
|
|
209
|
+
totals: {
|
|
210
|
+
total: input.numTotalTests,
|
|
211
|
+
passed: input.numPassedTests,
|
|
212
|
+
failed: input.numFailedTests,
|
|
213
|
+
skipped: input.numPendingTests + input.numTodoTests,
|
|
214
|
+
evalTotal: cases.length,
|
|
215
|
+
evalPassed: cases.filter((testCase) => testCase.status === "passed").length,
|
|
216
|
+
evalFailed: failures.length
|
|
217
|
+
},
|
|
218
|
+
score: evalScores.length > 0 ? {
|
|
219
|
+
average: evalScores.reduce((total, score) => total + score, 0) / evalScores.length,
|
|
220
|
+
minimum: Math.min(...evalScores)
|
|
221
|
+
} : void 0,
|
|
222
|
+
usage: usage2,
|
|
223
|
+
cases,
|
|
224
|
+
failures
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function collectEvalCase(file, assertion, options) {
|
|
228
|
+
const meta = isRecord(assertion.meta) ? assertion.meta : {};
|
|
229
|
+
const evalMeta = getEvalMeta(meta.eval);
|
|
230
|
+
const harnessMeta = getHarnessMeta(meta.harness);
|
|
231
|
+
if (!evalMeta && !harnessMeta) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const displayFile = normalizePathForGitHub(file.name, options.workspace);
|
|
235
|
+
const scores = evalMeta?.scores ?? [];
|
|
236
|
+
const harnessRun = harnessMeta?.run;
|
|
237
|
+
const toolCalls = collectToolCalls(harnessRun?.session);
|
|
238
|
+
const evalCase = {
|
|
239
|
+
id: `${file.name}:${assertion.location?.line ?? 0}:${assertion.fullName}`,
|
|
240
|
+
file: file.name,
|
|
241
|
+
displayFile,
|
|
242
|
+
title: assertion.title,
|
|
243
|
+
displayName: formatDisplayName(assertion),
|
|
244
|
+
status: assertion.status,
|
|
245
|
+
durationMs: typeof assertion.duration === "number" ? assertion.duration : void 0,
|
|
246
|
+
location: assertion.location ?? void 0,
|
|
247
|
+
failureMessages: assertion.failureMessages ?? [],
|
|
248
|
+
eval: evalMeta ? {
|
|
249
|
+
avgScore: evalMeta.avgScore,
|
|
250
|
+
thresholdFailed: evalMeta.thresholdFailed,
|
|
251
|
+
output: evalMeta.output,
|
|
252
|
+
scores
|
|
253
|
+
} : void 0,
|
|
254
|
+
harness: harnessMeta ? {
|
|
255
|
+
name: harnessMeta.name,
|
|
256
|
+
output: harnessRun?.output,
|
|
257
|
+
usage: harnessRun?.usage,
|
|
258
|
+
timingMs: harnessRun?.timings?.totalMs,
|
|
259
|
+
toolCalls,
|
|
260
|
+
errors: harnessRun?.errors ?? []
|
|
261
|
+
} : void 0
|
|
262
|
+
};
|
|
263
|
+
evalCase.primaryFailure = getPrimaryFailure(evalCase);
|
|
264
|
+
return evalCase;
|
|
265
|
+
}
|
|
266
|
+
function getEvalMeta(value) {
|
|
267
|
+
if (!isRecord(value)) {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
scores: Array.isArray(value.scores) ? value.scores.filter(isRecord).map(normalizeScore) : void 0,
|
|
272
|
+
avgScore: numberField(value.avgScore),
|
|
273
|
+
output: value.output,
|
|
274
|
+
thresholdFailed: typeof value.thresholdFailed === "boolean" ? value.thresholdFailed : void 0
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function normalizeScore(score) {
|
|
278
|
+
const metadata = isRecord(score.metadata) ? score.metadata : void 0;
|
|
279
|
+
return {
|
|
280
|
+
name: typeof score.name === "string" ? score.name : void 0,
|
|
281
|
+
score: numberField(score.score) ?? null,
|
|
282
|
+
metadata
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function getHarnessMeta(value) {
|
|
286
|
+
if (!isRecord(value)) {
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
const run = isRecord(value.run) ? value.run : void 0;
|
|
290
|
+
const usage2 = isRecord(run?.usage) ? getUsage(run.usage) : void 0;
|
|
291
|
+
const timings = isRecord(run?.timings) ? run.timings : void 0;
|
|
292
|
+
return {
|
|
293
|
+
name: typeof value.name === "string" ? value.name : void 0,
|
|
294
|
+
run: run ? {
|
|
295
|
+
output: run.output,
|
|
296
|
+
usage: usage2,
|
|
297
|
+
timings: {
|
|
298
|
+
totalMs: typeof timings?.totalMs === "number" ? timings.totalMs : void 0
|
|
299
|
+
},
|
|
300
|
+
session: isRecord(run.session) ? {
|
|
301
|
+
messages: Array.isArray(run.session.messages) ? run.session.messages : void 0
|
|
302
|
+
} : void 0,
|
|
303
|
+
errors: Array.isArray(run.errors) ? run.errors : void 0
|
|
304
|
+
} : void 0
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function getUsage(value) {
|
|
308
|
+
return {
|
|
309
|
+
inputTokens: numberField(value.inputTokens),
|
|
310
|
+
outputTokens: numberField(value.outputTokens),
|
|
311
|
+
reasoningTokens: numberField(value.reasoningTokens),
|
|
312
|
+
totalTokens: numberField(value.totalTokens),
|
|
313
|
+
estimatedCost: numberField(value.estimatedCost),
|
|
314
|
+
toolCalls: numberField(value.toolCalls)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function numberField(value) {
|
|
318
|
+
return isFiniteNumber(value) ? value : void 0;
|
|
319
|
+
}
|
|
320
|
+
function isFiniteNumber(value) {
|
|
321
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
322
|
+
}
|
|
323
|
+
function collectToolCalls(session) {
|
|
324
|
+
const messages = session?.messages ?? [];
|
|
325
|
+
const toolCalls = [];
|
|
326
|
+
for (const message of messages) {
|
|
327
|
+
if (!Array.isArray(message.toolCalls)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
for (const call of message.toolCalls) {
|
|
331
|
+
if (!isRecord(call) || typeof call.name !== "string") {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
toolCalls.push({
|
|
335
|
+
name: call.name,
|
|
336
|
+
error: getToolCallError(call.error),
|
|
337
|
+
durationMs: numberField(call.durationMs)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return toolCalls;
|
|
342
|
+
}
|
|
343
|
+
function getToolCallError(value) {
|
|
344
|
+
if (isRecord(value) && typeof value.message === "string") {
|
|
345
|
+
return value.message;
|
|
346
|
+
}
|
|
347
|
+
if (value !== void 0) {
|
|
348
|
+
return stringifyValue(value, 240);
|
|
349
|
+
}
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
function getPrimaryFailure(testCase) {
|
|
353
|
+
const failingScores = [...testCase.eval?.scores ?? []].filter(
|
|
354
|
+
(score2) => (score2.score ?? 0) < 1 || score2.metadata?.rationale !== void 0 || score2.metadata?.output !== void 0
|
|
355
|
+
).sort((left, right) => (left.score ?? 0) - (right.score ?? 0));
|
|
356
|
+
const primary = failingScores[0];
|
|
357
|
+
const score = typeof primary?.score === "number" ? primary.score : testCase.eval?.avgScore;
|
|
358
|
+
const reason = stringifyReason(primary?.metadata?.rationale) ?? compactLine(testCase.failureMessages.join("\n"), 500);
|
|
359
|
+
if (!primary && !reason && score === void 0) {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
judgeName: primary?.name,
|
|
364
|
+
score,
|
|
365
|
+
reason: reason || void 0
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function stringifyReason(value) {
|
|
369
|
+
if (value === void 0) {
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
return typeof value === "string" ? value : stringifyValue(value, 4e3);
|
|
373
|
+
}
|
|
374
|
+
function formatDisplayName(assertion) {
|
|
375
|
+
return [...assertion.ancestorTitles, assertion.title].filter((part) => part.length > 0).join(" > ");
|
|
376
|
+
}
|
|
377
|
+
function sumUsage(cases) {
|
|
378
|
+
const usage2 = {
|
|
379
|
+
inputTokens: 0,
|
|
380
|
+
outputTokens: 0,
|
|
381
|
+
reasoningTokens: 0,
|
|
382
|
+
totalTokens: 0,
|
|
383
|
+
estimatedCost: 0,
|
|
384
|
+
toolCalls: 0
|
|
385
|
+
};
|
|
386
|
+
for (const testCase of cases) {
|
|
387
|
+
const caseUsage = testCase.harness?.usage;
|
|
388
|
+
usage2.inputTokens += caseUsage?.inputTokens ?? 0;
|
|
389
|
+
usage2.outputTokens += caseUsage?.outputTokens ?? 0;
|
|
390
|
+
usage2.reasoningTokens += caseUsage?.reasoningTokens ?? 0;
|
|
391
|
+
usage2.totalTokens += caseUsage?.totalTokens ?? (caseUsage?.inputTokens ?? 0) + (caseUsage?.outputTokens ?? 0) + (caseUsage?.reasoningTokens ?? 0);
|
|
392
|
+
usage2.estimatedCost += caseUsage?.estimatedCost ?? 0;
|
|
393
|
+
usage2.toolCalls += caseUsage?.toolCalls ?? testCase.harness?.toolCalls.length ?? 0;
|
|
394
|
+
}
|
|
395
|
+
return usage2;
|
|
396
|
+
}
|
|
397
|
+
function resolveRunDuration(input) {
|
|
398
|
+
const startTimes = input.testResults.map((file) => file.startTime).filter((time) => Number.isFinite(time));
|
|
399
|
+
const endTimes = input.testResults.map((file) => file.endTime).filter((time) => Number.isFinite(time));
|
|
400
|
+
if (startTimes.length === 0 || endTimes.length === 0) {
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
return Math.max(...endTimes) - Math.min(...startTimes);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/annotations.ts
|
|
407
|
+
var DEFAULT_MAX_WORKFLOW_ANNOTATIONS = 10;
|
|
408
|
+
var DEFAULT_MAX_CHECK_ANNOTATIONS = 50;
|
|
409
|
+
var MAX_CHECK_FIELD_LENGTH = 64e3;
|
|
410
|
+
function renderWorkflowCommands(report, options = {}) {
|
|
411
|
+
const maxAnnotations = options.maxAnnotations ?? DEFAULT_MAX_WORKFLOW_ANNOTATIONS;
|
|
412
|
+
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map(
|
|
413
|
+
(testCase) => formatWorkflowCommand({
|
|
414
|
+
command: "error",
|
|
415
|
+
properties: {
|
|
416
|
+
file: testCase.displayFile,
|
|
417
|
+
line: String(testCase.location.line),
|
|
418
|
+
col: String(testCase.location.column),
|
|
419
|
+
title: "vitest-evals"
|
|
420
|
+
},
|
|
421
|
+
message: formatWorkflowMessage(testCase)
|
|
422
|
+
})
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function buildCheckAnnotations(report, options = {}) {
|
|
426
|
+
const maxAnnotations = options.maxAnnotations ?? DEFAULT_MAX_CHECK_ANNOTATIONS;
|
|
427
|
+
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map((testCase) => ({
|
|
428
|
+
path: testCase.displayFile,
|
|
429
|
+
start_line: testCase.location.line,
|
|
430
|
+
end_line: testCase.location.line,
|
|
431
|
+
annotation_level: "failure",
|
|
432
|
+
title: truncate(
|
|
433
|
+
`${testCase.primaryFailure?.judgeName ?? "vitest-evals"} - ${testCase.displayName}`,
|
|
434
|
+
255
|
|
435
|
+
),
|
|
436
|
+
message: truncate(
|
|
437
|
+
formatWorkflowMessage(testCase),
|
|
438
|
+
MAX_CHECK_FIELD_LENGTH
|
|
439
|
+
),
|
|
440
|
+
raw_details: truncate(formatRawDetails(testCase), MAX_CHECK_FIELD_LENGTH)
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
function hasAnnotationLocation(testCase) {
|
|
444
|
+
return Boolean(testCase.location);
|
|
445
|
+
}
|
|
446
|
+
function formatWorkflowMessage(testCase) {
|
|
447
|
+
const failure = testCase.primaryFailure;
|
|
448
|
+
const parts = [
|
|
449
|
+
testCase.displayName,
|
|
450
|
+
`score ${formatScore(failure?.score ?? testCase.eval?.avgScore)}`
|
|
451
|
+
];
|
|
452
|
+
if (failure?.judgeName) {
|
|
453
|
+
parts.push(failure.judgeName);
|
|
454
|
+
}
|
|
455
|
+
const reason = compactLine(failure?.reason ?? "", 320);
|
|
456
|
+
if (reason) {
|
|
457
|
+
parts.push(reason);
|
|
458
|
+
}
|
|
459
|
+
return parts.join(" - ");
|
|
460
|
+
}
|
|
461
|
+
function formatRawDetails(testCase) {
|
|
462
|
+
const lines = [
|
|
463
|
+
`Test: ${testCase.displayName}`,
|
|
464
|
+
`Location: ${testCase.displayFile}:${testCase.location.line}`,
|
|
465
|
+
`Harness: ${testCase.harness?.name ?? "n/a"}`,
|
|
466
|
+
`Score: ${formatScore(testCase.primaryFailure?.score ?? testCase.eval?.avgScore)}`,
|
|
467
|
+
`Judge: ${testCase.primaryFailure?.judgeName ?? "n/a"}`,
|
|
468
|
+
"",
|
|
469
|
+
"Reason:",
|
|
470
|
+
testCase.primaryFailure?.reason ?? "n/a"
|
|
471
|
+
];
|
|
472
|
+
const finalOutput = testCase.eval?.output ?? testCase.harness?.output;
|
|
473
|
+
if (finalOutput !== void 0) {
|
|
474
|
+
lines.push("", "Final:", stringifyValue(finalOutput, 8e3));
|
|
475
|
+
}
|
|
476
|
+
if (testCase.harness?.toolCalls.length) {
|
|
477
|
+
lines.push("", "Tools:");
|
|
478
|
+
for (const toolCall of testCase.harness.toolCalls) {
|
|
479
|
+
lines.push(
|
|
480
|
+
`- ${toolCall.name}: ${toolCall.error ? `error: ${toolCall.error}` : "ok"}`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return lines.join("\n");
|
|
485
|
+
}
|
|
486
|
+
function formatWorkflowCommand({
|
|
487
|
+
command,
|
|
488
|
+
properties,
|
|
489
|
+
message
|
|
490
|
+
}) {
|
|
491
|
+
const renderedProperties = Object.entries(properties).map(([key, value]) => `${key}=${escapeCommandProperty(value)}`).join(",");
|
|
492
|
+
return `::${command} ${renderedProperties}::${escapeCommandData(message)}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/summary.ts
|
|
496
|
+
var DEFAULT_MAX_FAILURES = 20;
|
|
497
|
+
var DEFAULT_MAX_REASON_CHARS = 8e3;
|
|
498
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 4e3;
|
|
499
|
+
var DEFAULT_MAX_TOOL_CALLS = 20;
|
|
500
|
+
var SCORE_DISTRIBUTION_BUCKETS = [
|
|
501
|
+
"0-19%",
|
|
502
|
+
"20-39%",
|
|
503
|
+
"40-59%",
|
|
504
|
+
"60-79%",
|
|
505
|
+
"80-100%"
|
|
506
|
+
];
|
|
507
|
+
var SCORE_DISTRIBUTION_BAR_WIDTH = 20;
|
|
508
|
+
function renderJobSummary(report, options = {}) {
|
|
509
|
+
const maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;
|
|
510
|
+
const failures = report.failures.slice(0, maxFailures);
|
|
511
|
+
const nonEvalFailures = report.totals.failed - report.totals.evalFailed;
|
|
512
|
+
const lines = [
|
|
513
|
+
"# vitest-evals",
|
|
514
|
+
"",
|
|
515
|
+
...renderSummaryTable(report, nonEvalFailures),
|
|
516
|
+
"",
|
|
517
|
+
...renderScoreDistribution(report),
|
|
518
|
+
"## Results",
|
|
519
|
+
""
|
|
520
|
+
];
|
|
521
|
+
if (report.failures.length > 0) {
|
|
522
|
+
lines.push("### Failures", "");
|
|
523
|
+
failures.forEach((testCase, index) => {
|
|
524
|
+
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
525
|
+
});
|
|
526
|
+
if (report.failures.length > failures.length) {
|
|
527
|
+
lines.push(
|
|
528
|
+
`${report.failures.length - failures.length} more failures omitted from this summary.`,
|
|
529
|
+
""
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
} else if (report.totals.evalTotal > 0) {
|
|
533
|
+
lines.push("### Failures", "", "No eval failures.", "");
|
|
534
|
+
}
|
|
535
|
+
if (report.totals.evalTotal === 0) {
|
|
536
|
+
lines.push("No eval metadata was found in the Vitest JSON report.", "");
|
|
537
|
+
}
|
|
538
|
+
return `${lines.join("\n")}
|
|
539
|
+
`;
|
|
540
|
+
}
|
|
541
|
+
function formatCountLine(passed, failed, total) {
|
|
542
|
+
return `${formatNumber(passed)} passed, ${formatNumber(failed)} failed, ${formatNumber(total)} total`;
|
|
543
|
+
}
|
|
544
|
+
function renderSummaryTable(report, nonEvalFailures) {
|
|
545
|
+
const rows = [
|
|
546
|
+
["Status", report.status],
|
|
547
|
+
[
|
|
548
|
+
"Tests",
|
|
549
|
+
formatCountLine(
|
|
550
|
+
report.totals.passed,
|
|
551
|
+
report.totals.failed,
|
|
552
|
+
report.totals.total
|
|
553
|
+
)
|
|
554
|
+
],
|
|
555
|
+
[
|
|
556
|
+
"Evals",
|
|
557
|
+
formatCountLine(
|
|
558
|
+
report.totals.evalPassed,
|
|
559
|
+
report.totals.evalFailed,
|
|
560
|
+
report.totals.evalTotal
|
|
561
|
+
)
|
|
562
|
+
]
|
|
563
|
+
];
|
|
564
|
+
if (report.score) {
|
|
565
|
+
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
566
|
+
}
|
|
567
|
+
const usage2 = formatUsage(report.usage);
|
|
568
|
+
if (usage2) {
|
|
569
|
+
rows.push(["Usage", usage2]);
|
|
570
|
+
}
|
|
571
|
+
if (nonEvalFailures > 0) {
|
|
572
|
+
rows.push([
|
|
573
|
+
"Other Failures",
|
|
574
|
+
`${formatNumber(nonEvalFailures)} non-eval test failure${nonEvalFailures === 1 ? "" : "s"}`
|
|
575
|
+
]);
|
|
576
|
+
}
|
|
577
|
+
rows.push(["Duration", formatDuration(report.durationMs)]);
|
|
578
|
+
return [
|
|
579
|
+
"| Metric | Value |",
|
|
580
|
+
"| --- | --- |",
|
|
581
|
+
...rows.map(
|
|
582
|
+
([metric, value]) => `| ${escapeTableCell(metric)} | ${escapeTableCell(value)} |`
|
|
583
|
+
)
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
function formatScoreSummary(score) {
|
|
587
|
+
return `avg ${formatScore(score.average)}${score.minimum === void 0 ? "" : `, min ${formatScore(score.minimum)}`}`;
|
|
588
|
+
}
|
|
589
|
+
function escapeTableCell(value) {
|
|
590
|
+
return value.replace(/\r?\n/g, " ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
591
|
+
}
|
|
592
|
+
function renderScoreDistribution(report) {
|
|
593
|
+
const scores = report.cases.map((testCase) => testCase.eval?.avgScore).filter(
|
|
594
|
+
(score) => typeof score === "number" && Number.isFinite(score)
|
|
595
|
+
);
|
|
596
|
+
if (scores.length === 0) {
|
|
597
|
+
return [];
|
|
598
|
+
}
|
|
599
|
+
const counts = SCORE_DISTRIBUTION_BUCKETS.map(() => 0);
|
|
600
|
+
for (const score of scores) {
|
|
601
|
+
const bucket = Math.min(
|
|
602
|
+
SCORE_DISTRIBUTION_BUCKETS.length - 1,
|
|
603
|
+
Math.max(0, Math.floor(score * SCORE_DISTRIBUTION_BUCKETS.length))
|
|
604
|
+
);
|
|
605
|
+
counts[bucket] = (counts[bucket] ?? 0) + 1;
|
|
606
|
+
}
|
|
607
|
+
const maxCount = Math.max(...counts);
|
|
608
|
+
return [
|
|
609
|
+
"Score distribution",
|
|
610
|
+
"",
|
|
611
|
+
"```text",
|
|
612
|
+
...SCORE_DISTRIBUTION_BUCKETS.map(
|
|
613
|
+
(label, index) => formatScoreDistributionBucket(label, counts[index] ?? 0, maxCount)
|
|
614
|
+
),
|
|
615
|
+
"```",
|
|
616
|
+
""
|
|
617
|
+
];
|
|
618
|
+
}
|
|
619
|
+
function formatScoreDistributionBucket(label, count, maxCount) {
|
|
620
|
+
const barLength = count === 0 ? 0 : Math.max(
|
|
621
|
+
1,
|
|
622
|
+
Math.round(count / maxCount * SCORE_DISTRIBUTION_BAR_WIDTH)
|
|
623
|
+
);
|
|
624
|
+
const bar = "#".repeat(barLength).padEnd(SCORE_DISTRIBUTION_BAR_WIDTH, " ");
|
|
625
|
+
return `${label.padEnd(7)} | ${bar} ${formatNumber(count)}`;
|
|
626
|
+
}
|
|
627
|
+
function renderFailureDetails(testCase, number, options) {
|
|
628
|
+
const failure = testCase.primaryFailure;
|
|
629
|
+
const maxReasonChars = options.maxReasonChars ?? DEFAULT_MAX_REASON_CHARS;
|
|
630
|
+
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
631
|
+
const maxToolCalls = options.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
632
|
+
const usage2 = formatCaseUsage(testCase);
|
|
633
|
+
const finalOutput = testCase.eval?.output ?? testCase.harness?.output;
|
|
634
|
+
const summary = `${number}. ${testCase.displayName} - ${failure?.judgeName ?? "failure"} - ${formatScore(failure?.score ?? testCase.eval?.avgScore)}`;
|
|
635
|
+
const lines = [
|
|
636
|
+
"<details>",
|
|
637
|
+
`<summary>${escapeHtml(summary)}</summary>`,
|
|
638
|
+
"",
|
|
639
|
+
"```text",
|
|
640
|
+
...renderFailureBlock(testCase, {
|
|
641
|
+
finalOutput,
|
|
642
|
+
maxOutputChars,
|
|
643
|
+
maxReasonChars,
|
|
644
|
+
maxToolCalls,
|
|
645
|
+
number,
|
|
646
|
+
usage: usage2
|
|
647
|
+
}).map(escapeFence),
|
|
648
|
+
"```",
|
|
649
|
+
"",
|
|
650
|
+
"</details>"
|
|
651
|
+
];
|
|
652
|
+
return lines;
|
|
653
|
+
}
|
|
654
|
+
function renderFailureBlock(testCase, {
|
|
655
|
+
finalOutput,
|
|
656
|
+
maxOutputChars,
|
|
657
|
+
maxReasonChars,
|
|
658
|
+
maxToolCalls,
|
|
659
|
+
number,
|
|
660
|
+
usage: usage2
|
|
661
|
+
}) {
|
|
662
|
+
const failure = testCase.primaryFailure;
|
|
663
|
+
const overviewRows = [
|
|
664
|
+
["Case", `${number}. ${testCase.displayName}`],
|
|
665
|
+
["Status", testCase.status],
|
|
666
|
+
["Location", formatLocation(testCase.displayFile, testCase.location)],
|
|
667
|
+
["Harness", testCase.harness?.name ?? "n/a"],
|
|
668
|
+
["Score", formatScore(failure?.score ?? testCase.eval?.avgScore)],
|
|
669
|
+
["Judge", failure?.judgeName ?? "n/a"]
|
|
670
|
+
];
|
|
671
|
+
if (usage2) {
|
|
672
|
+
overviewRows.push(["Usage", usage2]);
|
|
673
|
+
}
|
|
674
|
+
if (testCase.durationMs !== void 0) {
|
|
675
|
+
overviewRows.push(["Duration", formatDuration(testCase.durationMs)]);
|
|
676
|
+
}
|
|
677
|
+
const lines = [
|
|
678
|
+
...renderAsciiSection("Result", renderKeyValues(overviewRows)),
|
|
679
|
+
""
|
|
680
|
+
];
|
|
681
|
+
if (failure?.reason) {
|
|
682
|
+
lines.push(
|
|
683
|
+
...renderAsciiSection(
|
|
684
|
+
"Reason",
|
|
685
|
+
truncate(failure.reason, maxReasonChars).split(/\r?\n/)
|
|
686
|
+
),
|
|
687
|
+
""
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
if (testCase.eval?.scores.length) {
|
|
691
|
+
lines.push(
|
|
692
|
+
...renderAsciiTable(
|
|
693
|
+
["Judge", "Score"],
|
|
694
|
+
testCase.eval.scores.map((score) => [
|
|
695
|
+
score.name ?? "Unknown",
|
|
696
|
+
formatScore(score.score)
|
|
697
|
+
])
|
|
698
|
+
),
|
|
699
|
+
""
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
if (finalOutput !== void 0) {
|
|
703
|
+
lines.push(
|
|
704
|
+
...renderAsciiSection(
|
|
705
|
+
"Final Output",
|
|
706
|
+
stringifyValue(finalOutput, maxOutputChars).split(/\r?\n/)
|
|
707
|
+
),
|
|
708
|
+
""
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
if (testCase.harness?.toolCalls.length) {
|
|
712
|
+
const toolCalls = testCase.harness.toolCalls.slice(0, maxToolCalls);
|
|
713
|
+
lines.push(
|
|
714
|
+
...renderAsciiTable(
|
|
715
|
+
["Tool", "Status", "Duration"],
|
|
716
|
+
toolCalls.map((toolCall) => [
|
|
717
|
+
toolCall.name,
|
|
718
|
+
toolCall.error ? `error: ${compactLine(toolCall.error, 120)}` : "ok",
|
|
719
|
+
toolCall.durationMs === void 0 ? "n/a" : formatDuration(toolCall.durationMs)
|
|
720
|
+
])
|
|
721
|
+
)
|
|
722
|
+
);
|
|
723
|
+
if (testCase.harness.toolCalls.length > maxToolCalls) {
|
|
724
|
+
lines.push(
|
|
725
|
+
`${testCase.harness.toolCalls.length - maxToolCalls} more tool calls omitted`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
lines.push("");
|
|
729
|
+
}
|
|
730
|
+
if (testCase.harness?.errors.length) {
|
|
731
|
+
lines.push(
|
|
732
|
+
...renderAsciiSection(
|
|
733
|
+
"Harness Errors",
|
|
734
|
+
stringifyValue(testCase.harness.errors, maxReasonChars).split(/\r?\n/)
|
|
735
|
+
),
|
|
736
|
+
""
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
while (lines[lines.length - 1] === "") {
|
|
740
|
+
lines.pop();
|
|
741
|
+
}
|
|
742
|
+
return lines;
|
|
743
|
+
}
|
|
744
|
+
function renderAsciiSection(title, content) {
|
|
745
|
+
return [title, "-".repeat(title.length), ...content];
|
|
746
|
+
}
|
|
747
|
+
function renderKeyValues(rows) {
|
|
748
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
749
|
+
return rows.map(
|
|
750
|
+
([label, value]) => `${label.padEnd(labelWidth)} ${compactLine(value, 500)}`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
function renderAsciiTable(headers, rows) {
|
|
754
|
+
const widths = headers.map(
|
|
755
|
+
(header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0))
|
|
756
|
+
);
|
|
757
|
+
const renderRow = (row) => row.map((cell, index) => cell.padEnd(widths[index] ?? cell.length)).join(" ").trimEnd();
|
|
758
|
+
return [
|
|
759
|
+
renderRow(headers),
|
|
760
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
761
|
+
...rows.map(renderRow)
|
|
762
|
+
];
|
|
763
|
+
}
|
|
764
|
+
function formatUsage(usage2) {
|
|
765
|
+
const parts = [];
|
|
766
|
+
if (usage2.totalTokens > 0) {
|
|
767
|
+
parts.push(`${formatNumber(usage2.totalTokens)} tokens`);
|
|
768
|
+
}
|
|
769
|
+
if (usage2.toolCalls > 0) {
|
|
770
|
+
parts.push(
|
|
771
|
+
`${formatNumber(usage2.toolCalls)} tool${usage2.toolCalls === 1 ? "" : "s"}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
if (usage2.estimatedCost > 0) {
|
|
775
|
+
parts.push(`$${usage2.estimatedCost.toFixed(4)}`);
|
|
776
|
+
}
|
|
777
|
+
return parts.join(", ");
|
|
778
|
+
}
|
|
779
|
+
function formatCaseUsage(testCase) {
|
|
780
|
+
const usage2 = testCase.harness?.usage;
|
|
781
|
+
const parts = [];
|
|
782
|
+
const totalTokens = usage2?.totalTokens ?? (usage2?.inputTokens ?? 0) + (usage2?.outputTokens ?? 0) + (usage2?.reasoningTokens ?? 0);
|
|
783
|
+
const toolCalls = usage2?.toolCalls ?? testCase.harness?.toolCalls.length ?? 0;
|
|
784
|
+
if (totalTokens > 0) {
|
|
785
|
+
parts.push(`${formatNumber(totalTokens)} tokens`);
|
|
786
|
+
}
|
|
787
|
+
if (toolCalls > 0) {
|
|
788
|
+
parts.push(`${formatNumber(toolCalls)} tool${toolCalls === 1 ? "" : "s"}`);
|
|
789
|
+
}
|
|
790
|
+
if (testCase.harness?.timingMs !== void 0) {
|
|
791
|
+
parts.push(formatDuration(testCase.harness.timingMs));
|
|
792
|
+
}
|
|
793
|
+
return parts.join(", ");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/github.ts
|
|
797
|
+
var DEFAULT_CHECK_NAME = "vitest-evals";
|
|
798
|
+
var MAX_CHECK_SUMMARY_LENGTH = 64e3;
|
|
799
|
+
var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
|
|
800
|
+
async function publishCheckRun(report, options = {}) {
|
|
801
|
+
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
802
|
+
const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
|
|
803
|
+
const sha = options.sha ?? process.env.GITHUB_SHA;
|
|
804
|
+
if (!token) {
|
|
805
|
+
return { status: "skipped", reason: "missing GITHUB_TOKEN" };
|
|
806
|
+
}
|
|
807
|
+
if (!repository) {
|
|
808
|
+
return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
|
|
809
|
+
}
|
|
810
|
+
if (!sha && options.checkRunId === void 0) {
|
|
811
|
+
return { status: "skipped", reason: "missing GITHUB_SHA" };
|
|
812
|
+
}
|
|
813
|
+
const [owner, repo] = repository.split("/");
|
|
814
|
+
if (!owner || !repo) {
|
|
815
|
+
return {
|
|
816
|
+
status: "skipped",
|
|
817
|
+
reason: `invalid GitHub repository: ${repository}`
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const payload = buildCheckRunPayload(report, options);
|
|
821
|
+
const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
822
|
+
const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
|
|
823
|
+
const response = await fetch(requestUrl, {
|
|
824
|
+
method: options.checkRunId === void 0 ? "POST" : "PATCH",
|
|
825
|
+
headers: {
|
|
826
|
+
accept: "application/vnd.github+json",
|
|
827
|
+
authorization: `Bearer ${token}`,
|
|
828
|
+
"content-type": "application/json",
|
|
829
|
+
"x-github-api-version": "2022-11-28"
|
|
830
|
+
},
|
|
831
|
+
body: JSON.stringify(
|
|
832
|
+
options.checkRunId === void 0 ? {
|
|
833
|
+
name: options.name ?? DEFAULT_CHECK_NAME,
|
|
834
|
+
head_sha: sha,
|
|
835
|
+
...payload
|
|
836
|
+
} : payload
|
|
837
|
+
)
|
|
838
|
+
});
|
|
839
|
+
if (!response.ok) {
|
|
840
|
+
const text = await response.text();
|
|
841
|
+
throw new Error(
|
|
842
|
+
`GitHub Check Run request failed: ${response.status} ${response.statusText} ${text}`.trim()
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const data = await response.json();
|
|
846
|
+
return {
|
|
847
|
+
status: options.checkRunId === void 0 ? "created" : "updated",
|
|
848
|
+
id: data.id,
|
|
849
|
+
htmlUrl: data.html_url
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
function buildCheckRunPayload(report, options) {
|
|
853
|
+
const annotations = buildCheckAnnotations(report, {
|
|
854
|
+
maxAnnotations: options.maxAnnotations
|
|
855
|
+
});
|
|
856
|
+
const title = report.failures.length === 0 && report.status === "passed" ? "No eval failures" : report.failures.length === 0 ? "Vitest run failed" : `${report.failures.length} eval failure${report.failures.length === 1 ? "" : "s"}`;
|
|
857
|
+
return {
|
|
858
|
+
status: "completed",
|
|
859
|
+
conclusion: report.status === "passed" ? "success" : "failure",
|
|
860
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
861
|
+
output: {
|
|
862
|
+
title,
|
|
863
|
+
summary: truncateCheckSummary(
|
|
864
|
+
renderJobSummary(report, {
|
|
865
|
+
...options,
|
|
866
|
+
maxFailures: options.maxFailures ?? 5,
|
|
867
|
+
maxReasonChars: options.maxReasonChars ?? 4e3,
|
|
868
|
+
maxOutputChars: options.maxOutputChars ?? 2e3,
|
|
869
|
+
maxToolCalls: options.maxToolCalls ?? 10
|
|
870
|
+
})
|
|
871
|
+
),
|
|
872
|
+
annotations
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function truncateCheckSummary(summary) {
|
|
877
|
+
if (summary.length <= MAX_CHECK_SUMMARY_LENGTH) {
|
|
878
|
+
return summary;
|
|
879
|
+
}
|
|
880
|
+
return `${summary.slice(0, MAX_CHECK_SUMMARY_LENGTH - CHECK_SUMMARY_TRUNCATION_SUFFIX.length).trimEnd()}${CHECK_SUMMARY_TRUNCATION_SUFFIX}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/cli.ts
|
|
884
|
+
main().catch((error) => {
|
|
885
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
886
|
+
process.exitCode = 1;
|
|
887
|
+
});
|
|
888
|
+
async function main() {
|
|
889
|
+
const options = parseCliArgs(process.argv.slice(2));
|
|
890
|
+
if (options.help) {
|
|
891
|
+
console.log(usage());
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
const json = JSON.parse(
|
|
895
|
+
await (0, import_promises.readFile)(options.jsonPath, "utf8")
|
|
896
|
+
);
|
|
897
|
+
const report = collectEvalReport(json, {
|
|
898
|
+
workspace: options.workspace ?? process.env.GITHUB_WORKSPACE ?? process.cwd()
|
|
899
|
+
});
|
|
900
|
+
const summary = renderJobSummary(report, {
|
|
901
|
+
maxFailures: options.maxFailures
|
|
902
|
+
});
|
|
903
|
+
if (options.summaryEnabled) {
|
|
904
|
+
if (options.summaryPath) {
|
|
905
|
+
await (0, import_promises.appendFile)(options.summaryPath, `${summary}
|
|
906
|
+
`);
|
|
907
|
+
} else {
|
|
908
|
+
console.log(summary);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (options.annotations) {
|
|
912
|
+
for (const command of renderWorkflowCommands(report, {
|
|
913
|
+
maxAnnotations: options.maxAnnotations
|
|
914
|
+
})) {
|
|
915
|
+
console.log(command);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (options.checkRun) {
|
|
919
|
+
try {
|
|
920
|
+
const result = await publishCheckRun(report, {
|
|
921
|
+
checkRunId: options.checkRunId,
|
|
922
|
+
maxAnnotations: options.maxAnnotations,
|
|
923
|
+
maxFailures: options.maxFailures,
|
|
924
|
+
name: options.checkName,
|
|
925
|
+
repository: options.repository,
|
|
926
|
+
sha: options.sha,
|
|
927
|
+
token: options.token
|
|
928
|
+
});
|
|
929
|
+
if (result.status === "skipped") {
|
|
930
|
+
warn(`GitHub Check Run skipped: ${result.reason}`);
|
|
931
|
+
}
|
|
932
|
+
} catch (error) {
|
|
933
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
934
|
+
if (options.failOnCheckError) {
|
|
935
|
+
throw error;
|
|
936
|
+
}
|
|
937
|
+
warn(message);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function warn(message) {
|
|
942
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
943
|
+
console.log(`::warning::${escapeCommandData(message)}`);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
console.error(`Warning: ${message}`);
|
|
947
|
+
}
|
|
948
|
+
function usage() {
|
|
949
|
+
return [
|
|
950
|
+
"Usage: vitest-evals-github-report [vitest-results.json] [--json <path>]",
|
|
951
|
+
"",
|
|
952
|
+
"Options:",
|
|
953
|
+
" --json <path> Read Vitest JSON report from this path",
|
|
954
|
+
" --summary <path> Write job summary markdown to this path",
|
|
955
|
+
" --no-summary Disable summary output",
|
|
956
|
+
" --annotations Emit GitHub workflow-command annotations",
|
|
957
|
+
" --no-annotations Disable workflow-command annotations",
|
|
958
|
+
" --check-run Publish a GitHub Check Run when configured",
|
|
959
|
+
" --fail-on-check-error Fail when Check Run publishing fails",
|
|
960
|
+
" --check-run-id <id> Update an existing Check Run",
|
|
961
|
+
" --check-name <name> Check Run name (default: vitest-evals)",
|
|
962
|
+
" --token <token> GitHub token (default: GITHUB_TOKEN)",
|
|
963
|
+
" --repo <owner/repo> GitHub repository (default: GITHUB_REPOSITORY)",
|
|
964
|
+
" --sha <sha> Git commit SHA (default: GITHUB_SHA)",
|
|
965
|
+
" --workspace <path> Workspace path for relative annotation files",
|
|
966
|
+
" --max-annotations <n> Maximum annotations to emit",
|
|
967
|
+
" --max-failures <n> Maximum failures to include in details"
|
|
968
|
+
].join("\n");
|
|
969
|
+
}
|
|
970
|
+
//# sourceMappingURL=cli.js.map
|