@axlsdk/eval 0.16.1 → 0.17.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/README.md +18 -2
- package/dist/{chunk-CV6VE5VT.js → chunk-NPKIP4SB.js} +37 -9
- package/dist/chunk-NPKIP4SB.js.map +1 -0
- package/dist/cli.cjs +161 -93
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +124 -74
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +36 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-CV6VE5VT.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
27
|
+
var import_node_fs = require("fs");
|
|
28
28
|
var import_promises = require("fs/promises");
|
|
29
29
|
var path = __toESM(require("path"), 1);
|
|
30
30
|
|
|
@@ -114,13 +114,16 @@ function resolveThreshold(scorerName, options, metadata) {
|
|
|
114
114
|
return LEGACY_THRESHOLD;
|
|
115
115
|
}
|
|
116
116
|
function evalCompare(baseline, candidate, options) {
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
117
|
+
const baselineRunsRaw = Array.isArray(baseline) ? baseline : [baseline];
|
|
118
|
+
const candidateRunsRaw = Array.isArray(candidate) ? candidate : [candidate];
|
|
119
|
+
if (baselineRunsRaw.length === 0 || candidateRunsRaw.length === 0) {
|
|
120
120
|
throw new Error("Cannot compare empty eval result arrays");
|
|
121
121
|
}
|
|
122
|
-
const
|
|
123
|
-
const
|
|
122
|
+
const runCount = Math.min(baselineRunsRaw.length, candidateRunsRaw.length);
|
|
123
|
+
const baselineRuns = baselineRunsRaw.slice(0, runCount);
|
|
124
|
+
const candidateRuns = candidateRunsRaw.slice(0, runCount);
|
|
125
|
+
const baselineRef = baselineRunsRaw[0];
|
|
126
|
+
const candidateRef = candidateRunsRaw[0];
|
|
124
127
|
if (baselineRef.dataset !== candidateRef.dataset) {
|
|
125
128
|
throw new Error(
|
|
126
129
|
`Cannot compare evals from different datasets: "${baselineRef.dataset}" vs "${candidateRef.dataset}"`
|
|
@@ -135,7 +138,6 @@ function evalCompare(baseline, candidate, options) {
|
|
|
135
138
|
for (const name of baselineScorerNames) {
|
|
136
139
|
pairedDiffs[name] = [];
|
|
137
140
|
}
|
|
138
|
-
const runCount = Math.min(baselineRuns.length, candidateRuns.length);
|
|
139
141
|
for (let r = 0; r < runCount; r++) {
|
|
140
142
|
const bRun = baselineRuns[r];
|
|
141
143
|
const cRun = candidateRuns[r];
|
|
@@ -272,9 +274,21 @@ function evalCompare(baseline, candidate, options) {
|
|
|
272
274
|
parts.push(`${Math.abs(cost.deltaPercent).toFixed(0)}% ${dir}`);
|
|
273
275
|
}
|
|
274
276
|
const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no meaningful changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
|
|
277
|
+
const baselinePartial = detectPartial(baselineRunsRaw);
|
|
278
|
+
const candidatePartial = detectPartial(candidateRunsRaw);
|
|
275
279
|
return {
|
|
276
|
-
baseline: {
|
|
277
|
-
|
|
280
|
+
baseline: {
|
|
281
|
+
id: baselineRef.id,
|
|
282
|
+
metadata: baselineRef.metadata,
|
|
283
|
+
runCount,
|
|
284
|
+
...baselinePartial ? { partial: baselinePartial } : {}
|
|
285
|
+
},
|
|
286
|
+
candidate: {
|
|
287
|
+
id: candidateRef.id,
|
|
288
|
+
metadata: candidateRef.metadata,
|
|
289
|
+
runCount,
|
|
290
|
+
...candidatePartial ? { partial: candidatePartial } : {}
|
|
291
|
+
},
|
|
278
292
|
scorers,
|
|
279
293
|
timing,
|
|
280
294
|
cost,
|
|
@@ -283,6 +297,20 @@ function evalCompare(baseline, candidate, options) {
|
|
|
283
297
|
summary: summaryStr
|
|
284
298
|
};
|
|
285
299
|
}
|
|
300
|
+
function detectPartial(runs) {
|
|
301
|
+
if (runs.length === 0) return void 0;
|
|
302
|
+
let attempted;
|
|
303
|
+
for (const r of runs) {
|
|
304
|
+
const v = r.metadata?.batchAttempted;
|
|
305
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
306
|
+
attempted = v;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (attempted === void 0) return void 0;
|
|
311
|
+
if (runs.length >= attempted) return void 0;
|
|
312
|
+
return { completed: runs.length, attempted };
|
|
313
|
+
}
|
|
286
314
|
|
|
287
315
|
// src/scorer.ts
|
|
288
316
|
function normalizeScorerResult(result) {
|
|
@@ -786,66 +814,43 @@ function aggregateRuns(runs) {
|
|
|
786
814
|
}
|
|
787
815
|
|
|
788
816
|
// src/cli-utils.ts
|
|
789
|
-
var
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
"
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
];
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
817
|
+
var import_axl = require("@axlsdk/axl");
|
|
818
|
+
|
|
819
|
+
// src/cli-validate.ts
|
|
820
|
+
function validateEvalConfig(cfg) {
|
|
821
|
+
if (!cfg || typeof cfg !== "object") {
|
|
822
|
+
return `does not export a valid eval config \u2014 got ${cfg === null ? "null" : typeof cfg}.`;
|
|
823
|
+
}
|
|
824
|
+
const c = cfg;
|
|
825
|
+
const missing = [];
|
|
826
|
+
if (!c.workflow) missing.push("workflow");
|
|
827
|
+
if (!c.dataset) missing.push("dataset");
|
|
828
|
+
if (!c.scorers) missing.push("scorers");
|
|
829
|
+
if (missing.length > 0) {
|
|
830
|
+
const keys = Object.keys(c).slice(0, 10);
|
|
831
|
+
const got = keys.length > 0 ? ` Got: { ${keys.join(", ")} }.` : "";
|
|
832
|
+
return `does not export a valid eval config (missing ${missing.join(", ")}).${got}`;
|
|
833
|
+
}
|
|
834
|
+
if (typeof c.workflow !== "string") {
|
|
835
|
+
return `exports a non-string workflow (got ${typeof c.workflow}) \u2014 workflow must be the registered workflow name.`;
|
|
836
|
+
}
|
|
837
|
+
if (typeof c.dataset !== "object" || Array.isArray(c.dataset)) {
|
|
838
|
+
return `exports a non-object dataset (got ${Array.isArray(c.dataset) ? "array" : typeof c.dataset}) \u2014 use the dataset() factory.`;
|
|
839
|
+
}
|
|
840
|
+
if (typeof c.dataset.getItems !== "function") {
|
|
841
|
+
return `exports a dataset without a getItems() method \u2014 use the dataset() factory.`;
|
|
842
|
+
}
|
|
843
|
+
if (!Array.isArray(c.scorers) || c.scorers.length === 0) {
|
|
844
|
+
return `exports an empty scorers array \u2014 at least one scorer is required.`;
|
|
845
|
+
}
|
|
846
|
+
const badScorerIdx = c.scorers.findIndex(
|
|
847
|
+
(s) => !s || typeof s.score !== "function"
|
|
848
|
+
);
|
|
849
|
+
if (badScorerIdx !== -1) {
|
|
850
|
+
return `exports a scorers array with a non-scorer entry at index ${badScorerIdx} \u2014 each scorer must have a score() method (use the scorer() factory).`;
|
|
802
851
|
}
|
|
803
852
|
return void 0;
|
|
804
853
|
}
|
|
805
|
-
function needsTsxLoader(filePath) {
|
|
806
|
-
return /\.[mc]?tsx?$/.test(filePath);
|
|
807
|
-
}
|
|
808
|
-
function resolveRuntime(mod) {
|
|
809
|
-
const def = mod.default;
|
|
810
|
-
return def?.default ?? def ?? mod.runtime;
|
|
811
|
-
}
|
|
812
|
-
var tsImportFn;
|
|
813
|
-
async function importModule(filePath, parentURL) {
|
|
814
|
-
if (needsTsxLoader(filePath)) {
|
|
815
|
-
if (tsImportFn === void 0) {
|
|
816
|
-
try {
|
|
817
|
-
const mod = await import("tsx/esm/api");
|
|
818
|
-
tsImportFn = mod.tsImport ?? null;
|
|
819
|
-
} catch {
|
|
820
|
-
tsImportFn = null;
|
|
821
|
-
console.warn(
|
|
822
|
-
"[axl-eval] Warning: tsx is not installed. TypeScript files require tsx.\n Install it with: npm install -D tsx"
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
if (tsImportFn) {
|
|
827
|
-
return await tsImportFn((0, import_node_url.pathToFileURL)(filePath).href, parentURL);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
return await import((0, import_node_url.pathToFileURL)(filePath).href);
|
|
831
|
-
}
|
|
832
|
-
async function registerConditions(conditions) {
|
|
833
|
-
try {
|
|
834
|
-
const nodeModule = await import("module");
|
|
835
|
-
const hookCode = [
|
|
836
|
-
`const extra = ${JSON.stringify(conditions)};`,
|
|
837
|
-
`export async function resolve(specifier, context, nextResolve) {`,
|
|
838
|
-
` return nextResolve(specifier, {`,
|
|
839
|
-
` ...context,`,
|
|
840
|
-
` conditions: [...new Set([...context.conditions, ...extra])],`,
|
|
841
|
-
` });`,
|
|
842
|
-
`}`
|
|
843
|
-
].join("\n");
|
|
844
|
-
nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
|
|
845
|
-
} catch {
|
|
846
|
-
console.warn("[axl-eval] Warning: --conditions requires Node.js 20.6+");
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
854
|
|
|
850
855
|
// src/cli.ts
|
|
851
856
|
var import_meta = {};
|
|
@@ -878,7 +883,7 @@ Usage:
|
|
|
878
883
|
axl-eval compare <a> <b> --fail-on-regression Exit 1 if regressions
|
|
879
884
|
|
|
880
885
|
Config auto-detection (when --config is not specified):
|
|
881
|
-
${CONFIG_CANDIDATES.join(" -> ")}
|
|
886
|
+
${import_axl.CONFIG_CANDIDATES.join(" -> ")}
|
|
882
887
|
|
|
883
888
|
When a config is found, the exported AxlRuntime is passed to executeWorkflow
|
|
884
889
|
and cost is tracked automatically via runtime.trackCost().
|
|
@@ -1000,10 +1005,16 @@ async function runRescore(args) {
|
|
|
1000
1005
|
const results = Array.isArray(raw) ? raw : [raw];
|
|
1001
1006
|
const runtime = await getRuntime(configArg, conditions);
|
|
1002
1007
|
try {
|
|
1003
|
-
const mod = await importModule(path.resolve(evalFilePath), import_meta.url);
|
|
1004
|
-
const evalConfig =
|
|
1005
|
-
if (!evalConfig
|
|
1006
|
-
console.error(
|
|
1008
|
+
const mod = await (0, import_axl.importModule)(path.resolve(evalFilePath), import_meta.url);
|
|
1009
|
+
const evalConfig = (0, import_axl.pickDefault)(mod);
|
|
1010
|
+
if (!evalConfig || typeof evalConfig !== "object") {
|
|
1011
|
+
console.error(
|
|
1012
|
+
`Error: ${evalFilePath} does not export an eval config object \u2014 got ${evalConfig === null ? "null" : typeof evalConfig}.`
|
|
1013
|
+
);
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
if (!Array.isArray(evalConfig.scorers) || evalConfig.scorers.length === 0) {
|
|
1017
|
+
console.error(`Error: ${evalFilePath} does not export a non-empty scorers array.`);
|
|
1007
1018
|
process.exit(1);
|
|
1008
1019
|
}
|
|
1009
1020
|
const rescored = [];
|
|
@@ -1026,11 +1037,12 @@ async function runRescore(args) {
|
|
|
1026
1037
|
}
|
|
1027
1038
|
}
|
|
1028
1039
|
function collectEvalFiles(p) {
|
|
1040
|
+
if (p.includes("*")) return (0, import_axl.expandGlob)(p, process.cwd());
|
|
1029
1041
|
const resolved = path.resolve(p);
|
|
1030
1042
|
try {
|
|
1031
|
-
const stat = (0,
|
|
1043
|
+
const stat = (0, import_node_fs.statSync)(resolved);
|
|
1032
1044
|
if (stat.isDirectory()) {
|
|
1033
|
-
const entries = (0,
|
|
1045
|
+
const entries = (0, import_node_fs.readdirSync)(resolved);
|
|
1034
1046
|
return entries.filter((e) => /\.eval\.[mc]?[jt]sx?$/.test(e)).map((e) => path.join(resolved, e));
|
|
1035
1047
|
}
|
|
1036
1048
|
return [resolved];
|
|
@@ -1129,8 +1141,8 @@ function formatMultiRunTable(summary) {
|
|
|
1129
1141
|
}
|
|
1130
1142
|
async function resolveRuntimeFromConfig(configPath) {
|
|
1131
1143
|
try {
|
|
1132
|
-
const mod = await importModule(configPath, import_meta.url);
|
|
1133
|
-
const runtime = resolveRuntime(mod);
|
|
1144
|
+
const mod = await (0, import_axl.importModule)(configPath, import_meta.url);
|
|
1145
|
+
const runtime = (0, import_axl.resolveRuntime)(mod);
|
|
1134
1146
|
if (!runtime || typeof runtime.execute !== "function") {
|
|
1135
1147
|
console.error(`Config must export a default AxlRuntime instance.`);
|
|
1136
1148
|
if (runtime) {
|
|
@@ -1167,11 +1179,11 @@ async function resolveRuntimeFromConfig(configPath) {
|
|
|
1167
1179
|
}
|
|
1168
1180
|
async function getRuntime(configArg, conditions) {
|
|
1169
1181
|
if (conditions && conditions.length > 0) {
|
|
1170
|
-
await registerConditions(conditions);
|
|
1182
|
+
await (0, import_axl.registerConditions)(conditions);
|
|
1171
1183
|
}
|
|
1172
1184
|
if (configArg) {
|
|
1173
1185
|
const configPath = path.resolve(process.cwd(), configArg);
|
|
1174
|
-
const stat = (0,
|
|
1186
|
+
const stat = (0, import_node_fs.statSync)(configPath, { throwIfNoEntry: false });
|
|
1175
1187
|
if (!stat?.isFile()) {
|
|
1176
1188
|
console.error(`Config file not found: ${configPath}`);
|
|
1177
1189
|
process.exit(1);
|
|
@@ -1179,7 +1191,7 @@ async function getRuntime(configArg, conditions) {
|
|
|
1179
1191
|
console.error(`[axl-eval] Loading config from ${configPath}`);
|
|
1180
1192
|
return resolveRuntimeFromConfig(configPath);
|
|
1181
1193
|
}
|
|
1182
|
-
const found = findConfig(process.cwd());
|
|
1194
|
+
const found = (0, import_axl.findConfig)(process.cwd());
|
|
1183
1195
|
if (found) {
|
|
1184
1196
|
console.error(`[axl-eval] Auto-detected config: ${found}`);
|
|
1185
1197
|
return resolveRuntimeFromConfig(found);
|
|
@@ -1236,26 +1248,38 @@ async function runEvalCommand(args) {
|
|
|
1236
1248
|
}
|
|
1237
1249
|
const runtime = await getRuntime(configArg, conditions);
|
|
1238
1250
|
const results = [];
|
|
1251
|
+
let failedFiles = 0;
|
|
1239
1252
|
try {
|
|
1240
1253
|
for (const filePath of evalFiles) {
|
|
1241
1254
|
try {
|
|
1242
|
-
const mod = await importModule(path.resolve(filePath), import_meta.url);
|
|
1243
|
-
const evalConfig =
|
|
1244
|
-
|
|
1255
|
+
const mod = await (0, import_axl.importModule)(path.resolve(filePath), import_meta.url);
|
|
1256
|
+
const evalConfig = (0, import_axl.pickDefault)(mod);
|
|
1257
|
+
const validationError = validateEvalConfig(evalConfig);
|
|
1258
|
+
if (validationError) {
|
|
1259
|
+
console.error(`Error: ${filePath} ${validationError}`);
|
|
1260
|
+
failedFiles++;
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
const exported = (0, import_axl.pickExport)(mod, "executeWorkflow");
|
|
1264
|
+
let customExecute;
|
|
1265
|
+
if (typeof exported === "function") {
|
|
1266
|
+
customExecute = exported;
|
|
1267
|
+
} else if (exported !== void 0) {
|
|
1245
1268
|
console.error(
|
|
1246
|
-
`Error: ${filePath}
|
|
1269
|
+
`[axl-eval] Error: ${filePath} exports executeWorkflow but it is ${typeof exported}, not a function.`
|
|
1247
1270
|
);
|
|
1271
|
+
failedFiles++;
|
|
1248
1272
|
continue;
|
|
1249
1273
|
}
|
|
1250
1274
|
let executeWorkflow;
|
|
1251
|
-
if (
|
|
1275
|
+
if (customExecute) {
|
|
1252
1276
|
executeWorkflow = async (input, rt) => {
|
|
1253
1277
|
const {
|
|
1254
1278
|
result,
|
|
1255
1279
|
cost: trackedCost,
|
|
1256
1280
|
metadata
|
|
1257
1281
|
} = await runtime.trackExecution(async () => {
|
|
1258
|
-
return
|
|
1282
|
+
return customExecute(input, rt);
|
|
1259
1283
|
});
|
|
1260
1284
|
return {
|
|
1261
1285
|
output: result.output,
|
|
@@ -1271,26 +1295,69 @@ async function runEvalCommand(args) {
|
|
|
1271
1295
|
return { output: result, cost, metadata };
|
|
1272
1296
|
};
|
|
1273
1297
|
} else {
|
|
1274
|
-
|
|
1275
|
-
|
|
1298
|
+
const exportShape = (() => {
|
|
1299
|
+
const top = Object.keys(mod).filter((k) => k !== "default");
|
|
1300
|
+
const fromDefault = mod.default && typeof mod.default === "object" ? Object.keys(mod.default) : [];
|
|
1301
|
+
const all = [.../* @__PURE__ */ new Set([...top, ...fromDefault])];
|
|
1302
|
+
return all.length > 0 ? `Found exports: [${all.join(", ")}].` : "";
|
|
1303
|
+
})();
|
|
1304
|
+
console.error(
|
|
1305
|
+
`[axl-eval] Error: ${filePath} does not export an executeWorkflow function and no workflow named "${evalConfig.workflow}" is registered on the runtime.
|
|
1306
|
+
` + (exportShape ? ` ${exportShape}
|
|
1307
|
+
` : "") + ` Add either:
|
|
1308
|
+
export async function executeWorkflow(input, runtime) { ... }
|
|
1309
|
+
or register the workflow on your runtime via runtime.registerWorkflow(workflow).
|
|
1310
|
+
Tip: if your eval previously worked, your package.json may be missing
|
|
1311
|
+
"type": "module" \u2014 tsx loads .ts as CJS, putting named exports on
|
|
1312
|
+
mod.default.<name>. The CLI handles that interop, but if you renamed
|
|
1313
|
+
or removed the export the file path is your fix.`
|
|
1276
1314
|
);
|
|
1277
|
-
|
|
1315
|
+
failedFiles++;
|
|
1316
|
+
continue;
|
|
1278
1317
|
}
|
|
1279
1318
|
const runOptions = captureTraces ? { captureTraces: true } : void 0;
|
|
1280
1319
|
if (runs > 1) {
|
|
1281
1320
|
const { randomUUID: randomUUID4 } = await import("crypto");
|
|
1282
1321
|
const runGroupId = randomUUID4();
|
|
1283
1322
|
const runResults = [];
|
|
1323
|
+
let runFailure;
|
|
1284
1324
|
for (let r = 0; r < runs; r++) {
|
|
1285
1325
|
console.error(`[axl-eval] Run ${r + 1}/${runs}...`);
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1326
|
+
try {
|
|
1327
|
+
const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);
|
|
1328
|
+
result.metadata.runGroupId = runGroupId;
|
|
1329
|
+
result.metadata.runIndex = r;
|
|
1330
|
+
runResults.push(result);
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
runFailure = err instanceof Error ? err : new Error(String(err));
|
|
1333
|
+
console.error(`[axl-eval] Run ${r + 1}/${runs} failed: ${runFailure.message}`);
|
|
1334
|
+
break;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
if (runResults.length === 0) {
|
|
1338
|
+
console.error(
|
|
1339
|
+
`[axl-eval] Aborting ${filePath}: no runs completed (${runFailure?.message ?? "unknown failure"}).`
|
|
1340
|
+
);
|
|
1341
|
+
failedFiles++;
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
const partial = runResults.length < runs;
|
|
1345
|
+
if (partial) {
|
|
1346
|
+
const failureMsg = runFailure ? runFailure.message || String(runFailure) || void 0 : void 0;
|
|
1347
|
+
for (const r of runResults) {
|
|
1348
|
+
r.metadata.fromPartialBatch = true;
|
|
1349
|
+
r.metadata.batchCompleted = runResults.length;
|
|
1350
|
+
r.metadata.batchAttempted = runs;
|
|
1351
|
+
if (failureMsg) r.metadata.batchFailure = failureMsg;
|
|
1352
|
+
}
|
|
1353
|
+
console.error(
|
|
1354
|
+
`[axl-eval] PARTIAL: ${runResults.length} of ${runs} runs completed for ${filePath}.`
|
|
1355
|
+
);
|
|
1356
|
+
failedFiles++;
|
|
1291
1357
|
}
|
|
1292
1358
|
const summary = aggregateRuns(runResults);
|
|
1293
1359
|
console.log("\n" + formatMultiRunTable(summary) + "\n");
|
|
1360
|
+
for (const r of runResults) results.push(r);
|
|
1294
1361
|
} else {
|
|
1295
1362
|
const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);
|
|
1296
1363
|
results.push(result);
|
|
@@ -1300,6 +1367,7 @@ async function runEvalCommand(args) {
|
|
|
1300
1367
|
console.error(
|
|
1301
1368
|
`Error running eval ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1302
1369
|
);
|
|
1370
|
+
failedFiles++;
|
|
1303
1371
|
}
|
|
1304
1372
|
}
|
|
1305
1373
|
if (outputPath && results.length > 0) {
|
|
@@ -1313,7 +1381,7 @@ async function runEvalCommand(args) {
|
|
|
1313
1381
|
await runtime.shutdown().catch(() => {
|
|
1314
1382
|
});
|
|
1315
1383
|
}
|
|
1316
|
-
if (results.length === 0) {
|
|
1384
|
+
if (failedFiles > 0 || results.length === 0) {
|
|
1317
1385
|
process.exit(1);
|
|
1318
1386
|
}
|
|
1319
1387
|
}
|