@plune-ai/cli 0.26.0 → 0.28.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 +33 -6
- package/dist/cli.cjs +590 -187
- package/dist/index.cjs +272 -137
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +267 -136
- package/dist/reporter-core.cjs +3 -0
- package/dist/reporter-core.d.cts +2 -0
- package/dist/reporter-core.d.ts +2 -0
- package/dist/reporter-core.js +3 -0
- package/package.json +4 -2
package/dist/cli.cjs
CHANGED
|
@@ -622,7 +622,8 @@ function makeAnthropicProvider(config, env, pricing) {
|
|
|
622
622
|
usage: {
|
|
623
623
|
input_tokens: res.usage.input_tokens,
|
|
624
624
|
output_tokens: res.usage.output_tokens
|
|
625
|
-
}
|
|
625
|
+
},
|
|
626
|
+
...res.stop_reason === "max_tokens" ? { finish_reason: "length" } : res.stop_reason !== null && res.stop_reason !== void 0 ? { finish_reason: res.stop_reason } : {}
|
|
626
627
|
};
|
|
627
628
|
} catch (err) {
|
|
628
629
|
throw normalizeProviderError(err, apiKey, ENV_VAR);
|
|
@@ -680,8 +681,11 @@ function makeOpenAiCompatibleProvider(opts) {
|
|
|
680
681
|
{ max_retries: maxRetries }
|
|
681
682
|
);
|
|
682
683
|
const reportedCost = reportsCost ? res.usage?.cost : void 0;
|
|
684
|
+
const choice = res.choices[0];
|
|
685
|
+
const finish = choice?.error !== void 0 ? "error" : choice?.finish_reason;
|
|
683
686
|
return {
|
|
684
|
-
output:
|
|
687
|
+
output: choice?.message?.content ?? "",
|
|
688
|
+
...typeof finish === "string" ? { finish_reason: finish } : {},
|
|
685
689
|
usage: {
|
|
686
690
|
input_tokens: res.usage?.prompt_tokens ?? 0,
|
|
687
691
|
output_tokens: res.usage?.completion_tokens ?? 0
|
|
@@ -891,18 +895,115 @@ var init_embeddings = __esm({
|
|
|
891
895
|
}
|
|
892
896
|
});
|
|
893
897
|
|
|
898
|
+
// src/util/canonical-json.ts
|
|
899
|
+
function normalizeValue(value2) {
|
|
900
|
+
if (value2 === null) return null;
|
|
901
|
+
if (typeof value2 === "boolean") return value2;
|
|
902
|
+
if (typeof value2 === "number") {
|
|
903
|
+
if (!isFinite(value2)) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`canonicalJson: non-finite number rejected: ${String(value2)}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
return value2 === 0 ? 0 : value2;
|
|
909
|
+
}
|
|
910
|
+
if (typeof value2 === "string") {
|
|
911
|
+
return value2.normalize("NFC");
|
|
912
|
+
}
|
|
913
|
+
if (Array.isArray(value2)) {
|
|
914
|
+
return value2.map(normalizeValue);
|
|
915
|
+
}
|
|
916
|
+
if (typeof value2 === "object") {
|
|
917
|
+
const proto = Object.getPrototypeOf(value2);
|
|
918
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
919
|
+
const name = value2.constructor?.name ?? "unknown";
|
|
920
|
+
throw new Error(
|
|
921
|
+
`canonicalJson: unsupported non-plain object (${name}) \u2014 only plain objects, arrays, and primitives are serializable`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
const obj = value2;
|
|
925
|
+
const sorted = {};
|
|
926
|
+
for (const key of Object.keys(obj).sort()) {
|
|
927
|
+
sorted[key] = normalizeValue(obj[key]);
|
|
928
|
+
}
|
|
929
|
+
return sorted;
|
|
930
|
+
}
|
|
931
|
+
throw new Error(
|
|
932
|
+
`canonicalJson: unsupported value type: ${typeof value2}`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
function canonicalJson(value2) {
|
|
936
|
+
return JSON.stringify(normalizeValue(value2));
|
|
937
|
+
}
|
|
938
|
+
var init_canonical_json = __esm({
|
|
939
|
+
"src/util/canonical-json.ts"() {
|
|
940
|
+
"use strict";
|
|
941
|
+
init_cjs_shims();
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// src/util/hash.ts
|
|
946
|
+
function sha256(input) {
|
|
947
|
+
return (0, import_node_crypto.createHash)("sha256").update(input, "utf8").digest("hex");
|
|
948
|
+
}
|
|
949
|
+
function cacheKey(inputs, policyVersion = CACHE_POLICY_VERSION) {
|
|
950
|
+
const { provider, model, temperature, max_tokens, prompt_resolved } = inputs;
|
|
951
|
+
return sha256(
|
|
952
|
+
canonicalJson({ provider, model, temperature, max_tokens, prompt_resolved, policy: policyVersion })
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
function configHash(config) {
|
|
956
|
+
return sha256(canonicalJson(config));
|
|
957
|
+
}
|
|
958
|
+
var import_node_crypto, CACHE_POLICY_VERSION;
|
|
959
|
+
var init_hash = __esm({
|
|
960
|
+
"src/util/hash.ts"() {
|
|
961
|
+
"use strict";
|
|
962
|
+
init_cjs_shims();
|
|
963
|
+
import_node_crypto = require("crypto");
|
|
964
|
+
init_canonical_json();
|
|
965
|
+
CACHE_POLICY_VERSION = 1;
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
|
|
894
969
|
// src/cache/cache.ts
|
|
970
|
+
function isCacheable(res) {
|
|
971
|
+
if (res.output.trim() === "") return false;
|
|
972
|
+
return res.finish_reason === void 0 || !UNCACHEABLE_FINISH.has(res.finish_reason);
|
|
973
|
+
}
|
|
974
|
+
function loadDatabase() {
|
|
975
|
+
try {
|
|
976
|
+
return (0, import_node_module.createRequire)(importMetaUrl)("better-sqlite3");
|
|
977
|
+
} catch {
|
|
978
|
+
if (!missingNoted) {
|
|
979
|
+
missingNoted = true;
|
|
980
|
+
console.error(
|
|
981
|
+
"plune: completion cache disabled (better-sqlite3 is not installed or failed to load). Reinstall with a C++ toolchain and Python available, or ignore: runs work without the cache."
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
return void 0;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
895
987
|
function openCache(file, opts = {}) {
|
|
896
988
|
const now = opts.now ?? Date.now;
|
|
989
|
+
const Database = loadDatabase();
|
|
990
|
+
if (Database === void 0) return new NoOpCache();
|
|
897
991
|
try {
|
|
898
|
-
const db = new
|
|
992
|
+
const db = new Database(file);
|
|
899
993
|
try {
|
|
900
994
|
ensureSchema(db);
|
|
901
995
|
} catch (err) {
|
|
902
996
|
db.close();
|
|
903
997
|
throw err;
|
|
904
998
|
}
|
|
905
|
-
|
|
999
|
+
let stale;
|
|
1000
|
+
try {
|
|
1001
|
+
stale = markPolicy(db, opts.policyVersion ?? CACHE_POLICY_VERSION);
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
db.close();
|
|
1004
|
+
throw err;
|
|
1005
|
+
}
|
|
1006
|
+
return new SqliteCache(db, now, stale);
|
|
906
1007
|
} catch {
|
|
907
1008
|
return new NoOpCache();
|
|
908
1009
|
}
|
|
@@ -917,26 +1018,45 @@ function ensureSchema(db) {
|
|
|
917
1018
|
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run("schema_version", current);
|
|
918
1019
|
}
|
|
919
1020
|
}
|
|
920
|
-
|
|
1021
|
+
function markPolicy(db, version) {
|
|
1022
|
+
const current = String(version);
|
|
1023
|
+
const stored = db.prepare("SELECT value FROM meta WHERE key = 'policy_version'").get()?.value;
|
|
1024
|
+
if (stored === current) return 0;
|
|
1025
|
+
return db.transaction(() => {
|
|
1026
|
+
const { n } = db.prepare("SELECT COUNT(*) AS n FROM completions").get();
|
|
1027
|
+
db.exec("DELETE FROM completions");
|
|
1028
|
+
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run("policy_version", current);
|
|
1029
|
+
return n;
|
|
1030
|
+
})();
|
|
1031
|
+
}
|
|
1032
|
+
var import_node_module, SCHEMA_VERSION, META_DDL, COMPLETIONS_DDL, UNCACHEABLE_FINISH, missingNoted, SqliteCache, NoOpCache;
|
|
921
1033
|
var init_cache = __esm({
|
|
922
1034
|
"src/cache/cache.ts"() {
|
|
923
1035
|
"use strict";
|
|
924
1036
|
init_cjs_shims();
|
|
925
|
-
|
|
1037
|
+
import_node_module = require("module");
|
|
1038
|
+
init_hash();
|
|
926
1039
|
SCHEMA_VERSION = 1;
|
|
927
1040
|
META_DDL = "CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);";
|
|
928
1041
|
COMPLETIONS_DDL = "CREATE TABLE IF NOT EXISTS completions (cache_key TEXT PRIMARY KEY, output TEXT NOT NULL, input_tokens INTEGER NOT NULL, output_tokens INTEGER NOT NULL, created_at INTEGER NOT NULL);";
|
|
1042
|
+
UNCACHEABLE_FINISH = /* @__PURE__ */ new Set(["length", "max_tokens", "error", "content_filter", "pause_turn"]);
|
|
1043
|
+
missingNoted = false;
|
|
929
1044
|
SqliteCache = class {
|
|
930
|
-
constructor(db, now) {
|
|
1045
|
+
constructor(db, now, staleEntries) {
|
|
931
1046
|
this.db = db;
|
|
932
1047
|
this.now = now;
|
|
1048
|
+
this.staleEntries = staleEntries;
|
|
933
1049
|
}
|
|
934
1050
|
db;
|
|
935
1051
|
now;
|
|
1052
|
+
staleEntries;
|
|
936
1053
|
get(key, opts = {}) {
|
|
937
1054
|
try {
|
|
938
1055
|
const row = this.db.prepare("SELECT output, input_tokens, output_tokens, created_at FROM completions WHERE cache_key = ?").get(key);
|
|
939
1056
|
if (row === void 0) return void 0;
|
|
1057
|
+
if (typeof row.output !== "string" || !Number.isFinite(row.input_tokens) || !Number.isFinite(row.output_tokens) || !Number.isFinite(row.created_at)) {
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
940
1060
|
if (opts.maxAgeMs !== void 0 && this.now() - row.created_at > opts.maxAgeMs) {
|
|
941
1061
|
return void 0;
|
|
942
1062
|
}
|
|
@@ -962,6 +1082,12 @@ var init_cache = __esm({
|
|
|
962
1082
|
} catch {
|
|
963
1083
|
}
|
|
964
1084
|
}
|
|
1085
|
+
delete(key) {
|
|
1086
|
+
try {
|
|
1087
|
+
this.db.prepare("DELETE FROM completions WHERE cache_key = ?").run(key);
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
965
1091
|
clear() {
|
|
966
1092
|
try {
|
|
967
1093
|
this.db.exec("DELETE FROM completions");
|
|
@@ -978,6 +1104,8 @@ var init_cache = __esm({
|
|
|
978
1104
|
}
|
|
979
1105
|
set() {
|
|
980
1106
|
}
|
|
1107
|
+
delete() {
|
|
1108
|
+
}
|
|
981
1109
|
clear() {
|
|
982
1110
|
}
|
|
983
1111
|
close() {
|
|
@@ -995,71 +1123,77 @@ var init_cache2 = __esm({
|
|
|
995
1123
|
}
|
|
996
1124
|
});
|
|
997
1125
|
|
|
998
|
-
// src/
|
|
999
|
-
function
|
|
1000
|
-
if (
|
|
1001
|
-
|
|
1002
|
-
if (typeof value2 === "number") {
|
|
1003
|
-
if (!isFinite(value2)) {
|
|
1004
|
-
throw new Error(
|
|
1005
|
-
`canonicalJson: non-finite number rejected: ${String(value2)}`
|
|
1006
|
-
);
|
|
1007
|
-
}
|
|
1008
|
-
return value2 === 0 ? 0 : value2;
|
|
1126
|
+
// src/assertions/json-extract.ts
|
|
1127
|
+
function extractJson(output, mode) {
|
|
1128
|
+
if (mode === "strict") {
|
|
1129
|
+
return tryParse(output.trim());
|
|
1009
1130
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1131
|
+
const fenced = extractFencedBlock(output);
|
|
1132
|
+
if (fenced !== null) {
|
|
1133
|
+
const r = tryParse(fenced);
|
|
1134
|
+
if (r.ok) return r;
|
|
1012
1135
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1136
|
+
for (const run of balancedRuns(output)) {
|
|
1137
|
+
const r = tryParse(run);
|
|
1138
|
+
if (r.ok) return r;
|
|
1015
1139
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
const obj = value2;
|
|
1025
|
-
const sorted = {};
|
|
1026
|
-
for (const key of Object.keys(obj).sort()) {
|
|
1027
|
-
sorted[key] = normalizeValue(obj[key]);
|
|
1028
|
-
}
|
|
1029
|
-
return sorted;
|
|
1140
|
+
return { ok: false };
|
|
1141
|
+
}
|
|
1142
|
+
function tryParse(s) {
|
|
1143
|
+
try {
|
|
1144
|
+
return { ok: true, value: JSON.parse(s) };
|
|
1145
|
+
} catch {
|
|
1146
|
+
return { ok: false };
|
|
1030
1147
|
}
|
|
1031
|
-
throw new Error(
|
|
1032
|
-
`canonicalJson: unsupported value type: ${typeof value2}`
|
|
1033
|
-
);
|
|
1034
1148
|
}
|
|
1035
|
-
function
|
|
1036
|
-
|
|
1149
|
+
function extractFencedBlock(s) {
|
|
1150
|
+
const m = s.match(/```(?:json)?\s*\n?([\s\S]*?)```/i);
|
|
1151
|
+
return m?.[1]?.trim() ?? null;
|
|
1037
1152
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1153
|
+
function* balancedRuns(s) {
|
|
1154
|
+
let i = 0;
|
|
1155
|
+
while (i < s.length) {
|
|
1156
|
+
const rel = s.slice(i).search(/[{[]/);
|
|
1157
|
+
if (rel === -1) return;
|
|
1158
|
+
const start = i + rel;
|
|
1159
|
+
const run = balancedFrom(s, start);
|
|
1160
|
+
if (run === null) {
|
|
1161
|
+
i = start + 1;
|
|
1162
|
+
} else {
|
|
1163
|
+
yield run;
|
|
1164
|
+
i = start + run.length;
|
|
1165
|
+
}
|
|
1042
1166
|
}
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
// src/util/hash.ts
|
|
1046
|
-
function sha256(input) {
|
|
1047
|
-
return (0, import_node_crypto.createHash)("sha256").update(input, "utf8").digest("hex");
|
|
1048
|
-
}
|
|
1049
|
-
function cacheKey(inputs) {
|
|
1050
|
-
const { provider, model, temperature, max_tokens, prompt_resolved } = inputs;
|
|
1051
|
-
return sha256(canonicalJson({ provider, model, temperature, max_tokens, prompt_resolved }));
|
|
1052
1167
|
}
|
|
1053
|
-
function
|
|
1054
|
-
|
|
1168
|
+
function balancedFrom(s, start) {
|
|
1169
|
+
const open = s[start];
|
|
1170
|
+
const close = open === "{" ? "}" : "]";
|
|
1171
|
+
let depth = 0;
|
|
1172
|
+
let inString = false;
|
|
1173
|
+
let escaped = false;
|
|
1174
|
+
for (let i = start; i < s.length; i++) {
|
|
1175
|
+
const ch = s[i];
|
|
1176
|
+
if (inString) {
|
|
1177
|
+
if (escaped) escaped = false;
|
|
1178
|
+
else if (ch === "\\") escaped = true;
|
|
1179
|
+
else if (ch === '"') inString = false;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (ch === '"') {
|
|
1183
|
+
inString = true;
|
|
1184
|
+
} else if (ch === open) {
|
|
1185
|
+
depth++;
|
|
1186
|
+
} else if (ch === close) {
|
|
1187
|
+
depth--;
|
|
1188
|
+
if (depth === 0) return s.slice(start, i + 1);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1055
1192
|
}
|
|
1056
|
-
var
|
|
1057
|
-
|
|
1058
|
-
"src/util/hash.ts"() {
|
|
1193
|
+
var init_json_extract = __esm({
|
|
1194
|
+
"src/assertions/json-extract.ts"() {
|
|
1059
1195
|
"use strict";
|
|
1060
1196
|
init_cjs_shims();
|
|
1061
|
-
import_node_crypto = require("crypto");
|
|
1062
|
-
init_canonical_json();
|
|
1063
1197
|
}
|
|
1064
1198
|
});
|
|
1065
1199
|
|
|
@@ -1157,80 +1291,6 @@ var init_contains = __esm({
|
|
|
1157
1291
|
}
|
|
1158
1292
|
});
|
|
1159
1293
|
|
|
1160
|
-
// src/assertions/json-extract.ts
|
|
1161
|
-
function extractJson(output, mode) {
|
|
1162
|
-
if (mode === "strict") {
|
|
1163
|
-
return tryParse(output.trim());
|
|
1164
|
-
}
|
|
1165
|
-
const fenced = extractFencedBlock(output);
|
|
1166
|
-
if (fenced !== null) {
|
|
1167
|
-
const r = tryParse(fenced);
|
|
1168
|
-
if (r.ok) return r;
|
|
1169
|
-
}
|
|
1170
|
-
for (const run of balancedRuns(output)) {
|
|
1171
|
-
const r = tryParse(run);
|
|
1172
|
-
if (r.ok) return r;
|
|
1173
|
-
}
|
|
1174
|
-
return { ok: false };
|
|
1175
|
-
}
|
|
1176
|
-
function tryParse(s) {
|
|
1177
|
-
try {
|
|
1178
|
-
return { ok: true, value: JSON.parse(s) };
|
|
1179
|
-
} catch {
|
|
1180
|
-
return { ok: false };
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
function extractFencedBlock(s) {
|
|
1184
|
-
const m = s.match(/```(?:json)?\s*\n?([\s\S]*?)```/i);
|
|
1185
|
-
return m?.[1]?.trim() ?? null;
|
|
1186
|
-
}
|
|
1187
|
-
function* balancedRuns(s) {
|
|
1188
|
-
let i = 0;
|
|
1189
|
-
while (i < s.length) {
|
|
1190
|
-
const rel = s.slice(i).search(/[{[]/);
|
|
1191
|
-
if (rel === -1) return;
|
|
1192
|
-
const start = i + rel;
|
|
1193
|
-
const run = balancedFrom(s, start);
|
|
1194
|
-
if (run === null) {
|
|
1195
|
-
i = start + 1;
|
|
1196
|
-
} else {
|
|
1197
|
-
yield run;
|
|
1198
|
-
i = start + run.length;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
function balancedFrom(s, start) {
|
|
1203
|
-
const open = s[start];
|
|
1204
|
-
const close = open === "{" ? "}" : "]";
|
|
1205
|
-
let depth = 0;
|
|
1206
|
-
let inString = false;
|
|
1207
|
-
let escaped = false;
|
|
1208
|
-
for (let i = start; i < s.length; i++) {
|
|
1209
|
-
const ch = s[i];
|
|
1210
|
-
if (inString) {
|
|
1211
|
-
if (escaped) escaped = false;
|
|
1212
|
-
else if (ch === "\\") escaped = true;
|
|
1213
|
-
else if (ch === '"') inString = false;
|
|
1214
|
-
continue;
|
|
1215
|
-
}
|
|
1216
|
-
if (ch === '"') {
|
|
1217
|
-
inString = true;
|
|
1218
|
-
} else if (ch === open) {
|
|
1219
|
-
depth++;
|
|
1220
|
-
} else if (ch === close) {
|
|
1221
|
-
depth--;
|
|
1222
|
-
if (depth === 0) return s.slice(start, i + 1);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
return null;
|
|
1226
|
-
}
|
|
1227
|
-
var init_json_extract = __esm({
|
|
1228
|
-
"src/assertions/json-extract.ts"() {
|
|
1229
|
-
"use strict";
|
|
1230
|
-
init_cjs_shims();
|
|
1231
|
-
}
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
1294
|
// src/assertions/json-schema.ts
|
|
1235
1295
|
var import_ajv, jsonSchema;
|
|
1236
1296
|
var init_json_schema = __esm({
|
|
@@ -1692,6 +1752,11 @@ var init_results = __esm({
|
|
|
1692
1752
|
});
|
|
1693
1753
|
|
|
1694
1754
|
// src/orchestrator/run.ts
|
|
1755
|
+
function answerParses(output, assertions) {
|
|
1756
|
+
return assertions.every(
|
|
1757
|
+
(a) => a.type !== "json-schema" || extractJson(output, a.extract ?? "auto").ok
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1695
1760
|
function resolvePrompt(template, vars) {
|
|
1696
1761
|
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key) => {
|
|
1697
1762
|
if (!(key in vars)) {
|
|
@@ -1719,17 +1784,35 @@ async function runRow(p) {
|
|
|
1719
1784
|
const usage3 = { ...est, cost_usd: p.provider.estimateCost(est).cost_usd };
|
|
1720
1785
|
return { vars: p.row.vars, output: null, cached: false, usage: usage3, assertions: [] };
|
|
1721
1786
|
}
|
|
1722
|
-
const key = cacheKey(
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1787
|
+
const key = cacheKey(
|
|
1788
|
+
{
|
|
1789
|
+
provider: p.providerConfig.type,
|
|
1790
|
+
model: p.providerConfig.model,
|
|
1791
|
+
temperature: temperature ?? 0,
|
|
1792
|
+
max_tokens: maxTokens,
|
|
1793
|
+
prompt_resolved: prompt5
|
|
1794
|
+
},
|
|
1795
|
+
p.policyVersion
|
|
1796
|
+
);
|
|
1729
1797
|
let output;
|
|
1730
1798
|
let usage2;
|
|
1731
1799
|
let cached = false;
|
|
1732
|
-
|
|
1800
|
+
let cacheSaved;
|
|
1801
|
+
let hit = p.noCache ? void 0 : p.cache.get(key, p.cacheMaxAgeMs !== void 0 ? { maxAgeMs: p.cacheMaxAgeMs } : {});
|
|
1802
|
+
if (hit !== void 0 && !answerParses(hit.output, p.assertions)) {
|
|
1803
|
+
if (p.cacheStrict !== true) p.cache.delete(key);
|
|
1804
|
+
hit = void 0;
|
|
1805
|
+
}
|
|
1806
|
+
if (hit === void 0 && p.cacheStrict === true) {
|
|
1807
|
+
return {
|
|
1808
|
+
vars: p.row.vars,
|
|
1809
|
+
output: null,
|
|
1810
|
+
cached: false,
|
|
1811
|
+
latency_ms: p.now() - t0,
|
|
1812
|
+
error: { code: CACHE_MISS_CODE, message: "no usable cache entry (--cache-strict)" },
|
|
1813
|
+
assertions: []
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1733
1816
|
if (hit !== void 0) {
|
|
1734
1817
|
output = hit.output;
|
|
1735
1818
|
cached = true;
|
|
@@ -1738,6 +1821,11 @@ async function runRow(p) {
|
|
|
1738
1821
|
output_tokens: hit.usage.output_tokens,
|
|
1739
1822
|
cost_usd: 0
|
|
1740
1823
|
};
|
|
1824
|
+
cacheSaved = {
|
|
1825
|
+
input_tokens: hit.usage.input_tokens,
|
|
1826
|
+
output_tokens: hit.usage.output_tokens,
|
|
1827
|
+
cost_usd: p.provider.estimateCost(hit.usage).cost_usd
|
|
1828
|
+
};
|
|
1741
1829
|
} else {
|
|
1742
1830
|
try {
|
|
1743
1831
|
const res = await p.provider.complete({
|
|
@@ -1755,7 +1843,9 @@ async function runRow(p) {
|
|
|
1755
1843
|
// lets a config pricing override win, else falls back to the table estimate (ADR-PRC01).
|
|
1756
1844
|
cost_usd: p.provider.estimateCost(res.usage, res.cost_usd).cost_usd
|
|
1757
1845
|
};
|
|
1758
|
-
if (!p.noCache)
|
|
1846
|
+
if (!p.noCache && isCacheable(res) && answerParses(res.output, p.assertions)) {
|
|
1847
|
+
p.cache.set(key, res);
|
|
1848
|
+
}
|
|
1759
1849
|
} catch (err) {
|
|
1760
1850
|
return {
|
|
1761
1851
|
vars: p.row.vars,
|
|
@@ -1801,6 +1891,7 @@ async function runRow(p) {
|
|
|
1801
1891
|
cached,
|
|
1802
1892
|
usage: finalUsage,
|
|
1803
1893
|
latency_ms: p.now() - t0,
|
|
1894
|
+
...cacheSaved !== void 0 ? { cache_saved: cacheSaved } : {},
|
|
1804
1895
|
...assertionError !== void 0 ? { error: assertionError } : {},
|
|
1805
1896
|
assertions: records
|
|
1806
1897
|
};
|
|
@@ -1835,19 +1926,41 @@ function summarize(evals, durationMs) {
|
|
|
1835
1926
|
cost_usd: 0,
|
|
1836
1927
|
duration_ms: durationMs
|
|
1837
1928
|
};
|
|
1929
|
+
const cache = {
|
|
1930
|
+
from_cache: 0,
|
|
1931
|
+
from_model: 0,
|
|
1932
|
+
missed: 0,
|
|
1933
|
+
errored: 0,
|
|
1934
|
+
saved: { input_tokens: 0, output_tokens: 0, cost_usd: 0 }
|
|
1935
|
+
};
|
|
1838
1936
|
for (const ev of evals) {
|
|
1839
1937
|
for (const row of ev.rows) {
|
|
1840
1938
|
summary.total += 1;
|
|
1841
1939
|
summary[rowStatus(row)] += 1;
|
|
1842
1940
|
summary.cost_usd += row.usage?.cost_usd ?? 0;
|
|
1941
|
+
if (row.cached) {
|
|
1942
|
+
cache.from_cache += 1;
|
|
1943
|
+
cache.saved.input_tokens += row.cache_saved?.input_tokens ?? 0;
|
|
1944
|
+
cache.saved.output_tokens += row.cache_saved?.output_tokens ?? 0;
|
|
1945
|
+
cache.saved.cost_usd += row.cache_saved?.cost_usd ?? 0;
|
|
1946
|
+
} else if (row.error?.code === CACHE_MISS_CODE) cache.missed += 1;
|
|
1947
|
+
else if (row.output !== null) cache.from_model += 1;
|
|
1948
|
+
else if (row.error !== void 0) cache.errored += 1;
|
|
1843
1949
|
}
|
|
1844
1950
|
}
|
|
1845
|
-
return summary;
|
|
1951
|
+
return { ...summary, cache };
|
|
1846
1952
|
}
|
|
1847
1953
|
async function runOrchestration(config, options, deps) {
|
|
1954
|
+
const noCache = options.noCache ?? false;
|
|
1955
|
+
const cacheStrict = options.cacheStrict ?? false;
|
|
1956
|
+
if (noCache && cacheStrict) {
|
|
1957
|
+
throw new RunConfigError("--no-cache and --cache-strict cannot be used together");
|
|
1958
|
+
}
|
|
1959
|
+
if (noCache && options.cacheMaxAgeMs !== void 0) {
|
|
1960
|
+
throw new RunConfigError("--no-cache and --cache-max-age cannot be used together");
|
|
1961
|
+
}
|
|
1848
1962
|
const started = deps.now();
|
|
1849
1963
|
const dryRun = options.dryRun ?? false;
|
|
1850
|
-
const noCache = options.noCache ?? false;
|
|
1851
1964
|
const evalResults = [];
|
|
1852
1965
|
const prepared = selectEvals(config.evals, options.only).map((ev) => {
|
|
1853
1966
|
const template = resolveTemplate(ev, deps.baseDir);
|
|
@@ -1874,7 +1987,9 @@ async function runOrchestration(config, options, deps) {
|
|
|
1874
1987
|
cache: deps.cache,
|
|
1875
1988
|
now: deps.now,
|
|
1876
1989
|
dryRun,
|
|
1877
|
-
noCache
|
|
1990
|
+
noCache,
|
|
1991
|
+
cacheStrict,
|
|
1992
|
+
...options.cacheMaxAgeMs !== void 0 ? { cacheMaxAgeMs: options.cacheMaxAgeMs } : {}
|
|
1878
1993
|
})
|
|
1879
1994
|
);
|
|
1880
1995
|
const passed = rowResults.every((r) => rowStatus(r) === "passed");
|
|
@@ -1893,7 +2008,7 @@ async function runOrchestration(config, options, deps) {
|
|
|
1893
2008
|
evals: evalResults
|
|
1894
2009
|
};
|
|
1895
2010
|
}
|
|
1896
|
-
var fs3, path3, DEFAULT_MAX_TOKENS2, PLUNE_VERSION, RunConfigError;
|
|
2011
|
+
var fs3, path3, DEFAULT_MAX_TOKENS2, PLUNE_VERSION, RunConfigError, CACHE_MISS_CODE;
|
|
1897
2012
|
var init_run = __esm({
|
|
1898
2013
|
"src/orchestrator/run.ts"() {
|
|
1899
2014
|
"use strict";
|
|
@@ -1901,6 +2016,8 @@ var init_run = __esm({
|
|
|
1901
2016
|
fs3 = __toESM(require("fs"), 1);
|
|
1902
2017
|
path3 = __toESM(require("path"), 1);
|
|
1903
2018
|
init_hash();
|
|
2019
|
+
init_json_extract();
|
|
2020
|
+
init_cache();
|
|
1904
2021
|
init_assertions();
|
|
1905
2022
|
init_judge();
|
|
1906
2023
|
init_concurrency();
|
|
@@ -1915,20 +2032,29 @@ var init_run = __esm({
|
|
|
1915
2032
|
this.name = "RunConfigError";
|
|
1916
2033
|
}
|
|
1917
2034
|
};
|
|
2035
|
+
CACHE_MISS_CODE = "CACHE_MISS";
|
|
1918
2036
|
}
|
|
1919
2037
|
});
|
|
1920
2038
|
|
|
1921
2039
|
// src/orchestrator/exit-code.ts
|
|
2040
|
+
function cacheMissRows(result) {
|
|
2041
|
+
return result.evals.flatMap(
|
|
2042
|
+
(ev) => ev.rows.filter((r) => r.error?.code === CACHE_MISS_CODE).map((r) => `${ev.id} ${JSON.stringify(r.vars)}`)
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
1922
2045
|
function exitCodeFor(result) {
|
|
1923
2046
|
const { failed, errored } = result.summary;
|
|
1924
2047
|
if (failed > 0) return 1;
|
|
1925
2048
|
if (errored > 0) return 2;
|
|
1926
2049
|
return 0;
|
|
1927
2050
|
}
|
|
2051
|
+
var CACHE_STRICT_EXIT;
|
|
1928
2052
|
var init_exit_code = __esm({
|
|
1929
2053
|
"src/orchestrator/exit-code.ts"() {
|
|
1930
2054
|
"use strict";
|
|
1931
2055
|
init_cjs_shims();
|
|
2056
|
+
init_run();
|
|
2057
|
+
CACHE_STRICT_EXIT = 5;
|
|
1932
2058
|
}
|
|
1933
2059
|
});
|
|
1934
2060
|
|
|
@@ -1953,8 +2079,11 @@ var init_dataset = __esm({
|
|
|
1953
2079
|
// src/orchestrator/index.ts
|
|
1954
2080
|
var orchestrator_exports = {};
|
|
1955
2081
|
__export(orchestrator_exports, {
|
|
2082
|
+
CACHE_MISS_CODE: () => CACHE_MISS_CODE,
|
|
2083
|
+
CACHE_STRICT_EXIT: () => CACHE_STRICT_EXIT,
|
|
1956
2084
|
RunConfigError: () => RunConfigError,
|
|
1957
2085
|
buildJudge: () => buildJudge,
|
|
2086
|
+
cacheMissRows: () => cacheMissRows,
|
|
1958
2087
|
exitCodeFor: () => exitCodeFor,
|
|
1959
2088
|
loadDataset: () => loadDataset,
|
|
1960
2089
|
mapLimit: () => mapLimit,
|
|
@@ -2013,6 +2142,11 @@ async function handleRun(options, depsFactory) {
|
|
|
2013
2142
|
const factory = depsFactory ?? ((c, d) => buildRealDeps(c, d, options.dryRun));
|
|
2014
2143
|
const deps = factory(config, baseDir);
|
|
2015
2144
|
try {
|
|
2145
|
+
const stale = deps.cache.staleEntries ?? 0;
|
|
2146
|
+
if (stale > 0) {
|
|
2147
|
+
process.stderr.write(`cache policy changed, ${stale} entries are stale (dropped)
|
|
2148
|
+
`);
|
|
2149
|
+
}
|
|
2016
2150
|
const result = await runOrchestration(
|
|
2017
2151
|
config,
|
|
2018
2152
|
{
|
|
@@ -2020,7 +2154,9 @@ async function handleRun(options, depsFactory) {
|
|
|
2020
2154
|
...options.only !== void 0 ? { only: options.only } : {},
|
|
2021
2155
|
...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {},
|
|
2022
2156
|
...options.noCache !== void 0 ? { noCache: options.noCache } : {},
|
|
2023
|
-
...options.bail !== void 0 ? { bail: options.bail } : {}
|
|
2157
|
+
...options.bail !== void 0 ? { bail: options.bail } : {},
|
|
2158
|
+
...options.cacheStrict === true ? { cacheStrict: true } : {},
|
|
2159
|
+
...options.cacheMaxAgeMs !== void 0 ? { cacheMaxAgeMs: options.cacheMaxAgeMs } : {}
|
|
2024
2160
|
},
|
|
2025
2161
|
deps
|
|
2026
2162
|
);
|
|
@@ -2045,6 +2181,7 @@ var init_run2 = __esm({
|
|
|
2045
2181
|
init_cache2();
|
|
2046
2182
|
init_orchestrator();
|
|
2047
2183
|
NOOP_CACHE = { get: () => void 0, set: () => {
|
|
2184
|
+
}, delete: () => {
|
|
2048
2185
|
}, clear: () => {
|
|
2049
2186
|
}, close: () => {
|
|
2050
2187
|
} };
|
|
@@ -2104,6 +2241,15 @@ function renderConsole(result, opts = {}) {
|
|
|
2104
2241
|
lines.push(
|
|
2105
2242
|
`${passedTxt} \xB7 ${failedTxt} \xB7 ${erroredTxt}${blockedTxt}${skippedTxt} \xB7 ${summary.total} total \xB7 $${summary.cost_usd.toFixed(4)} \xB7 ${summary.duration_ms}ms`
|
|
2106
2243
|
);
|
|
2244
|
+
const c = summary.cache;
|
|
2245
|
+
if (c !== void 0 && c.from_cache + c.from_model + c.missed > 0) {
|
|
2246
|
+
const missedTxt = c.missed > 0 ? ` \xB7 ${s.yellow(`${c.missed} missed`)}` : "";
|
|
2247
|
+
lines.push(
|
|
2248
|
+
s.dim(
|
|
2249
|
+
`cache: ${c.from_cache} from cache \xB7 ${c.from_model} from model${missedTxt} \xB7 saved ${c.saved.input_tokens + c.saved.output_tokens} tokens, $${c.saved.cost_usd.toFixed(4)}`
|
|
2250
|
+
)
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2107
2253
|
lines.push("");
|
|
2108
2254
|
for (const ev of result.evals) {
|
|
2109
2255
|
const mark = ev.passed ? s.green("PASS") : s.red("FAIL");
|
|
@@ -2579,6 +2725,7 @@ function readEnv(env = process.env) {
|
|
|
2579
2725
|
const warnings = [];
|
|
2580
2726
|
const uploadConcurrency = bounded(env, "PLUNE_UPLOAD_CONCURRENCY", UPLOAD_CONCURRENCY, warnings);
|
|
2581
2727
|
const uploadRetries = bounded(env, "PLUNE_UPLOAD_RETRIES", UPLOAD_RETRIES, warnings);
|
|
2728
|
+
const importTimeout = bounded(env, "PLUNE_IMPORT_TIMEOUT", IMPORT_TIMEOUT, warnings);
|
|
2582
2729
|
const title = value(env, "PLUNE_RUN_TITLE");
|
|
2583
2730
|
const environment = value(env, "PLUNE_ENV");
|
|
2584
2731
|
const labels = list(env, "PLUNE_LABELS");
|
|
@@ -2610,6 +2757,7 @@ function readEnv(env = process.env) {
|
|
|
2610
2757
|
keepOpen: flag(env, "PLUNE_SHARED_RUN") || flag(env, "PLUNE_PROCEED") || shard !== void 0 || shardError !== void 0,
|
|
2611
2758
|
...shard !== void 0 ? { shard } : {},
|
|
2612
2759
|
...shardError !== void 0 ? { shardError } : {},
|
|
2760
|
+
...importTimeout !== void 0 ? { importTimeout } : {},
|
|
2613
2761
|
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0),
|
|
2614
2762
|
warnings
|
|
2615
2763
|
};
|
|
@@ -2637,7 +2785,7 @@ function ciCommit(reported, env = process.env) {
|
|
|
2637
2785
|
...branch !== void 0 ? { branch } : {}
|
|
2638
2786
|
};
|
|
2639
2787
|
}
|
|
2640
|
-
var path6, UNSUPPORTED_VARS, SHARDS_MAX, UPLOAD_CONCURRENCY, UPLOAD_RETRIES, PROVIDERS;
|
|
2788
|
+
var path6, UNSUPPORTED_VARS, SHARDS_MAX, UPLOAD_CONCURRENCY, UPLOAD_RETRIES, IMPORT_TIMEOUT, PROVIDERS;
|
|
2641
2789
|
var init_env2 = __esm({
|
|
2642
2790
|
"src/reporter-core/env.ts"() {
|
|
2643
2791
|
"use strict";
|
|
@@ -2647,6 +2795,7 @@ var init_env2 = __esm({
|
|
|
2647
2795
|
SHARDS_MAX = 1e3;
|
|
2648
2796
|
UPLOAD_CONCURRENCY = { min: 1, max: 32, default: 16 };
|
|
2649
2797
|
UPLOAD_RETRIES = { min: 0, max: 20, default: 6 };
|
|
2798
|
+
IMPORT_TIMEOUT = { min: 0, max: 86400, default: 600 };
|
|
2650
2799
|
PROVIDERS = [
|
|
2651
2800
|
{ is: "GITHUB_ACTIONS", sha: "GITHUB_SHA", branch: ["GITHUB_HEAD_REF", "GITHUB_REF_NAME"] },
|
|
2652
2801
|
{ is: "GITLAB_CI", sha: "CI_COMMIT_SHA", branch: ["CI_COMMIT_REF_NAME"] },
|
|
@@ -3900,7 +4049,7 @@ async function handleRunStart(options = {}) {
|
|
|
3900
4049
|
kind: options.kind ?? "automated",
|
|
3901
4050
|
externalKey,
|
|
3902
4051
|
// The same name the reporter would give it; `PLUNE_RUN_TITLE` replaces it, as everywhere.
|
|
3903
|
-
title: env.config.title ?? defaultRunTitle()
|
|
4052
|
+
title: options.name ?? env.config.title ?? defaultRunTitle()
|
|
3904
4053
|
});
|
|
3905
4054
|
if (!out.ok) throw new RunCommandError(`Could not start the run \u2014 ${out.detail || out.kind}.`);
|
|
3906
4055
|
const result = {
|
|
@@ -4119,6 +4268,7 @@ var init_run_result_schema = __esm({
|
|
|
4119
4268
|
latency_ms: import_zod3.z.number().optional(),
|
|
4120
4269
|
error: runErrorSchema.optional(),
|
|
4121
4270
|
assertions: import_zod3.z.array(assertionResultRecordSchema2),
|
|
4271
|
+
cache_saved: import_zod3.z.object({ input_tokens: import_zod3.z.number(), output_tokens: import_zod3.z.number(), cost_usd: import_zod3.z.number() }).optional(),
|
|
4122
4272
|
/** ADR 0045. Optional and additive — the same enum the platform validates on `POST /v1/runs`. */
|
|
4123
4273
|
inconclusive: import_zod3.z.enum(ROW_INCONCLUSIVE).optional()
|
|
4124
4274
|
});
|
|
@@ -4131,7 +4281,14 @@ var init_run_result_schema = __esm({
|
|
|
4131
4281
|
blocked: import_zod3.z.number().optional(),
|
|
4132
4282
|
skipped: import_zod3.z.number().optional(),
|
|
4133
4283
|
cost_usd: import_zod3.z.number(),
|
|
4134
|
-
duration_ms: import_zod3.z.number()
|
|
4284
|
+
duration_ms: import_zod3.z.number(),
|
|
4285
|
+
cache: import_zod3.z.object({
|
|
4286
|
+
from_cache: import_zod3.z.number(),
|
|
4287
|
+
from_model: import_zod3.z.number(),
|
|
4288
|
+
missed: import_zod3.z.number(),
|
|
4289
|
+
errored: import_zod3.z.number(),
|
|
4290
|
+
saved: import_zod3.z.object({ input_tokens: import_zod3.z.number(), output_tokens: import_zod3.z.number(), cost_usd: import_zod3.z.number() })
|
|
4291
|
+
}).optional()
|
|
4135
4292
|
});
|
|
4136
4293
|
evalResultSchema = import_zod3.z.object({
|
|
4137
4294
|
id: import_zod3.z.string(),
|
|
@@ -4306,6 +4463,7 @@ __export(plan_exports, {
|
|
|
4306
4463
|
grepFragment: () => grepFragment,
|
|
4307
4464
|
handlePlanGrep: () => handlePlanGrep,
|
|
4308
4465
|
handleRunGrep: () => handleRunGrep,
|
|
4466
|
+
platformGet: () => platformGet,
|
|
4309
4467
|
runGrepSummary: () => runGrepSummary
|
|
4310
4468
|
});
|
|
4311
4469
|
function grepFragment(c) {
|
|
@@ -5365,8 +5523,24 @@ var init_importers = __esm({
|
|
|
5365
5523
|
// src/cli/commands/run-import.ts
|
|
5366
5524
|
var run_import_exports = {};
|
|
5367
5525
|
__export(run_import_exports, {
|
|
5526
|
+
ImportTimeoutError: () => ImportTimeoutError,
|
|
5368
5527
|
handleRunImport: () => handleRunImport
|
|
5369
5528
|
});
|
|
5529
|
+
async function timeoutMessage(limit, runId, ours, options) {
|
|
5530
|
+
const head = `plune: run import did not finish in ${limit} s (--timeout / PLUNE_IMPORT_TIMEOUT; 0 turns the limit off); the platform may have received part of the results`;
|
|
5531
|
+
if (runId === null) return `${head}; the run id is not known, so nothing was terminated - look for an open run with this key in Plune and terminate it there, then import again.`;
|
|
5532
|
+
if (!ours) return `${head}; run ${runId} is shared with other jobs and was left open - close it with "plune run finish ${runId}" once they are done, then import this report again.`;
|
|
5533
|
+
const wait = options.terminateWaitMs ?? 15e3;
|
|
5534
|
+
let timer;
|
|
5535
|
+
const gaveUp = new Promise((resolve10) => timer = setTimeout(() => resolve10("gaveup"), wait));
|
|
5536
|
+
const quiet = () => void 0;
|
|
5537
|
+
const closed = handleRunFinish(runId, { ...options, write: quiet, terminate: true, reason: `import timed out after ${limit} s` }).then(
|
|
5538
|
+
() => true,
|
|
5539
|
+
() => false
|
|
5540
|
+
);
|
|
5541
|
+
const done = await Promise.race([closed, gaveUp]).finally(() => clearTimeout(timer)) === true;
|
|
5542
|
+
return done ? `${head}; run ${runId} was terminated - import the report again.` : `${head}; run ${runId} could not be terminated from here - run "plune run finish ${runId} --terminate" (the platform also closes a silent run after 30 minutes), then import again.`;
|
|
5543
|
+
}
|
|
5370
5544
|
async function handleRunImport(options) {
|
|
5371
5545
|
const write = options.write ?? ((line) => void process.stdout.write(`${line}
|
|
5372
5546
|
`));
|
|
@@ -5393,31 +5567,48 @@ async function handleRunImport(options) {
|
|
|
5393
5567
|
const apiUrl = resolveApiUrl(options.apiUrl ?? env.config.apiUrl);
|
|
5394
5568
|
const token = options.token ?? env.config.token ?? loadToken() ?? "";
|
|
5395
5569
|
if (token === "") throw new NoTokenError();
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5570
|
+
let session;
|
|
5571
|
+
const send = async () => {
|
|
5572
|
+
session = await startRun(
|
|
5573
|
+
{
|
|
5574
|
+
apiUrl,
|
|
5575
|
+
token,
|
|
5576
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
5577
|
+
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
5578
|
+
...options.name !== void 0 ? { title: options.name } : {},
|
|
5579
|
+
...options.create === true ? { offerDiscovered: true } : {},
|
|
5580
|
+
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
5581
|
+
kind: "automated",
|
|
5582
|
+
// The CI run the report names, never this machine's: whoever imports may be another job (#790).
|
|
5583
|
+
// The commit and branch likewise, where the report names a commit; a report that names none —
|
|
5584
|
+
// JUnit's never does — takes this job's own, which is where the tests ran when import follows
|
|
5585
|
+
// them in one job (plune-ai/plune#927).
|
|
5586
|
+
meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {}, ...ciCommit(ci) },
|
|
5587
|
+
log: write
|
|
5588
|
+
},
|
|
5589
|
+
// The file is the complete list of what ran, so the platform can resolve every test in one
|
|
5590
|
+
// request and derive what never ran from the same list (AC-09/AC-10) — the lazy path would be
|
|
5591
|
+
// a lookup per result.
|
|
5592
|
+
results.map((r) => r.keys)
|
|
5593
|
+
);
|
|
5594
|
+
for (const result2 of results) await session.add(result2);
|
|
5595
|
+
return env.keepOpen ? session.leaveOpen({ holdShardLines: true }) : session.finish();
|
|
5596
|
+
};
|
|
5597
|
+
const limit = options.timeoutSec ?? env.importTimeout ?? IMPORT_TIMEOUT.default;
|
|
5598
|
+
const sent = send();
|
|
5418
5599
|
let frozen;
|
|
5419
|
-
if (
|
|
5420
|
-
else
|
|
5600
|
+
if (limit === 0) frozen = await sent;
|
|
5601
|
+
else {
|
|
5602
|
+
sent.catch(() => void 0);
|
|
5603
|
+
let timer;
|
|
5604
|
+
const late = new Promise((resolve10) => timer = setTimeout(() => resolve10("late"), limit * 1e3));
|
|
5605
|
+
const first = await Promise.race([sent, late]).finally(() => clearTimeout(timer));
|
|
5606
|
+
if (first === "late") {
|
|
5607
|
+
throw new ImportTimeoutError(await timeoutMessage(limit, session?.runId ?? null, !env.keepOpen, { ...options, apiUrl, token }));
|
|
5608
|
+
}
|
|
5609
|
+
frozen = first;
|
|
5610
|
+
}
|
|
5611
|
+
if (session === void 0) throw new RunCommandError("The import did not start.");
|
|
5421
5612
|
const stats = session.stats;
|
|
5422
5613
|
const result = {
|
|
5423
5614
|
runId: session.runId,
|
|
@@ -5512,7 +5703,7 @@ function describe(result, apiUrl) {
|
|
|
5512
5703
|
if (result.runId !== null) lines.push(runUrl2(apiUrl, result.runId));
|
|
5513
5704
|
return lines;
|
|
5514
5705
|
}
|
|
5515
|
-
var fs12, NEXT;
|
|
5706
|
+
var fs12, ImportTimeoutError, NEXT;
|
|
5516
5707
|
var init_run_import = __esm({
|
|
5517
5708
|
"src/cli/commands/run-import.ts"() {
|
|
5518
5709
|
"use strict";
|
|
@@ -5526,6 +5717,8 @@ var init_run_import = __esm({
|
|
|
5526
5717
|
init_credentials();
|
|
5527
5718
|
init_gate();
|
|
5528
5719
|
init_env2();
|
|
5720
|
+
ImportTimeoutError = class extends RunCommandError {
|
|
5721
|
+
};
|
|
5529
5722
|
NEXT = {
|
|
5530
5723
|
queue: "the review queue is full. Approve or reject what is waiting, then import this report again to offer the rest.",
|
|
5531
5724
|
cases: "the project is at its test case limit. Delete cases you no longer need, or ask an operator to raise the limit, then import this report again to offer the rest.",
|
|
@@ -6608,6 +6801,130 @@ var init_requirements_import = __esm({
|
|
|
6608
6801
|
}
|
|
6609
6802
|
});
|
|
6610
6803
|
|
|
6804
|
+
// src/cli/commands/verify.ts
|
|
6805
|
+
var verify_exports = {};
|
|
6806
|
+
__export(verify_exports, {
|
|
6807
|
+
compare: () => compare,
|
|
6808
|
+
formatVerify: () => formatVerify,
|
|
6809
|
+
handleVerify: () => handleVerify,
|
|
6810
|
+
testsOfListing: () => testsOfListing
|
|
6811
|
+
});
|
|
6812
|
+
function testsOfListing(listing) {
|
|
6813
|
+
const found = /* @__PURE__ */ new Map();
|
|
6814
|
+
const walk2 = (suite, titles) => {
|
|
6815
|
+
for (const spec of suite.specs ?? []) {
|
|
6816
|
+
const annotated = spec.tests?.flatMap((t) => t.annotations ?? []).find(
|
|
6817
|
+
(a) => a.type === "PluneId" && a.description !== void 0 && a.description !== ""
|
|
6818
|
+
)?.description;
|
|
6819
|
+
const id = annotated ?? TOKEN4.exec(spec.title)?.[1];
|
|
6820
|
+
const pathTitle = [...titles, spec.title].join("#");
|
|
6821
|
+
if (!found.has(pathTitle))
|
|
6822
|
+
found.set(pathTitle, { pathTitle, ...id === void 0 ? {} : { id } });
|
|
6823
|
+
}
|
|
6824
|
+
for (const child of suite.suites ?? []) walk2(child, [...titles, child.title]);
|
|
6825
|
+
};
|
|
6826
|
+
for (const file of listing.suites ?? []) walk2(file, [file.title.replace(/\\/g, "/")]);
|
|
6827
|
+
return [...found.values()];
|
|
6828
|
+
}
|
|
6829
|
+
function compare(code, cases) {
|
|
6830
|
+
const live = cases.filter((c) => c.type === "automated" && c.state !== "deprecated");
|
|
6831
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6832
|
+
for (const c of live)
|
|
6833
|
+
for (const k of c.externalKeys ?? []) if (k.kind === "path-title") byPath.set(k.value, c);
|
|
6834
|
+
const byId = new Map(live.map((c) => [c.id, c]));
|
|
6835
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6836
|
+
const onlyInCode = [];
|
|
6837
|
+
for (const t of code) {
|
|
6838
|
+
const hit = (t.id === void 0 ? void 0 : byId.get(t.id)) ?? byPath.get(t.pathTitle);
|
|
6839
|
+
if (hit === void 0) onlyInCode.push({ pathTitle: t.pathTitle });
|
|
6840
|
+
else seen.add(hit.id);
|
|
6841
|
+
}
|
|
6842
|
+
return {
|
|
6843
|
+
matched: seen.size,
|
|
6844
|
+
onlyInPlatform: live.filter((c) => !seen.has(c.id)).map((c) => ({ id: c.id, title: c.title, state: c.state, detached: c.state === "detached" })),
|
|
6845
|
+
onlyInCode
|
|
6846
|
+
};
|
|
6847
|
+
}
|
|
6848
|
+
function runPlaywrightList(args, cwd) {
|
|
6849
|
+
try {
|
|
6850
|
+
return (0, import_node_child_process3.execFileSync)("npx", ["playwright", "test", ...args], {
|
|
6851
|
+
cwd,
|
|
6852
|
+
encoding: "utf8",
|
|
6853
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
6854
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6855
|
+
shell: process.platform === "win32"
|
|
6856
|
+
});
|
|
6857
|
+
} catch (err) {
|
|
6858
|
+
const stderr = err.stderr?.trim();
|
|
6859
|
+
throw new SyncFileError(`could not list the Playwright tests${stderr ? `: ${stderr}` : ""}`);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
async function handleVerify(deps) {
|
|
6863
|
+
const get = platformGet(deps);
|
|
6864
|
+
let raw;
|
|
6865
|
+
if (deps.from !== void 0) {
|
|
6866
|
+
try {
|
|
6867
|
+
raw = (0, import_node_fs8.readFileSync)(deps.from, "utf8");
|
|
6868
|
+
} catch {
|
|
6869
|
+
throw new SyncFileError(`cannot read ${deps.from}`);
|
|
6870
|
+
}
|
|
6871
|
+
} else {
|
|
6872
|
+
const args = [
|
|
6873
|
+
"--list",
|
|
6874
|
+
"--reporter=json",
|
|
6875
|
+
...deps.playwrightConfig === void 0 ? [] : ["--config", deps.playwrightConfig]
|
|
6876
|
+
];
|
|
6877
|
+
raw = (deps.list ?? runPlaywrightList)(args, deps.cwd ?? process.cwd());
|
|
6878
|
+
}
|
|
6879
|
+
let listing;
|
|
6880
|
+
try {
|
|
6881
|
+
listing = JSON.parse(raw);
|
|
6882
|
+
} catch {
|
|
6883
|
+
throw new SyncFileError("the Playwright listing is not JSON");
|
|
6884
|
+
}
|
|
6885
|
+
const cases = [];
|
|
6886
|
+
let cursor = null;
|
|
6887
|
+
do {
|
|
6888
|
+
const page = await get(
|
|
6889
|
+
`/v1/test-cases?type=automated&stability=0&limit=200${cursor === null ? "" : `&cursor=${cursor}`}`
|
|
6890
|
+
);
|
|
6891
|
+
cases.push(...page.testCases);
|
|
6892
|
+
cursor = page.nextCursor;
|
|
6893
|
+
} while (cursor !== null);
|
|
6894
|
+
return compare(testsOfListing(listing), cases);
|
|
6895
|
+
}
|
|
6896
|
+
function formatVerify(r) {
|
|
6897
|
+
const lines = [];
|
|
6898
|
+
if (r.onlyInPlatform.length > 0) {
|
|
6899
|
+
lines.push(`In Plune, not in the code (${r.onlyInPlatform.length}):`);
|
|
6900
|
+
for (const c of r.onlyInPlatform) {
|
|
6901
|
+
lines.push(
|
|
6902
|
+
` ${c.id} ${c.title} [${c.detached ? "already detached" : `${c.state} \u2014 a full run would detach it`}]`
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
if (r.onlyInCode.length > 0) {
|
|
6907
|
+
lines.push(`In the code, not in Plune (${r.onlyInCode.length}):`);
|
|
6908
|
+
for (const t of r.onlyInCode) lines.push(` ${t.pathTitle}`);
|
|
6909
|
+
}
|
|
6910
|
+
lines.push(
|
|
6911
|
+
r.onlyInPlatform.length + r.onlyInCode.length === 0 ? `No drift: ${r.matched} case(s) match the code.` : `${r.matched} matched. Nothing was changed.`
|
|
6912
|
+
);
|
|
6913
|
+
return lines.join("\n") + "\n";
|
|
6914
|
+
}
|
|
6915
|
+
var import_node_child_process3, import_node_fs8, TOKEN4;
|
|
6916
|
+
var init_verify = __esm({
|
|
6917
|
+
"src/cli/commands/verify.ts"() {
|
|
6918
|
+
"use strict";
|
|
6919
|
+
init_cjs_shims();
|
|
6920
|
+
import_node_child_process3 = require("child_process");
|
|
6921
|
+
import_node_fs8 = require("fs");
|
|
6922
|
+
init_plan();
|
|
6923
|
+
init_sync();
|
|
6924
|
+
TOKEN4 = /@P([A-Za-z0-9_-]+)/;
|
|
6925
|
+
}
|
|
6926
|
+
});
|
|
6927
|
+
|
|
6611
6928
|
// src/cli.ts
|
|
6612
6929
|
var cli_exports = {};
|
|
6613
6930
|
__export(cli_exports, {
|
|
@@ -6617,11 +6934,25 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
6617
6934
|
init_cjs_shims();
|
|
6618
6935
|
init_api_url();
|
|
6619
6936
|
var import_commander = require("commander");
|
|
6620
|
-
var
|
|
6937
|
+
var import_node_fs9 = require("fs");
|
|
6621
6938
|
var import_node_path5 = require("path");
|
|
6622
6939
|
var import_node_url = require("url");
|
|
6940
|
+
|
|
6941
|
+
// src/util/duration.ts
|
|
6942
|
+
init_cjs_shims();
|
|
6943
|
+
var UNIT_MS = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
6944
|
+
function parseDuration(text2) {
|
|
6945
|
+
const m = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(text2.trim());
|
|
6946
|
+
const ms = m === null ? NaN : Number(m[1]) * UNIT_MS[m[2] ?? "ms"];
|
|
6947
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
6948
|
+
throw new Error(`Invalid duration "${text2}" (use e.g. 90s, 15m, 12h, 7d)`);
|
|
6949
|
+
}
|
|
6950
|
+
return ms;
|
|
6951
|
+
}
|
|
6952
|
+
|
|
6953
|
+
// src/cli.ts
|
|
6623
6954
|
var __dirname = (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
6624
|
-
var pkg = JSON.parse((0,
|
|
6955
|
+
var pkg = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path5.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
6625
6956
|
function toFormat(s) {
|
|
6626
6957
|
return s === "json" || s === "markdown" ? s : "console";
|
|
6627
6958
|
}
|
|
@@ -6630,7 +6961,7 @@ function shouldColor(outputPath) {
|
|
|
6630
6961
|
}
|
|
6631
6962
|
function writeReport(text2, outputPath) {
|
|
6632
6963
|
if (outputPath !== void 0) {
|
|
6633
|
-
(0,
|
|
6964
|
+
(0, import_node_fs9.writeFileSync)(outputPath, text2);
|
|
6634
6965
|
} else {
|
|
6635
6966
|
process.stdout.write(text2 + "\n");
|
|
6636
6967
|
}
|
|
@@ -6683,13 +7014,17 @@ function createProgram() {
|
|
|
6683
7014
|
"Run a subset by eval id or tag (repeatable)",
|
|
6684
7015
|
(val, prev) => [...prev, val],
|
|
6685
7016
|
[]
|
|
6686
|
-
).option("--concurrency <n>", "Override provider concurrency", (v) => parseInt(v, 10)).option("--no-cache", "Bypass the local cache for this run").option(
|
|
7017
|
+
).option("--concurrency <n>", "Override provider concurrency", (v) => parseInt(v, 10)).option("--no-cache", "Bypass the local cache for this run").option(
|
|
7018
|
+
"--cache-strict",
|
|
7019
|
+
"Every row must be in the cache: no model calls for completions; a miss ends the run with exit 5 and lists the rows",
|
|
7020
|
+
false
|
|
7021
|
+
).option("--cache-max-age <duration>", "Treat cache entries older than this as a miss (e.g. 90s, 15m, 12h, 7d)").option("--bail", "Stop after the first failing eval", false).option("--format <fmt>", "console | json | markdown", "console").option("-o, --output <path>", "Write the report to a file instead of stdout").action(
|
|
6687
7022
|
async (options, command) => {
|
|
6688
7023
|
const globals = command.optsWithGlobals();
|
|
6689
7024
|
const configPath = globals.config;
|
|
6690
7025
|
const [
|
|
6691
7026
|
{ handleRun: handleRun2 },
|
|
6692
|
-
{ exitCodeFor: exitCodeFor2, RunConfigError: RunConfigError2 },
|
|
7027
|
+
{ exitCodeFor: exitCodeFor2, cacheMissRows: cacheMissRows2, CACHE_STRICT_EXIT: CACHE_STRICT_EXIT2, RunConfigError: RunConfigError2 },
|
|
6693
7028
|
{ renderReport: renderReport2 },
|
|
6694
7029
|
{ ConfigNotFoundError: ConfigNotFoundError2, ConfigValidationError: ConfigValidationError2, YamlParseError: YamlParseError2 },
|
|
6695
7030
|
{ AuthError: AuthError2 }
|
|
@@ -6701,13 +7036,36 @@ function createProgram() {
|
|
|
6701
7036
|
Promise.resolve().then(() => (init_errors2(), errors_exports2))
|
|
6702
7037
|
]);
|
|
6703
7038
|
try {
|
|
7039
|
+
if (options.cache === false && options.cacheMaxAge !== void 0) {
|
|
7040
|
+
process.stderr.write("--no-cache and --cache-max-age cannot be used together\n");
|
|
7041
|
+
process.exit(2);
|
|
7042
|
+
return;
|
|
7043
|
+
}
|
|
7044
|
+
if (options.cache === false && options.cacheStrict) {
|
|
7045
|
+
process.stderr.write("--no-cache and --cache-strict cannot be used together\n");
|
|
7046
|
+
process.exit(2);
|
|
7047
|
+
return;
|
|
7048
|
+
}
|
|
7049
|
+
let cacheMaxAgeMs;
|
|
7050
|
+
if (options.cacheMaxAge !== void 0) {
|
|
7051
|
+
try {
|
|
7052
|
+
cacheMaxAgeMs = parseDuration(options.cacheMaxAge);
|
|
7053
|
+
} catch (err) {
|
|
7054
|
+
process.stderr.write(`--cache-max-age: ${err.message}
|
|
7055
|
+
`);
|
|
7056
|
+
process.exit(2);
|
|
7057
|
+
return;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
6704
7060
|
const result = await handleRun2({
|
|
6705
7061
|
dryRun: options.dryRun,
|
|
6706
7062
|
...configPath !== void 0 ? { configPath } : {},
|
|
6707
7063
|
...options.only.length > 0 ? { only: options.only } : {},
|
|
6708
7064
|
...typeof options.concurrency === "number" && Number.isFinite(options.concurrency) ? { concurrency: options.concurrency } : {},
|
|
6709
7065
|
noCache: options.cache === false,
|
|
6710
|
-
bail: options.bail
|
|
7066
|
+
bail: options.bail,
|
|
7067
|
+
...options.cacheStrict ? { cacheStrict: true } : {},
|
|
7068
|
+
...cacheMaxAgeMs !== void 0 ? { cacheMaxAgeMs } : {}
|
|
6711
7069
|
});
|
|
6712
7070
|
if (options.only.length > 0 && result.evals.length === 0) {
|
|
6713
7071
|
process.stderr.write("No evals matched the selector.\n");
|
|
@@ -6717,6 +7075,15 @@ function createProgram() {
|
|
|
6717
7075
|
});
|
|
6718
7076
|
writeReport(text2, options.output);
|
|
6719
7077
|
process.exitCode = exitCodeFor2(result);
|
|
7078
|
+
const missed = cacheMissRows2(result);
|
|
7079
|
+
if (missed.length > 0) {
|
|
7080
|
+
process.stderr.write(
|
|
7081
|
+
`--cache-strict: ${missed.length} row(s) have no cache entry:
|
|
7082
|
+
${missed.map((m) => ` - ${m}`).join("\n")}
|
|
7083
|
+
`
|
|
7084
|
+
);
|
|
7085
|
+
process.exitCode = CACHE_STRICT_EXIT2;
|
|
7086
|
+
}
|
|
6720
7087
|
} catch (err) {
|
|
6721
7088
|
const verbose = globals.verbose === true;
|
|
6722
7089
|
if (err instanceof ConfigValidationError2) {
|
|
@@ -6766,11 +7133,12 @@ function createProgram() {
|
|
|
6766
7133
|
};
|
|
6767
7134
|
const verboseOf = (command) => command.optsWithGlobals().verbose === true;
|
|
6768
7135
|
const FAIL_ON_FAILURES = "Exit with the platform's verdict on the run: 1 on a failed or broken result (muted ones do not count), on an expected case that never ran, or when there is no verdict at all";
|
|
6769
|
-
runCommand.command("start").description("Open a platform run (or join one already carrying the key) and print its id").option("--key <externalKey>", "The key several processes share; generated when absent").option("--json", "Print one machine-readable line instead of prose", false).action(async (options, command) => {
|
|
7136
|
+
runCommand.command("start").description("Open a platform run (or join one already carrying the key) and print its id").option("--key <externalKey>", "The key several processes share; generated when absent").option("--name <text>", "What to call the run in the list (default: the folder name, the time and where it ran; PLUNE_RUN_TITLE sets it too)").option("--json", "Print one machine-readable line instead of prose", false).action(async (options, command) => {
|
|
6770
7137
|
try {
|
|
6771
7138
|
const { handleRunStart: handleRunStart2 } = await Promise.resolve().then(() => (init_run_lifecycle(), run_lifecycle_exports));
|
|
6772
7139
|
await handleRunStart2({
|
|
6773
7140
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7141
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6774
7142
|
json: options.json
|
|
6775
7143
|
});
|
|
6776
7144
|
} catch (err) {
|
|
@@ -6794,12 +7162,13 @@ function createProgram() {
|
|
|
6794
7162
|
);
|
|
6795
7163
|
runCommand.command("exec").argument("<command...>", "The command to run inside the run, after `--`").description(
|
|
6796
7164
|
"Open a run, run a command inside it, close the run \u2014 whatever the command returns"
|
|
6797
|
-
).option("--key <externalKey>", "The key several processes share; generated when absent").option("--fail-on-failures", `${FAIL_ON_FAILURES}; the command's own exit code when no result reached the run`, false).action(async (argv, options, command) => {
|
|
7165
|
+
).option("--key <externalKey>", "The key several processes share; generated when absent").option("--name <text>", "What to call the run in the list (default: the folder name, the time and where it ran; PLUNE_RUN_TITLE sets it too)").option("--fail-on-failures", `${FAIL_ON_FAILURES}; the command's own exit code when no result reached the run`, false).action(async (argv, options, command) => {
|
|
6798
7166
|
try {
|
|
6799
7167
|
const { handleRunExec: handleRunExec2 } = await Promise.resolve().then(() => (init_run_lifecycle(), run_lifecycle_exports));
|
|
6800
7168
|
process.exitCode = await handleRunExec2({
|
|
6801
7169
|
argv,
|
|
6802
7170
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7171
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6803
7172
|
...options.failOnFailures ? { failOnFailures: true } : {}
|
|
6804
7173
|
});
|
|
6805
7174
|
} catch (err) {
|
|
@@ -6851,7 +7220,7 @@ function createProgram() {
|
|
|
6851
7220
|
await failRunCommand(err, verboseOf(command));
|
|
6852
7221
|
}
|
|
6853
7222
|
});
|
|
6854
|
-
runCommand.command("import").argument("<file>", "A JUnit XML or Playwright JSON report, or an Allure allure-results folder").description("Turn a report another runner wrote into a run in Plune \u2014 no provider key needed").option("--format <fmt>", "junit | playwright-json | allure; detected from the file (a folder is allure) when omitted").option("--key <externalKey>", "The key several jobs share so their reports land in one run").option("--create", "Offer tests nothing matched to the review queue", false).option(
|
|
7223
|
+
runCommand.command("import").argument("<file>", "A JUnit XML or Playwright JSON report, or an Allure allure-results folder").description("Turn a report another runner wrote into a run in Plune \u2014 no provider key needed").option("--format <fmt>", "junit | playwright-json | allure; detected from the file (a folder is allure) when omitted").option("--key <externalKey>", "The key several jobs share so their reports land in one run").option("--name <text>", "What to call the run in the list (default: the folder name, the time and where it ran; PLUNE_RUN_TITLE sets it too)").option("--create", "Offer tests nothing matched to the review queue", false).option(
|
|
6855
7224
|
"--fail-on-missing-attachments",
|
|
6856
7225
|
"Exit 1 when a file the report names ([[ATTACHMENT|path]] in JUnit, a source in allure-results) is not there; the results are sent either way",
|
|
6857
7226
|
false
|
|
@@ -6862,6 +7231,13 @@ function createProgram() {
|
|
|
6862
7231
|
if (!/^\d+$/.test(v) || Number(v) < 1 || Number(v) > 32) throw new import_commander.InvalidArgumentError("a whole number from 1 to 32.");
|
|
6863
7232
|
return Number(v);
|
|
6864
7233
|
}
|
|
7234
|
+
).option(
|
|
7235
|
+
"--timeout <seconds>",
|
|
7236
|
+
"Give up on the whole import after this many seconds, 0 for no limit (default 600; PLUNE_IMPORT_TIMEOUT sets it too). On a timeout the run this import opened is terminated, and the exit code is 1",
|
|
7237
|
+
(v) => {
|
|
7238
|
+
if (!/^\d+$/.test(v) || Number(v) > 86400) throw new import_commander.InvalidArgumentError("a whole number of seconds from 0 to 86400.");
|
|
7239
|
+
return Number(v);
|
|
7240
|
+
}
|
|
6865
7241
|
).option("--fail-on-failures", FAIL_ON_FAILURES, false).action(
|
|
6866
7242
|
async (file, options, command) => {
|
|
6867
7243
|
const [
|
|
@@ -6886,8 +7262,10 @@ function createProgram() {
|
|
|
6886
7262
|
file,
|
|
6887
7263
|
...format !== void 0 ? { format } : {},
|
|
6888
7264
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7265
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6889
7266
|
...options.create ? { create: true } : {},
|
|
6890
7267
|
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
7268
|
+
...options.timeout !== void 0 ? { timeoutSec: options.timeout } : {},
|
|
6891
7269
|
...options.failOnFailures ? { failOnFailures: true } : {}
|
|
6892
7270
|
});
|
|
6893
7271
|
if (options.failOnMissingAttachments && imported.missingAttachments.length > 0) process.exitCode = 1;
|
|
@@ -7145,6 +7523,31 @@ function createProgram() {
|
|
|
7145
7523
|
failUnexpected(err, verbose);
|
|
7146
7524
|
}
|
|
7147
7525
|
});
|
|
7526
|
+
program.command("verify").description(
|
|
7527
|
+
"Compare the project's automated cases with the Playwright tests in the code, read-only \u2014 both sides of the drift, nothing written"
|
|
7528
|
+
).option("--from <file>", "A saved `playwright test --list --reporter=json` document instead of running Playwright").option("--playwright-config <path>", "Passed to `playwright test --config`").option("--json", "Print the report as JSON", false).option("--fail-on-drift", "Exit 1 when either side has something the other lacks", false).action(
|
|
7529
|
+
async (options, command) => {
|
|
7530
|
+
const verbose = verboseOf(command);
|
|
7531
|
+
const { handleVerify: handleVerify2, formatVerify: formatVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
7532
|
+
const { reportCasesFailure: reportCasesFailure2 } = await Promise.resolve().then(() => (init_cases(), cases_exports));
|
|
7533
|
+
try {
|
|
7534
|
+
const report = await handleVerify2({
|
|
7535
|
+
...options.from === void 0 ? {} : { from: options.from },
|
|
7536
|
+
...options.playwrightConfig === void 0 ? {} : { playwrightConfig: options.playwrightConfig }
|
|
7537
|
+
});
|
|
7538
|
+
process.stdout.write(options.json ? JSON.stringify(report, null, 2) + "\n" : formatVerify2(report));
|
|
7539
|
+
if (options.failOnDrift && report.onlyInPlatform.length + report.onlyInCode.length > 0) process.exitCode = 1;
|
|
7540
|
+
} catch (err) {
|
|
7541
|
+
const code = reportCasesFailure2(err, (s) => process.stderr.write(s));
|
|
7542
|
+
if (code !== null) {
|
|
7543
|
+
maybeStack(err, verbose);
|
|
7544
|
+
process.exitCode = code;
|
|
7545
|
+
return;
|
|
7546
|
+
}
|
|
7547
|
+
failUnexpected(err, verbose);
|
|
7548
|
+
}
|
|
7549
|
+
}
|
|
7550
|
+
);
|
|
7148
7551
|
program.on("command:*", (operands) => {
|
|
7149
7552
|
process.stderr.write(`Unknown command: ${operands.join(" ")}
|
|
7150
7553
|
`);
|
|
@@ -7156,7 +7559,7 @@ function isMainModule() {
|
|
|
7156
7559
|
const entry = process.argv[1];
|
|
7157
7560
|
if (entry === void 0) return false;
|
|
7158
7561
|
try {
|
|
7159
|
-
return (0,
|
|
7562
|
+
return (0, import_node_fs9.realpathSync)(entry) === (0, import_node_fs9.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
7160
7563
|
} catch {
|
|
7161
7564
|
return false;
|
|
7162
7565
|
}
|