@plune-ai/cli 0.27.0 → 0.29.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 +38 -6
- package/dist/cli.cjs +806 -193
- 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 +15 -2
- package/dist/reporter-core.d.cts +11 -1
- package/dist/reporter-core.d.ts +11 -1
- package/dist/reporter-core.js +14 -2
- package/package.json +4 -2
package/dist/cli.cjs
CHANGED
|
@@ -555,22 +555,22 @@ var init_prices = __esm({
|
|
|
555
555
|
});
|
|
556
556
|
|
|
557
557
|
// src/providers/cost.ts
|
|
558
|
-
function priceFrom(price,
|
|
559
|
-
return
|
|
558
|
+
function priceFrom(price, usage3) {
|
|
559
|
+
return usage3.input_tokens / 1e3 * price.input_per_1k_usd + usage3.output_tokens / 1e3 * price.output_per_1k_usd;
|
|
560
560
|
}
|
|
561
|
-
function resolveCost(
|
|
561
|
+
function resolveCost(usage3, model, reportedCostUsd, overrides, deps = {}) {
|
|
562
562
|
const table = deps.table ?? PRICE_TABLE;
|
|
563
563
|
const warn = deps.warn ?? defaultWarn;
|
|
564
564
|
const override = overrides?.[model];
|
|
565
565
|
if (override !== void 0) {
|
|
566
|
-
return priceFrom(override,
|
|
566
|
+
return priceFrom(override, usage3);
|
|
567
567
|
}
|
|
568
568
|
if (reportedCostUsd !== void 0) {
|
|
569
569
|
return reportedCostUsd;
|
|
570
570
|
}
|
|
571
571
|
const tablePrice = table[model];
|
|
572
572
|
if (tablePrice !== void 0) {
|
|
573
|
-
return priceFrom(tablePrice,
|
|
573
|
+
return priceFrom(tablePrice, usage3);
|
|
574
574
|
}
|
|
575
575
|
warn(
|
|
576
576
|
`plune: no price for model "${model}" \u2014 reporting cost_usd=0. Set pricing["${model}"] in plune.yaml to track its cost.`
|
|
@@ -622,14 +622,15 @@ 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);
|
|
629
630
|
}
|
|
630
631
|
},
|
|
631
|
-
estimateCost(
|
|
632
|
-
return { cost_usd: resolveCost(
|
|
632
|
+
estimateCost(usage3, reportedCostUsd) {
|
|
633
|
+
return { cost_usd: resolveCost(usage3, config.model, reportedCostUsd, pricing) };
|
|
633
634
|
}
|
|
634
635
|
};
|
|
635
636
|
}
|
|
@@ -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
|
|
@@ -692,8 +696,8 @@ function makeOpenAiCompatibleProvider(opts) {
|
|
|
692
696
|
throw normalizeProviderError(err, apiKey, apiKeyEnv);
|
|
693
697
|
}
|
|
694
698
|
},
|
|
695
|
-
estimateCost(
|
|
696
|
-
return { cost_usd: resolveCost(
|
|
699
|
+
estimateCost(usage3, reportedCostUsd) {
|
|
700
|
+
return { cost_usd: resolveCost(usage3, config.model, reportedCostUsd, pricing) };
|
|
697
701
|
}
|
|
698
702
|
};
|
|
699
703
|
}
|
|
@@ -796,8 +800,8 @@ function makeEstimateProvider(config, pricing) {
|
|
|
796
800
|
complete: () => Promise.reject(
|
|
797
801
|
new Error("The --dry-run provider only estimates cost; it never calls a model.")
|
|
798
802
|
),
|
|
799
|
-
estimateCost: (
|
|
800
|
-
cost_usd: resolveCost(
|
|
803
|
+
estimateCost: (usage3, reportedCostUsd) => ({
|
|
804
|
+
cost_usd: resolveCost(usage3, config.model, reportedCostUsd, pricing)
|
|
801
805
|
})
|
|
802
806
|
};
|
|
803
807
|
}
|
|
@@ -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)) {
|
|
@@ -1716,28 +1781,51 @@ async function runRow(p) {
|
|
|
1716
1781
|
const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
|
|
1717
1782
|
if (p.dryRun) {
|
|
1718
1783
|
const est = estimateUsage(prompt5, maxTokens);
|
|
1719
|
-
const
|
|
1720
|
-
return { vars: p.row.vars, output: null, cached: false, usage:
|
|
1721
|
-
}
|
|
1722
|
-
const key = cacheKey(
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1784
|
+
const usage4 = { ...est, cost_usd: p.provider.estimateCost(est).cost_usd };
|
|
1785
|
+
return { vars: p.row.vars, output: null, cached: false, usage: usage4, assertions: [] };
|
|
1786
|
+
}
|
|
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
|
-
let
|
|
1798
|
+
let usage3;
|
|
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;
|
|
1736
|
-
|
|
1819
|
+
usage3 = {
|
|
1737
1820
|
input_tokens: hit.usage.input_tokens,
|
|
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({
|
|
@@ -1748,14 +1836,16 @@ async function runRow(p) {
|
|
|
1748
1836
|
prompt_resolved: prompt5
|
|
1749
1837
|
});
|
|
1750
1838
|
output = res.output;
|
|
1751
|
-
|
|
1839
|
+
usage3 = {
|
|
1752
1840
|
input_tokens: res.usage.input_tokens,
|
|
1753
1841
|
output_tokens: res.usage.output_tokens,
|
|
1754
1842
|
// Prefer the provider-reported actual cost (res.cost_usd) when present; resolveCost still
|
|
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,
|
|
@@ -1794,13 +1884,14 @@ async function runRow(p) {
|
|
|
1794
1884
|
break;
|
|
1795
1885
|
}
|
|
1796
1886
|
}
|
|
1797
|
-
const finalUsage = { ...
|
|
1887
|
+
const finalUsage = { ...usage3, cost_usd: usage3.cost_usd + judgeCost };
|
|
1798
1888
|
return {
|
|
1799
1889
|
vars: p.row.vars,
|
|
1800
1890
|
output,
|
|
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");
|
|
@@ -2707,7 +2853,14 @@ function repoRootOf(dir, stopAt) {
|
|
|
2707
2853
|
if (here === stopAt || (0, import_node_path2.dirname)(here) === here) return void 0;
|
|
2708
2854
|
}
|
|
2709
2855
|
}
|
|
2710
|
-
function
|
|
2856
|
+
function errorContextFields(attempt, home = (0, import_node_os2.homedir)()) {
|
|
2857
|
+
const { text: text2, omitted: omitted2 } = cutErrorContext(attempt, home);
|
|
2858
|
+
return {
|
|
2859
|
+
...text2 !== "" ? { errorContext: text2 } : {},
|
|
2860
|
+
...omitted2 > 0 ? { errorContextOmitted: omitted2 } : {}
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
function cutErrorContext(attempt, home) {
|
|
2711
2864
|
const text2 = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
2712
2865
|
return cut(machineless(text2, attempt.repoRoot, home));
|
|
2713
2866
|
}
|
|
@@ -2733,7 +2886,7 @@ function pathIn(path17) {
|
|
|
2733
2886
|
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
2734
2887
|
}
|
|
2735
2888
|
function cut(text2) {
|
|
2736
|
-
if (jsonBytes(text2) <= TEXT_LIMIT) return text2;
|
|
2889
|
+
if (jsonBytes(text2) <= TEXT_LIMIT) return { text: text2, omitted: 0 };
|
|
2737
2890
|
const lines = text2.split("\n");
|
|
2738
2891
|
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
2739
2892
|
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
@@ -2742,7 +2895,8 @@ function cut(text2) {
|
|
|
2742
2895
|
let tail = lines.length;
|
|
2743
2896
|
while (head < tail && used + size[head] <= budget / 2) used += size[head++];
|
|
2744
2897
|
while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
|
|
2745
|
-
|
|
2898
|
+
const kept = [...lines.slice(0, head), ...lines.slice(tail)].join("\n");
|
|
2899
|
+
return { text: [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n"), omitted: text2.length - kept.length };
|
|
2746
2900
|
}
|
|
2747
2901
|
function chosen({ status, errors }) {
|
|
2748
2902
|
if (status === "timedOut") {
|
|
@@ -3903,7 +4057,7 @@ async function handleRunStart(options = {}) {
|
|
|
3903
4057
|
kind: options.kind ?? "automated",
|
|
3904
4058
|
externalKey,
|
|
3905
4059
|
// The same name the reporter would give it; `PLUNE_RUN_TITLE` replaces it, as everywhere.
|
|
3906
|
-
title: env.config.title ?? defaultRunTitle()
|
|
4060
|
+
title: options.name ?? env.config.title ?? defaultRunTitle()
|
|
3907
4061
|
});
|
|
3908
4062
|
if (!out.ok) throw new RunCommandError(`Could not start the run \u2014 ${out.detail || out.kind}.`);
|
|
3909
4063
|
const result = {
|
|
@@ -4122,6 +4276,7 @@ var init_run_result_schema = __esm({
|
|
|
4122
4276
|
latency_ms: import_zod3.z.number().optional(),
|
|
4123
4277
|
error: runErrorSchema.optional(),
|
|
4124
4278
|
assertions: import_zod3.z.array(assertionResultRecordSchema2),
|
|
4279
|
+
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(),
|
|
4125
4280
|
/** ADR 0045. Optional and additive — the same enum the platform validates on `POST /v1/runs`. */
|
|
4126
4281
|
inconclusive: import_zod3.z.enum(ROW_INCONCLUSIVE).optional()
|
|
4127
4282
|
});
|
|
@@ -4134,7 +4289,14 @@ var init_run_result_schema = __esm({
|
|
|
4134
4289
|
blocked: import_zod3.z.number().optional(),
|
|
4135
4290
|
skipped: import_zod3.z.number().optional(),
|
|
4136
4291
|
cost_usd: import_zod3.z.number(),
|
|
4137
|
-
duration_ms: import_zod3.z.number()
|
|
4292
|
+
duration_ms: import_zod3.z.number(),
|
|
4293
|
+
cache: import_zod3.z.object({
|
|
4294
|
+
from_cache: import_zod3.z.number(),
|
|
4295
|
+
from_model: import_zod3.z.number(),
|
|
4296
|
+
missed: import_zod3.z.number(),
|
|
4297
|
+
errored: import_zod3.z.number(),
|
|
4298
|
+
saved: import_zod3.z.object({ input_tokens: import_zod3.z.number(), output_tokens: import_zod3.z.number(), cost_usd: import_zod3.z.number() })
|
|
4299
|
+
}).optional()
|
|
4138
4300
|
});
|
|
4139
4301
|
evalResultSchema = import_zod3.z.object({
|
|
4140
4302
|
id: import_zod3.z.string(),
|
|
@@ -4309,6 +4471,7 @@ __export(plan_exports, {
|
|
|
4309
4471
|
grepFragment: () => grepFragment,
|
|
4310
4472
|
handlePlanGrep: () => handlePlanGrep,
|
|
4311
4473
|
handleRunGrep: () => handleRunGrep,
|
|
4474
|
+
platformGet: () => platformGet,
|
|
4312
4475
|
runGrepSummary: () => runGrepSummary
|
|
4313
4476
|
});
|
|
4314
4477
|
function grepFragment(c) {
|
|
@@ -4853,7 +5016,7 @@ function pendingOf(r, retry, ctx) {
|
|
|
4853
5016
|
})),
|
|
4854
5017
|
testFile: ""
|
|
4855
5018
|
};
|
|
4856
|
-
const errorContext =
|
|
5019
|
+
const errorContext = errorContextFields(attempt);
|
|
4857
5020
|
const failure = FAILED.has(status) ? failureOf(attempt) : void 0;
|
|
4858
5021
|
const pkg2 = labelOf(r, "package");
|
|
4859
5022
|
const where = (key.includes("#") ? key.split("#")[0] : pkg2)?.replace(/^[A-Za-z][A-Za-z0-9_-]+:(?![\\/])/, "");
|
|
@@ -4877,7 +5040,7 @@ function pendingOf(r, retry, ctx) {
|
|
|
4877
5040
|
retry
|
|
4878
5041
|
}
|
|
4879
5042
|
} : {},
|
|
4880
|
-
...errorContext
|
|
5043
|
+
...errorContext,
|
|
4881
5044
|
...failure !== void 0 ? { failure } : {},
|
|
4882
5045
|
...files.length > 0 ? { files } : {}
|
|
4883
5046
|
};
|
|
@@ -5207,7 +5370,7 @@ function pendingFrom(spec, test, result, file, titles, ctx) {
|
|
|
5207
5370
|
const stated = statedIdOf(test, spec.title ?? "");
|
|
5208
5371
|
const expected = test.expectedStatus;
|
|
5209
5372
|
const attempt = attemptOf(result, file, ctx);
|
|
5210
|
-
const errorContext =
|
|
5373
|
+
const errorContext = errorContextFields(attempt);
|
|
5211
5374
|
const failure = NOT_FAILED.has(attempt.status) ? void 0 : failureOf(attempt);
|
|
5212
5375
|
const files = filesOf(attempt);
|
|
5213
5376
|
const keys = [
|
|
@@ -5237,7 +5400,7 @@ function pendingFrom(spec, test, result, file, titles, ctx) {
|
|
|
5237
5400
|
...result.workerIndex !== void 0 ? { worker: String(result.workerIndex) } : {}
|
|
5238
5401
|
}
|
|
5239
5402
|
} : {},
|
|
5240
|
-
...errorContext
|
|
5403
|
+
...errorContext,
|
|
5241
5404
|
...failure !== void 0 ? { failure } : {},
|
|
5242
5405
|
...files.length > 0 ? { files } : {}
|
|
5243
5406
|
};
|
|
@@ -5420,6 +5583,7 @@ async function handleRunImport(options) {
|
|
|
5420
5583
|
token,
|
|
5421
5584
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
5422
5585
|
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
5586
|
+
...options.name !== void 0 ? { title: options.name } : {},
|
|
5423
5587
|
...options.create === true ? { offerDiscovered: true } : {},
|
|
5424
5588
|
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
5425
5589
|
kind: "automated",
|
|
@@ -6353,12 +6517,18 @@ __export(requirements_import_exports, {
|
|
|
6353
6517
|
MAX_MARKDOWN_BYTES: () => MAX_MARKDOWN_BYTES,
|
|
6354
6518
|
RequirementsImportError: () => RequirementsImportError,
|
|
6355
6519
|
buildImportBody: () => buildImportBody,
|
|
6520
|
+
cut: () => cut2,
|
|
6521
|
+
flat: () => flat,
|
|
6356
6522
|
formatDiscover: () => formatDiscover,
|
|
6357
6523
|
formatReport: () => formatReport2,
|
|
6358
6524
|
handleRequirementsImport: () => handleRequirementsImport,
|
|
6359
6525
|
parseMap: () => parseMap,
|
|
6360
6526
|
parseTableSpec: () => parseTableSpec,
|
|
6361
|
-
|
|
6527
|
+
plural: () => plural,
|
|
6528
|
+
readMarkdown: () => readMarkdown,
|
|
6529
|
+
refusal: () => refusal,
|
|
6530
|
+
reportRequirementsImportFailure: () => reportRequirementsImportFailure,
|
|
6531
|
+
resolveProject: () => resolveProject
|
|
6362
6532
|
});
|
|
6363
6533
|
function parseMap(flag2, raw) {
|
|
6364
6534
|
const out = {};
|
|
@@ -6645,6 +6815,321 @@ var init_requirements_import = __esm({
|
|
|
6645
6815
|
}
|
|
6646
6816
|
});
|
|
6647
6817
|
|
|
6818
|
+
// src/cli/commands/requirements-links.ts
|
|
6819
|
+
var requirements_links_exports = {};
|
|
6820
|
+
__export(requirements_links_exports, {
|
|
6821
|
+
buildLinksBody: () => buildLinksBody,
|
|
6822
|
+
formatLinks: () => formatLinks,
|
|
6823
|
+
formatLinksDiscover: () => formatLinksDiscover,
|
|
6824
|
+
handleRequirementsLinks: () => handleRequirementsLinks
|
|
6825
|
+
});
|
|
6826
|
+
function buildLinksBody(f) {
|
|
6827
|
+
if (f.table === void 0 && f.requirementColumn === void 0 && f.testColumn === void 0 && f.separator === void 0) return null;
|
|
6828
|
+
if (f.table === void 0) return usage2("--requirement-column, --test-column and --separator need --table <n>. Run without flags to see which tables the file has.");
|
|
6829
|
+
if (!/^\d+$/.test(f.table)) usage2(`--table is the number of the table in the list (0, 1, ...), not "${f.table}".`);
|
|
6830
|
+
if (f.requirementColumn === void 0 || f.requirementColumn.trim() === "") usage2("--table needs --requirement-column <header> (the column with the requirement keys).");
|
|
6831
|
+
if (f.testColumn === void 0 || f.testColumn.trim() === "") usage2("--table needs --test-column <header> (the column with the test titles or paths).");
|
|
6832
|
+
const body = { table: Number(f.table), requirementColumn: f.requirementColumn, testColumn: f.testColumn };
|
|
6833
|
+
if (f.separator !== void 0) {
|
|
6834
|
+
if (f.separator.length < 1 || f.separator.length > 3) usage2(`--separator is 1 to 3 characters, not "${f.separator}".`);
|
|
6835
|
+
body["requirementSeparator"] = f.separator;
|
|
6836
|
+
}
|
|
6837
|
+
return body;
|
|
6838
|
+
}
|
|
6839
|
+
async function handleRequirementsLinks(deps) {
|
|
6840
|
+
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
6841
|
+
const mapping = buildLinksBody(deps);
|
|
6842
|
+
const markdown = readMarkdown(deps.cwd ?? process.cwd(), deps.file);
|
|
6843
|
+
const token = (deps.loadToken ?? loadToken)();
|
|
6844
|
+
if (token === null) throw new RequirementsImportError('Not logged in. Run "plune login" first, or set PLUNE_TOKEN.', 2);
|
|
6845
|
+
const client = createClient({ apiUrl, token, ...deps.fetchImpl !== void 0 ? { fetchImpl: deps.fetchImpl } : {} });
|
|
6846
|
+
const projectId = await resolveProject(client, deps.project, apiUrl);
|
|
6847
|
+
const base = `/v1/projects/${encodeURIComponent(projectId)}/requirements/import`;
|
|
6848
|
+
if (mapping === null) {
|
|
6849
|
+
const out2 = await client.post(`${base}/discover`, { markdown });
|
|
6850
|
+
if (!out2.ok) throw refusal(out2, apiUrl, projectId);
|
|
6851
|
+
return { step: "discover", result: out2.body };
|
|
6852
|
+
}
|
|
6853
|
+
const out = await client.post(`${base}/links`, { markdown, dryRun: deps.yes !== true, ...mapping });
|
|
6854
|
+
if (!out.ok) throw refusal(out, apiUrl, projectId);
|
|
6855
|
+
return { step: "links", report: out.body };
|
|
6856
|
+
}
|
|
6857
|
+
function formatLinksDiscover(file, r) {
|
|
6858
|
+
const out = [`${file}: ${plural(r.tables.length, "table")}`, ""];
|
|
6859
|
+
if (r.tables.length === 0) out.push(" (none)");
|
|
6860
|
+
for (const t of r.tables) {
|
|
6861
|
+
const mark = t.unreadable !== void 0 ? ` cannot be read: ${t.unreadable}` : "";
|
|
6862
|
+
out.push(` #${t.index} line ${t.line} ${t.heading === null ? "(no heading)" : `"${flat(t.heading)}"`} ${cut2(t.headers.map(flat).join(" | "), 100)} ${plural(t.rowCount, "row")}${mark}`);
|
|
6863
|
+
}
|
|
6864
|
+
if (r.truncated) out.push("", "The file has more tables than are listed here.");
|
|
6865
|
+
out.push("", "Pick the matrix and name its two columns (a dry run, nothing is written):", ` plune requirements links ${file} --table <n> --requirement-column <header> --test-column <header>`);
|
|
6866
|
+
return out.join("\n") + "\n";
|
|
6867
|
+
}
|
|
6868
|
+
function formatLinks(report) {
|
|
6869
|
+
const s = report.summary;
|
|
6870
|
+
const out = [`link ${s.link} \xB7 unchanged ${s.unchanged} \xB7 unmatched ${s.unmatched}`];
|
|
6871
|
+
for (const action of ["unmatched", "link", "unchanged"]) {
|
|
6872
|
+
const rows = report.items.filter((i) => i.action === action);
|
|
6873
|
+
if (rows.length === 0) continue;
|
|
6874
|
+
out.push("", `${action} (${rows.length})`);
|
|
6875
|
+
for (const i of rows.slice(0, SHOWN2)) {
|
|
6876
|
+
out.push(` line ${i.line} ${i.key === "" ? "-" : i.key} -> ${i.test === "" ? "-" : cut2(flat(i.test), 80)} - ${flat(i.reason)}`);
|
|
6877
|
+
}
|
|
6878
|
+
if (rows.length > SHOWN2) out.push(` ... and ${rows.length - SHOWN2} more (--json lists every row)`);
|
|
6879
|
+
}
|
|
6880
|
+
out.push("", report.applied ? `applied \u2014 ${plural(s.link, "link")} created.` : "dry run \u2014 nothing was written; add --yes to apply");
|
|
6881
|
+
return out.join("\n") + "\n";
|
|
6882
|
+
}
|
|
6883
|
+
var usage2, SHOWN2;
|
|
6884
|
+
var init_requirements_links = __esm({
|
|
6885
|
+
"src/cli/commands/requirements-links.ts"() {
|
|
6886
|
+
"use strict";
|
|
6887
|
+
init_cjs_shims();
|
|
6888
|
+
init_client();
|
|
6889
|
+
init_api_url();
|
|
6890
|
+
init_credentials();
|
|
6891
|
+
init_requirements_import();
|
|
6892
|
+
usage2 = (message) => {
|
|
6893
|
+
throw new RequirementsImportError(message, 2);
|
|
6894
|
+
};
|
|
6895
|
+
SHOWN2 = 50;
|
|
6896
|
+
}
|
|
6897
|
+
});
|
|
6898
|
+
|
|
6899
|
+
// src/cli/commands/verify.ts
|
|
6900
|
+
var verify_exports = {};
|
|
6901
|
+
__export(verify_exports, {
|
|
6902
|
+
compare: () => compare,
|
|
6903
|
+
formatVerify: () => formatVerify,
|
|
6904
|
+
handleVerify: () => handleVerify,
|
|
6905
|
+
loadSources: () => loadSources,
|
|
6906
|
+
testsOfListing: () => testsOfListing
|
|
6907
|
+
});
|
|
6908
|
+
function testsOfListing(listing) {
|
|
6909
|
+
const found = /* @__PURE__ */ new Map();
|
|
6910
|
+
const walk2 = (suite, titles) => {
|
|
6911
|
+
for (const spec of suite.specs ?? []) {
|
|
6912
|
+
const annotated = spec.tests?.flatMap((t) => t.annotations ?? []).find(
|
|
6913
|
+
(a) => a.type === "PluneId" && a.description !== void 0 && a.description !== ""
|
|
6914
|
+
)?.description;
|
|
6915
|
+
const id = annotated ?? TOKEN4.exec(spec.title)?.[1];
|
|
6916
|
+
const pathTitle = [...titles, spec.title].join("#");
|
|
6917
|
+
if (!found.has(pathTitle))
|
|
6918
|
+
found.set(pathTitle, { pathTitle, ...id === void 0 ? {} : { id } });
|
|
6919
|
+
}
|
|
6920
|
+
for (const child of suite.suites ?? []) walk2(child, [...titles, child.title]);
|
|
6921
|
+
};
|
|
6922
|
+
for (const file of listing.suites ?? []) walk2(file, [file.title.replace(/\\/g, "/")]);
|
|
6923
|
+
return [...found.values()];
|
|
6924
|
+
}
|
|
6925
|
+
function compare(code, cases) {
|
|
6926
|
+
const live = cases.filter((c) => c.type === "automated" && c.state !== "deprecated");
|
|
6927
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6928
|
+
for (const c of live)
|
|
6929
|
+
for (const k of c.externalKeys ?? []) if (k.kind === "path-title") byPath.set(k.value, c);
|
|
6930
|
+
const byId = new Map(live.map((c) => [c.id, c]));
|
|
6931
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6932
|
+
const onlyInCode = [];
|
|
6933
|
+
for (const t of code) {
|
|
6934
|
+
const hit = (t.id === void 0 ? void 0 : byId.get(t.id)) ?? byPath.get(t.pathTitle);
|
|
6935
|
+
if (hit === void 0) onlyInCode.push({ pathTitle: t.pathTitle });
|
|
6936
|
+
else seen.add(hit.id);
|
|
6937
|
+
}
|
|
6938
|
+
return {
|
|
6939
|
+
matched: seen.size,
|
|
6940
|
+
onlyInPlatform: live.filter((c) => !seen.has(c.id)).map((c) => ({ id: c.id, title: c.title, state: c.state, detached: c.state === "detached" })),
|
|
6941
|
+
onlyInCode
|
|
6942
|
+
};
|
|
6943
|
+
}
|
|
6944
|
+
function runPlaywrightList(args, cwd) {
|
|
6945
|
+
try {
|
|
6946
|
+
return (0, import_node_child_process3.execFileSync)("npx", ["playwright", "test", ...args], {
|
|
6947
|
+
cwd,
|
|
6948
|
+
encoding: "utf8",
|
|
6949
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
6950
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6951
|
+
shell: process.platform === "win32"
|
|
6952
|
+
});
|
|
6953
|
+
} catch (err) {
|
|
6954
|
+
const stderr = err.stderr?.trim();
|
|
6955
|
+
throw new SyncFileError(`could not list the Playwright tests${stderr ? `: ${stderr}` : ""}`);
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
async function loadSources(deps) {
|
|
6959
|
+
const get = platformGet(deps);
|
|
6960
|
+
let raw;
|
|
6961
|
+
if (deps.from !== void 0) {
|
|
6962
|
+
try {
|
|
6963
|
+
raw = (0, import_node_fs8.readFileSync)(deps.from, "utf8");
|
|
6964
|
+
} catch {
|
|
6965
|
+
throw new SyncFileError(`cannot read ${deps.from}`);
|
|
6966
|
+
}
|
|
6967
|
+
} else {
|
|
6968
|
+
const args = [
|
|
6969
|
+
"--list",
|
|
6970
|
+
"--reporter=json",
|
|
6971
|
+
...deps.playwrightConfig === void 0 ? [] : ["--config", deps.playwrightConfig]
|
|
6972
|
+
];
|
|
6973
|
+
raw = (deps.list ?? runPlaywrightList)(args, deps.cwd ?? process.cwd());
|
|
6974
|
+
}
|
|
6975
|
+
let listing;
|
|
6976
|
+
try {
|
|
6977
|
+
listing = JSON.parse(raw);
|
|
6978
|
+
} catch {
|
|
6979
|
+
throw new SyncFileError("the Playwright listing is not JSON");
|
|
6980
|
+
}
|
|
6981
|
+
const cases = [];
|
|
6982
|
+
let cursor = null;
|
|
6983
|
+
do {
|
|
6984
|
+
const page = await get(
|
|
6985
|
+
`/v1/test-cases?type=automated&stability=0&limit=200${cursor === null ? "" : `&cursor=${cursor}`}`
|
|
6986
|
+
);
|
|
6987
|
+
cases.push(...page.testCases);
|
|
6988
|
+
cursor = page.nextCursor;
|
|
6989
|
+
} while (cursor !== null);
|
|
6990
|
+
return { code: testsOfListing(listing), cases };
|
|
6991
|
+
}
|
|
6992
|
+
async function handleVerify(deps) {
|
|
6993
|
+
const { code, cases } = await loadSources(deps);
|
|
6994
|
+
return compare(code, cases);
|
|
6995
|
+
}
|
|
6996
|
+
function formatVerify(r) {
|
|
6997
|
+
const lines = [];
|
|
6998
|
+
if (r.onlyInPlatform.length > 0) {
|
|
6999
|
+
lines.push(`In Plune, not in the code (${r.onlyInPlatform.length}):`);
|
|
7000
|
+
for (const c of r.onlyInPlatform) {
|
|
7001
|
+
lines.push(
|
|
7002
|
+
` ${c.id} ${c.title} [${c.detached ? "already detached" : `${c.state} \u2014 a full run would detach it`}]`
|
|
7003
|
+
);
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
if (r.onlyInCode.length > 0) {
|
|
7007
|
+
lines.push(`In the code, not in Plune (${r.onlyInCode.length}):`);
|
|
7008
|
+
for (const t of r.onlyInCode) lines.push(` ${t.pathTitle}`);
|
|
7009
|
+
}
|
|
7010
|
+
lines.push(
|
|
7011
|
+
r.onlyInPlatform.length + r.onlyInCode.length === 0 ? `No drift: ${r.matched} case(s) match the code.` : `${r.matched} matched. Nothing was changed.`
|
|
7012
|
+
);
|
|
7013
|
+
return lines.join("\n") + "\n";
|
|
7014
|
+
}
|
|
7015
|
+
var import_node_child_process3, import_node_fs8, TOKEN4;
|
|
7016
|
+
var init_verify = __esm({
|
|
7017
|
+
"src/cli/commands/verify.ts"() {
|
|
7018
|
+
"use strict";
|
|
7019
|
+
init_cjs_shims();
|
|
7020
|
+
import_node_child_process3 = require("child_process");
|
|
7021
|
+
import_node_fs8 = require("fs");
|
|
7022
|
+
init_plan();
|
|
7023
|
+
init_sync();
|
|
7024
|
+
TOKEN4 = /@P([A-Za-z0-9_-]+)/;
|
|
7025
|
+
}
|
|
7026
|
+
});
|
|
7027
|
+
|
|
7028
|
+
// src/cli/commands/ids.ts
|
|
7029
|
+
var ids_exports = {};
|
|
7030
|
+
__export(ids_exports, {
|
|
7031
|
+
DEFAULT_IDS_FILE: () => DEFAULT_IDS_FILE,
|
|
7032
|
+
formatIds: () => formatIds,
|
|
7033
|
+
handleIds: () => handleIds,
|
|
7034
|
+
planIds: () => planIds,
|
|
7035
|
+
readMap: () => readMap,
|
|
7036
|
+
serializeMap: () => serializeMap
|
|
7037
|
+
});
|
|
7038
|
+
function readMap(file) {
|
|
7039
|
+
if (!(0, import_node_fs9.existsSync)(file)) return {};
|
|
7040
|
+
let parsed;
|
|
7041
|
+
try {
|
|
7042
|
+
parsed = JSON.parse((0, import_node_fs9.readFileSync)(file, "utf8"));
|
|
7043
|
+
} catch {
|
|
7044
|
+
throw new SyncFileError(`${file} is not valid JSON; fix or remove it, nothing was written`);
|
|
7045
|
+
}
|
|
7046
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || Object.values(parsed).some((v) => typeof v !== "string" || v === ""))
|
|
7047
|
+
throw new SyncFileError(
|
|
7048
|
+
`${file} must be an object of path-title to case id strings; nothing was written`
|
|
7049
|
+
);
|
|
7050
|
+
return parsed;
|
|
7051
|
+
}
|
|
7052
|
+
function planIds(code, cases, current) {
|
|
7053
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
7054
|
+
for (const c of cases)
|
|
7055
|
+
if (c.type === "automated" && c.state !== "deprecated") {
|
|
7056
|
+
for (const k of c.externalKeys ?? []) if (k.kind === "path-title") byPath.set(k.value, c);
|
|
7057
|
+
}
|
|
7058
|
+
const added = {};
|
|
7059
|
+
const noCase = [];
|
|
7060
|
+
let covered = 0;
|
|
7061
|
+
for (const t of code) {
|
|
7062
|
+
if (t.id !== void 0 || current[t.pathTitle] !== void 0) covered++;
|
|
7063
|
+
else {
|
|
7064
|
+
const hit = byPath.get(t.pathTitle);
|
|
7065
|
+
if (hit === void 0) noCase.push(t.pathTitle);
|
|
7066
|
+
else added[t.pathTitle] = hit.id;
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
const inCode = new Set(code.map((t) => t.pathTitle));
|
|
7070
|
+
return {
|
|
7071
|
+
added,
|
|
7072
|
+
covered,
|
|
7073
|
+
noCase: noCase.sort(compareKeys),
|
|
7074
|
+
orphaned: Object.keys(current).filter((k) => !inCode.has(k)).sort(compareKeys)
|
|
7075
|
+
};
|
|
7076
|
+
}
|
|
7077
|
+
function serializeMap(map) {
|
|
7078
|
+
const sorted = {};
|
|
7079
|
+
for (const k of Object.keys(map).sort(compareKeys)) sorted[k] = map[k];
|
|
7080
|
+
return JSON.stringify(sorted, null, 2) + "\n";
|
|
7081
|
+
}
|
|
7082
|
+
async function handleIds(deps) {
|
|
7083
|
+
const file = deps.file ?? DEFAULT_IDS_FILE;
|
|
7084
|
+
const current = readMap(file);
|
|
7085
|
+
const { code, cases } = await loadSources(deps);
|
|
7086
|
+
const plan = planIds(code, cases, current);
|
|
7087
|
+
let written = false;
|
|
7088
|
+
if (deps.write === true && Object.keys(plan.added).length > 0) {
|
|
7089
|
+
try {
|
|
7090
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path5.dirname)(file), { recursive: true });
|
|
7091
|
+
(0, import_node_fs9.writeFileSync)(file, serializeMap({ ...current, ...plan.added }));
|
|
7092
|
+
} catch {
|
|
7093
|
+
throw new SyncFileError(`cannot write ${file}`);
|
|
7094
|
+
}
|
|
7095
|
+
written = true;
|
|
7096
|
+
}
|
|
7097
|
+
return { file, ...plan, written };
|
|
7098
|
+
}
|
|
7099
|
+
function formatIds(r, write) {
|
|
7100
|
+
const n = Object.keys(r.added).length;
|
|
7101
|
+
const lines = [];
|
|
7102
|
+
if (n > 0) {
|
|
7103
|
+
lines.push(`${write ? "Added to" : "Would add to"} ${r.file} (${n}):`);
|
|
7104
|
+
for (const k of Object.keys(r.added).sort(compareKeys)) lines.push(` ${k} -> ${r.added[k]}`);
|
|
7105
|
+
}
|
|
7106
|
+
if (r.noCase.length > 0) {
|
|
7107
|
+
lines.push(`In the code, no case in Plune yet, nothing to write (${r.noCase.length}):`);
|
|
7108
|
+
for (const k of r.noCase) lines.push(` ${k}`);
|
|
7109
|
+
}
|
|
7110
|
+
if (r.orphaned.length > 0) {
|
|
7111
|
+
lines.push(`In ${r.file}, test no longer in the code, kept (${r.orphaned.length}):`);
|
|
7112
|
+
for (const k of r.orphaned) lines.push(` ${k}`);
|
|
7113
|
+
}
|
|
7114
|
+
lines.push(
|
|
7115
|
+
n === 0 ? `Nothing to write: ${r.covered} test(s) already have an id.` : write ? `${r.covered} already had an id. ${r.file} written.` : `${r.covered} already had an id. Dry run: nothing was written; add --write.`
|
|
7116
|
+
);
|
|
7117
|
+
return lines.join("\n") + "\n";
|
|
7118
|
+
}
|
|
7119
|
+
var import_node_fs9, import_node_path5, DEFAULT_IDS_FILE, compareKeys;
|
|
7120
|
+
var init_ids = __esm({
|
|
7121
|
+
"src/cli/commands/ids.ts"() {
|
|
7122
|
+
"use strict";
|
|
7123
|
+
init_cjs_shims();
|
|
7124
|
+
import_node_fs9 = require("fs");
|
|
7125
|
+
import_node_path5 = require("path");
|
|
7126
|
+
init_verify();
|
|
7127
|
+
init_sync();
|
|
7128
|
+
DEFAULT_IDS_FILE = "plune/ids.json";
|
|
7129
|
+
compareKeys = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
7130
|
+
}
|
|
7131
|
+
});
|
|
7132
|
+
|
|
6648
7133
|
// src/cli.ts
|
|
6649
7134
|
var cli_exports = {};
|
|
6650
7135
|
__export(cli_exports, {
|
|
@@ -6654,11 +7139,25 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
6654
7139
|
init_cjs_shims();
|
|
6655
7140
|
init_api_url();
|
|
6656
7141
|
var import_commander = require("commander");
|
|
6657
|
-
var
|
|
6658
|
-
var
|
|
7142
|
+
var import_node_fs10 = require("fs");
|
|
7143
|
+
var import_node_path6 = require("path");
|
|
6659
7144
|
var import_node_url = require("url");
|
|
6660
|
-
|
|
6661
|
-
|
|
7145
|
+
|
|
7146
|
+
// src/util/duration.ts
|
|
7147
|
+
init_cjs_shims();
|
|
7148
|
+
var UNIT_MS = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
7149
|
+
function parseDuration(text2) {
|
|
7150
|
+
const m = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(text2.trim());
|
|
7151
|
+
const ms = m === null ? NaN : Number(m[1]) * UNIT_MS[m[2] ?? "ms"];
|
|
7152
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
7153
|
+
throw new Error(`Invalid duration "${text2}" (use e.g. 90s, 15m, 12h, 7d)`);
|
|
7154
|
+
}
|
|
7155
|
+
return ms;
|
|
7156
|
+
}
|
|
7157
|
+
|
|
7158
|
+
// src/cli.ts
|
|
7159
|
+
var __dirname = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
7160
|
+
var pkg = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
6662
7161
|
function toFormat(s) {
|
|
6663
7162
|
return s === "json" || s === "markdown" ? s : "console";
|
|
6664
7163
|
}
|
|
@@ -6667,7 +7166,7 @@ function shouldColor(outputPath) {
|
|
|
6667
7166
|
}
|
|
6668
7167
|
function writeReport(text2, outputPath) {
|
|
6669
7168
|
if (outputPath !== void 0) {
|
|
6670
|
-
(0,
|
|
7169
|
+
(0, import_node_fs10.writeFileSync)(outputPath, text2);
|
|
6671
7170
|
} else {
|
|
6672
7171
|
process.stdout.write(text2 + "\n");
|
|
6673
7172
|
}
|
|
@@ -6712,7 +7211,7 @@ function createProgram() {
|
|
|
6712
7211
|
program.hook("preAction", async (_program, action) => {
|
|
6713
7212
|
const { loadEnv: loadEnv2 } = await Promise.resolve().then(() => (init_env(), env_exports));
|
|
6714
7213
|
const { config } = action.optsWithGlobals();
|
|
6715
|
-
if (config !== void 0) loadEnv2((0,
|
|
7214
|
+
if (config !== void 0) loadEnv2((0, import_node_path6.dirname)((0, import_node_path6.resolve)(config)));
|
|
6716
7215
|
loadEnv2(process.cwd());
|
|
6717
7216
|
});
|
|
6718
7217
|
const runCommand = program.command("run").description("Run assertions against a dataset").option("--dry-run", "Estimate cost/tokens; do not call the model", false).option("--config <path>", "Path to plune.yaml config file").option(
|
|
@@ -6720,13 +7219,17 @@ function createProgram() {
|
|
|
6720
7219
|
"Run a subset by eval id or tag (repeatable)",
|
|
6721
7220
|
(val, prev) => [...prev, val],
|
|
6722
7221
|
[]
|
|
6723
|
-
).option("--concurrency <n>", "Override provider concurrency", (v) => parseInt(v, 10)).option("--no-cache", "Bypass the local cache for this run").option(
|
|
7222
|
+
).option("--concurrency <n>", "Override provider concurrency", (v) => parseInt(v, 10)).option("--no-cache", "Bypass the local cache for this run").option(
|
|
7223
|
+
"--cache-strict",
|
|
7224
|
+
"Every row must be in the cache: no model calls for completions; a miss ends the run with exit 5 and lists the rows",
|
|
7225
|
+
false
|
|
7226
|
+
).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(
|
|
6724
7227
|
async (options, command) => {
|
|
6725
7228
|
const globals = command.optsWithGlobals();
|
|
6726
7229
|
const configPath = globals.config;
|
|
6727
7230
|
const [
|
|
6728
7231
|
{ handleRun: handleRun2 },
|
|
6729
|
-
{ exitCodeFor: exitCodeFor2, RunConfigError: RunConfigError2 },
|
|
7232
|
+
{ exitCodeFor: exitCodeFor2, cacheMissRows: cacheMissRows2, CACHE_STRICT_EXIT: CACHE_STRICT_EXIT2, RunConfigError: RunConfigError2 },
|
|
6730
7233
|
{ renderReport: renderReport2 },
|
|
6731
7234
|
{ ConfigNotFoundError: ConfigNotFoundError2, ConfigValidationError: ConfigValidationError2, YamlParseError: YamlParseError2 },
|
|
6732
7235
|
{ AuthError: AuthError2 }
|
|
@@ -6738,13 +7241,36 @@ function createProgram() {
|
|
|
6738
7241
|
Promise.resolve().then(() => (init_errors2(), errors_exports2))
|
|
6739
7242
|
]);
|
|
6740
7243
|
try {
|
|
7244
|
+
if (options.cache === false && options.cacheMaxAge !== void 0) {
|
|
7245
|
+
process.stderr.write("--no-cache and --cache-max-age cannot be used together\n");
|
|
7246
|
+
process.exit(2);
|
|
7247
|
+
return;
|
|
7248
|
+
}
|
|
7249
|
+
if (options.cache === false && options.cacheStrict) {
|
|
7250
|
+
process.stderr.write("--no-cache and --cache-strict cannot be used together\n");
|
|
7251
|
+
process.exit(2);
|
|
7252
|
+
return;
|
|
7253
|
+
}
|
|
7254
|
+
let cacheMaxAgeMs;
|
|
7255
|
+
if (options.cacheMaxAge !== void 0) {
|
|
7256
|
+
try {
|
|
7257
|
+
cacheMaxAgeMs = parseDuration(options.cacheMaxAge);
|
|
7258
|
+
} catch (err) {
|
|
7259
|
+
process.stderr.write(`--cache-max-age: ${err.message}
|
|
7260
|
+
`);
|
|
7261
|
+
process.exit(2);
|
|
7262
|
+
return;
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
6741
7265
|
const result = await handleRun2({
|
|
6742
7266
|
dryRun: options.dryRun,
|
|
6743
7267
|
...configPath !== void 0 ? { configPath } : {},
|
|
6744
7268
|
...options.only.length > 0 ? { only: options.only } : {},
|
|
6745
7269
|
...typeof options.concurrency === "number" && Number.isFinite(options.concurrency) ? { concurrency: options.concurrency } : {},
|
|
6746
7270
|
noCache: options.cache === false,
|
|
6747
|
-
bail: options.bail
|
|
7271
|
+
bail: options.bail,
|
|
7272
|
+
...options.cacheStrict ? { cacheStrict: true } : {},
|
|
7273
|
+
...cacheMaxAgeMs !== void 0 ? { cacheMaxAgeMs } : {}
|
|
6748
7274
|
});
|
|
6749
7275
|
if (options.only.length > 0 && result.evals.length === 0) {
|
|
6750
7276
|
process.stderr.write("No evals matched the selector.\n");
|
|
@@ -6754,6 +7280,15 @@ function createProgram() {
|
|
|
6754
7280
|
});
|
|
6755
7281
|
writeReport(text2, options.output);
|
|
6756
7282
|
process.exitCode = exitCodeFor2(result);
|
|
7283
|
+
const missed = cacheMissRows2(result);
|
|
7284
|
+
if (missed.length > 0) {
|
|
7285
|
+
process.stderr.write(
|
|
7286
|
+
`--cache-strict: ${missed.length} row(s) have no cache entry:
|
|
7287
|
+
${missed.map((m) => ` - ${m}`).join("\n")}
|
|
7288
|
+
`
|
|
7289
|
+
);
|
|
7290
|
+
process.exitCode = CACHE_STRICT_EXIT2;
|
|
7291
|
+
}
|
|
6757
7292
|
} catch (err) {
|
|
6758
7293
|
const verbose = globals.verbose === true;
|
|
6759
7294
|
if (err instanceof ConfigValidationError2) {
|
|
@@ -6803,11 +7338,12 @@ function createProgram() {
|
|
|
6803
7338
|
};
|
|
6804
7339
|
const verboseOf = (command) => command.optsWithGlobals().verbose === true;
|
|
6805
7340
|
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";
|
|
6806
|
-
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) => {
|
|
7341
|
+
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) => {
|
|
6807
7342
|
try {
|
|
6808
7343
|
const { handleRunStart: handleRunStart2 } = await Promise.resolve().then(() => (init_run_lifecycle(), run_lifecycle_exports));
|
|
6809
7344
|
await handleRunStart2({
|
|
6810
7345
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7346
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6811
7347
|
json: options.json
|
|
6812
7348
|
});
|
|
6813
7349
|
} catch (err) {
|
|
@@ -6831,12 +7367,13 @@ function createProgram() {
|
|
|
6831
7367
|
);
|
|
6832
7368
|
runCommand.command("exec").argument("<command...>", "The command to run inside the run, after `--`").description(
|
|
6833
7369
|
"Open a run, run a command inside it, close the run \u2014 whatever the command returns"
|
|
6834
|
-
).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) => {
|
|
7370
|
+
).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) => {
|
|
6835
7371
|
try {
|
|
6836
7372
|
const { handleRunExec: handleRunExec2 } = await Promise.resolve().then(() => (init_run_lifecycle(), run_lifecycle_exports));
|
|
6837
7373
|
process.exitCode = await handleRunExec2({
|
|
6838
7374
|
argv,
|
|
6839
7375
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7376
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6840
7377
|
...options.failOnFailures ? { failOnFailures: true } : {}
|
|
6841
7378
|
});
|
|
6842
7379
|
} catch (err) {
|
|
@@ -6888,7 +7425,7 @@ function createProgram() {
|
|
|
6888
7425
|
await failRunCommand(err, verboseOf(command));
|
|
6889
7426
|
}
|
|
6890
7427
|
});
|
|
6891
|
-
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(
|
|
7428
|
+
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(
|
|
6892
7429
|
"--fail-on-missing-attachments",
|
|
6893
7430
|
"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",
|
|
6894
7431
|
false
|
|
@@ -6930,6 +7467,7 @@ function createProgram() {
|
|
|
6930
7467
|
file,
|
|
6931
7468
|
...format !== void 0 ? { format } : {},
|
|
6932
7469
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7470
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
6933
7471
|
...options.create ? { create: true } : {},
|
|
6934
7472
|
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
6935
7473
|
...options.timeout !== void 0 ? { timeoutSec: options.timeout } : {},
|
|
@@ -6953,7 +7491,7 @@ function createProgram() {
|
|
|
6953
7491
|
Promise.resolve().then(() => (init_report(), report_exports)),
|
|
6954
7492
|
Promise.resolve().then(() => (init_reporters(), reporters_exports))
|
|
6955
7493
|
]);
|
|
6956
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
7494
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path6.dirname)((0, import_node_path6.resolve)(globals.config)) : process.cwd();
|
|
6957
7495
|
try {
|
|
6958
7496
|
const result = handleReport2({ cwd });
|
|
6959
7497
|
const text2 = renderReport2(result, toFormat(options.format), {
|
|
@@ -7050,7 +7588,7 @@ function createProgram() {
|
|
|
7050
7588
|
const globals = command.optsWithGlobals();
|
|
7051
7589
|
const verbose = globals.verbose === true;
|
|
7052
7590
|
const { handleSync: handleSync2, reportSyncFailure: reportSyncFailure2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
7053
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
7591
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path6.dirname)((0, import_node_path6.resolve)(globals.config)) : process.cwd();
|
|
7054
7592
|
try {
|
|
7055
7593
|
const { id, url, gate } = await handleSync2({
|
|
7056
7594
|
cwd,
|
|
@@ -7139,7 +7677,7 @@ function createProgram() {
|
|
|
7139
7677
|
failUnexpected(err, verbose);
|
|
7140
7678
|
}
|
|
7141
7679
|
});
|
|
7142
|
-
const requirementsCommand = program.command("requirements").description("Requirements on the platform \u2014 import them from a Markdown file");
|
|
7680
|
+
const requirementsCommand = program.command("requirements").description("Requirements on the platform \u2014 import them, or their links to tests, from a Markdown file");
|
|
7143
7681
|
requirementsCommand.command("import").argument("<file>", "A Markdown file (UTF-8, at most 1 MiB)").description(
|
|
7144
7682
|
"Import requirements from a Markdown file. With no mapping flags it lists the tables and sections found; with them it is a dry run until --yes"
|
|
7145
7683
|
).option(
|
|
@@ -7166,6 +7704,30 @@ function createProgram() {
|
|
|
7166
7704
|
failUnexpected(err, verbose);
|
|
7167
7705
|
}
|
|
7168
7706
|
});
|
|
7707
|
+
requirementsCommand.command("links").argument("<file>", "A Markdown file with a traceability matrix (UTF-8, at most 1 MiB)").description(
|
|
7708
|
+
"Import a traceability matrix (requirement key -> test) as links. With no flags it lists the tables found; with them it is a dry run until --yes"
|
|
7709
|
+
).option("--table <n>", "The table with the matrix (its number from the list)").option("--requirement-column <header>", "The column holding the requirement keys").option("--test-column <header>", "The column holding the tests: an exact case title or a source path, one per line").option("--separator <text>", 'Splits a requirement cell into several keys, 1 to 3 characters (default ",")').option("--project <id>", "The project (default: PLUNE_PROJECT, else the only project of the account)").option("--yes", "Write it. Without it the import is a dry run", false).option("--json", "Print the platform\u2019s answer as JSON", false).option("--fail-on-unmatched", "Exit 1 when any requirement or test of the matrix matched nothing", false).action(async (file, options, command) => {
|
|
7710
|
+
const verbose = command.optsWithGlobals().verbose === true;
|
|
7711
|
+
const { handleRequirementsLinks: handleRequirementsLinks2, formatLinksDiscover: formatLinksDiscover2, formatLinks: formatLinks2 } = await Promise.resolve().then(() => (init_requirements_links(), requirements_links_exports));
|
|
7712
|
+
const { reportRequirementsImportFailure: reportRequirementsImportFailure2 } = await Promise.resolve().then(() => (init_requirements_import(), requirements_import_exports));
|
|
7713
|
+
try {
|
|
7714
|
+
const res = await handleRequirementsLinks2({ file, ...options });
|
|
7715
|
+
if (options.json === true) {
|
|
7716
|
+
process.stdout.write(JSON.stringify(res.step === "discover" ? res.result : res.report, null, 2) + "\n");
|
|
7717
|
+
} else {
|
|
7718
|
+
process.stdout.write(res.step === "discover" ? formatLinksDiscover2(file, res.result) : formatLinks2(res.report));
|
|
7719
|
+
}
|
|
7720
|
+
if (options.failOnUnmatched === true && res.step === "links" && res.report.summary.unmatched > 0) process.exitCode = 1;
|
|
7721
|
+
} catch (err) {
|
|
7722
|
+
const code = reportRequirementsImportFailure2(err, (s) => process.stderr.write(s));
|
|
7723
|
+
if (code !== null) {
|
|
7724
|
+
maybeStack(err, verbose);
|
|
7725
|
+
process.exitCode = code;
|
|
7726
|
+
return;
|
|
7727
|
+
}
|
|
7728
|
+
failUnexpected(err, verbose);
|
|
7729
|
+
}
|
|
7730
|
+
});
|
|
7169
7731
|
const planCommand = program.command("plan").description("Test plans on the platform \u2014 what a plan collects, as something a runner can take");
|
|
7170
7732
|
planCommand.command("grep").argument("<id>", "The plan").description(
|
|
7171
7733
|
"Print the plan's cases as one --grep pattern for the runner (GET /v1/plans/:id/cases)"
|
|
@@ -7190,6 +7752,57 @@ function createProgram() {
|
|
|
7190
7752
|
failUnexpected(err, verbose);
|
|
7191
7753
|
}
|
|
7192
7754
|
});
|
|
7755
|
+
program.command("verify").description(
|
|
7756
|
+
"Compare the project's automated cases with the Playwright tests in the code, read-only \u2014 both sides of the drift, nothing written"
|
|
7757
|
+
).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(
|
|
7758
|
+
async (options, command) => {
|
|
7759
|
+
const verbose = verboseOf(command);
|
|
7760
|
+
const { handleVerify: handleVerify2, formatVerify: formatVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
7761
|
+
const { reportCasesFailure: reportCasesFailure2 } = await Promise.resolve().then(() => (init_cases(), cases_exports));
|
|
7762
|
+
try {
|
|
7763
|
+
const report = await handleVerify2({
|
|
7764
|
+
...options.from === void 0 ? {} : { from: options.from },
|
|
7765
|
+
...options.playwrightConfig === void 0 ? {} : { playwrightConfig: options.playwrightConfig }
|
|
7766
|
+
});
|
|
7767
|
+
process.stdout.write(options.json ? JSON.stringify(report, null, 2) + "\n" : formatVerify2(report));
|
|
7768
|
+
if (options.failOnDrift && report.onlyInPlatform.length + report.onlyInCode.length > 0) process.exitCode = 1;
|
|
7769
|
+
} catch (err) {
|
|
7770
|
+
const code = reportCasesFailure2(err, (s) => process.stderr.write(s));
|
|
7771
|
+
if (code !== null) {
|
|
7772
|
+
maybeStack(err, verbose);
|
|
7773
|
+
process.exitCode = code;
|
|
7774
|
+
return;
|
|
7775
|
+
}
|
|
7776
|
+
failUnexpected(err, verbose);
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
);
|
|
7780
|
+
program.command("ids").description(
|
|
7781
|
+
"Write the Plune case id of each Playwright test that names none into plune/ids.json (dry run unless --write); test sources are not touched"
|
|
7782
|
+
).option("--write", "Write the file; without it only shows what would be written", false).option("--file <path>", "The mapfile", "plune/ids.json").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).action(
|
|
7783
|
+
async (options, command) => {
|
|
7784
|
+
const verbose = verboseOf(command);
|
|
7785
|
+
const { handleIds: handleIds2, formatIds: formatIds2 } = await Promise.resolve().then(() => (init_ids(), ids_exports));
|
|
7786
|
+
const { reportCasesFailure: reportCasesFailure2 } = await Promise.resolve().then(() => (init_cases(), cases_exports));
|
|
7787
|
+
try {
|
|
7788
|
+
const report = await handleIds2({
|
|
7789
|
+
write: options.write,
|
|
7790
|
+
file: options.file,
|
|
7791
|
+
...options.from === void 0 ? {} : { from: options.from },
|
|
7792
|
+
...options.playwrightConfig === void 0 ? {} : { playwrightConfig: options.playwrightConfig }
|
|
7793
|
+
});
|
|
7794
|
+
process.stdout.write(options.json ? JSON.stringify(report, null, 2) + "\n" : formatIds2(report, options.write));
|
|
7795
|
+
} catch (err) {
|
|
7796
|
+
const code = reportCasesFailure2(err, (s) => process.stderr.write(s));
|
|
7797
|
+
if (code !== null) {
|
|
7798
|
+
maybeStack(err, verbose);
|
|
7799
|
+
process.exitCode = code;
|
|
7800
|
+
return;
|
|
7801
|
+
}
|
|
7802
|
+
failUnexpected(err, verbose);
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
);
|
|
7193
7806
|
program.on("command:*", (operands) => {
|
|
7194
7807
|
process.stderr.write(`Unknown command: ${operands.join(" ")}
|
|
7195
7808
|
`);
|
|
@@ -7201,7 +7814,7 @@ function isMainModule() {
|
|
|
7201
7814
|
const entry = process.argv[1];
|
|
7202
7815
|
if (entry === void 0) return false;
|
|
7203
7816
|
try {
|
|
7204
|
-
return (0,
|
|
7817
|
+
return (0, import_node_fs10.realpathSync)(entry) === (0, import_node_fs10.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
7205
7818
|
} catch {
|
|
7206
7819
|
return false;
|
|
7207
7820
|
}
|