@plune-ai/cli 0.32.1 → 0.33.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 +53 -1
- package/dist/cli.cjs +394 -109
- package/dist/index.cjs +34 -16
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +34 -16
- package/dist/reporter-core.cjs +9 -8
- package/dist/reporter-core.js +9 -8
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -70,6 +70,21 @@ var init_api_url = __esm({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
// src/importers/min-score.ts
|
|
74
|
+
function parseMinScore(value2, previous = {}) {
|
|
75
|
+
const match = /^([A-Za-z0-9_-]+)=(\d+(?:\.\d+)?)$/.exec(value2.trim());
|
|
76
|
+
if (match === null) throw new Error(`"${value2}" is not category=score (for example performance=90).`);
|
|
77
|
+
const score = Number(match[2]);
|
|
78
|
+
if (score > 100) throw new Error(`"${value2}" \u2014 a Lighthouse score is 0 to 100.`);
|
|
79
|
+
return { ...previous, [match[1]]: score };
|
|
80
|
+
}
|
|
81
|
+
var init_min_score = __esm({
|
|
82
|
+
"src/importers/min-score.ts"() {
|
|
83
|
+
"use strict";
|
|
84
|
+
init_cjs_shims();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
73
88
|
// src/cli/env.ts
|
|
74
89
|
var env_exports = {};
|
|
75
90
|
__export(env_exports, {
|
|
@@ -558,24 +573,24 @@ var init_prices = __esm({
|
|
|
558
573
|
function priceFrom(price, usage3) {
|
|
559
574
|
return usage3.input_tokens / 1e3 * price.input_per_1k_usd + usage3.output_tokens / 1e3 * price.output_per_1k_usd;
|
|
560
575
|
}
|
|
561
|
-
function
|
|
576
|
+
function resolveCostEntry(usage3, model, reportedCostUsd, overrides, deps = {}) {
|
|
562
577
|
const table = deps.table ?? PRICE_TABLE;
|
|
563
578
|
const warn = deps.warn ?? defaultWarn;
|
|
564
579
|
const override = overrides?.[model];
|
|
565
580
|
if (override !== void 0) {
|
|
566
|
-
return priceFrom(override, usage3);
|
|
581
|
+
return { cost_usd: priceFrom(override, usage3), priced: true };
|
|
567
582
|
}
|
|
568
583
|
if (reportedCostUsd !== void 0) {
|
|
569
|
-
return reportedCostUsd;
|
|
584
|
+
return { cost_usd: reportedCostUsd, priced: true };
|
|
570
585
|
}
|
|
571
586
|
const tablePrice = table[model];
|
|
572
587
|
if (tablePrice !== void 0) {
|
|
573
|
-
return priceFrom(tablePrice, usage3);
|
|
588
|
+
return { cost_usd: priceFrom(tablePrice, usage3), priced: true };
|
|
574
589
|
}
|
|
575
590
|
warn(
|
|
576
591
|
`plune: no price for model "${model}" \u2014 reporting cost_usd=0. Set pricing["${model}"] in plune.yaml to track its cost.`
|
|
577
592
|
);
|
|
578
|
-
return 0;
|
|
593
|
+
return { cost_usd: 0, priced: false };
|
|
579
594
|
}
|
|
580
595
|
var defaultWarn;
|
|
581
596
|
var init_cost = __esm({
|
|
@@ -630,7 +645,7 @@ function makeAnthropicProvider(config, env, pricing) {
|
|
|
630
645
|
}
|
|
631
646
|
},
|
|
632
647
|
estimateCost(usage3, reportedCostUsd) {
|
|
633
|
-
return
|
|
648
|
+
return resolveCostEntry(usage3, config.model, reportedCostUsd, pricing);
|
|
634
649
|
}
|
|
635
650
|
};
|
|
636
651
|
}
|
|
@@ -697,7 +712,7 @@ function makeOpenAiCompatibleProvider(opts) {
|
|
|
697
712
|
}
|
|
698
713
|
},
|
|
699
714
|
estimateCost(usage3, reportedCostUsd) {
|
|
700
|
-
return
|
|
715
|
+
return resolveCostEntry(usage3, config.model, reportedCostUsd, pricing);
|
|
701
716
|
}
|
|
702
717
|
};
|
|
703
718
|
}
|
|
@@ -800,9 +815,7 @@ function makeEstimateProvider(config, pricing) {
|
|
|
800
815
|
complete: () => Promise.reject(
|
|
801
816
|
new Error("The --dry-run provider only estimates cost; it never calls a model.")
|
|
802
817
|
),
|
|
803
|
-
estimateCost: (usage3, reportedCostUsd) => (
|
|
804
|
-
cost_usd: resolveCost(usage3, config.model, reportedCostUsd, pricing)
|
|
805
|
-
})
|
|
818
|
+
estimateCost: (usage3, reportedCostUsd) => resolveCostEntry(usage3, config.model, reportedCostUsd, pricing)
|
|
806
819
|
};
|
|
807
820
|
}
|
|
808
821
|
var init_estimate = __esm({
|
|
@@ -1774,6 +1787,12 @@ function toRunError(err) {
|
|
|
1774
1787
|
function estimateUsage(prompt5, maxTokens) {
|
|
1775
1788
|
return { input_tokens: Math.ceil(prompt5.length / 4), output_tokens: maxTokens };
|
|
1776
1789
|
}
|
|
1790
|
+
function pricedField(priced) {
|
|
1791
|
+
return priced === void 0 ? {} : { priced };
|
|
1792
|
+
}
|
|
1793
|
+
function costFields(c) {
|
|
1794
|
+
return { cost_usd: c.cost_usd, ...pricedField(c.priced) };
|
|
1795
|
+
}
|
|
1777
1796
|
async function runRow(p) {
|
|
1778
1797
|
const t0 = p.now();
|
|
1779
1798
|
const prompt5 = resolvePrompt(p.template, p.row.vars);
|
|
@@ -1781,7 +1800,8 @@ async function runRow(p) {
|
|
|
1781
1800
|
const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
|
|
1782
1801
|
if (p.dryRun) {
|
|
1783
1802
|
const est = estimateUsage(prompt5, maxTokens);
|
|
1784
|
-
const
|
|
1803
|
+
const cost = p.provider.estimateCost(est);
|
|
1804
|
+
const usage4 = { ...est, ...costFields(cost) };
|
|
1785
1805
|
return { vars: p.row.vars, output: null, cached: false, usage: usage4, assertions: [] };
|
|
1786
1806
|
}
|
|
1787
1807
|
const key = cacheKey(
|
|
@@ -1819,7 +1839,9 @@ async function runRow(p) {
|
|
|
1819
1839
|
usage3 = {
|
|
1820
1840
|
input_tokens: hit.usage.input_tokens,
|
|
1821
1841
|
output_tokens: hit.usage.output_tokens,
|
|
1822
|
-
cost_usd: 0
|
|
1842
|
+
cost_usd: 0,
|
|
1843
|
+
// A hit costs nothing, but whether that 0 is a known price depends on the model.
|
|
1844
|
+
...pricedField(p.provider.estimateCost(hit.usage).priced)
|
|
1823
1845
|
};
|
|
1824
1846
|
cacheSaved = {
|
|
1825
1847
|
input_tokens: hit.usage.input_tokens,
|
|
@@ -1836,12 +1858,13 @@ async function runRow(p) {
|
|
|
1836
1858
|
prompt_resolved: prompt5
|
|
1837
1859
|
});
|
|
1838
1860
|
output = res.output;
|
|
1861
|
+
const cost = p.provider.estimateCost(res.usage, res.cost_usd);
|
|
1839
1862
|
usage3 = {
|
|
1840
1863
|
input_tokens: res.usage.input_tokens,
|
|
1841
1864
|
output_tokens: res.usage.output_tokens,
|
|
1842
1865
|
// Prefer the provider-reported actual cost (res.cost_usd) when present; resolveCost still
|
|
1843
1866
|
// lets a config pricing override win, else falls back to the table estimate (ADR-PRC01).
|
|
1844
|
-
|
|
1867
|
+
...costFields(cost)
|
|
1845
1868
|
};
|
|
1846
1869
|
if (!p.noCache && isCacheable(res) && answerParses(res.output, p.assertions)) {
|
|
1847
1870
|
p.cache.set(key, res);
|
|
@@ -1858,8 +1881,11 @@ async function runRow(p) {
|
|
|
1858
1881
|
}
|
|
1859
1882
|
}
|
|
1860
1883
|
let judgeCost = 0;
|
|
1884
|
+
let judgeUnpriced = false;
|
|
1861
1885
|
const judge = buildJudge(p.provider, p.providerConfig, (u) => {
|
|
1862
|
-
|
|
1886
|
+
const c = p.provider.estimateCost(u);
|
|
1887
|
+
judgeCost += c.cost_usd;
|
|
1888
|
+
if (c.priced === false) judgeUnpriced = true;
|
|
1863
1889
|
});
|
|
1864
1890
|
const records = [];
|
|
1865
1891
|
let assertionError;
|
|
@@ -1884,7 +1910,12 @@ async function runRow(p) {
|
|
|
1884
1910
|
break;
|
|
1885
1911
|
}
|
|
1886
1912
|
}
|
|
1887
|
-
const finalUsage = {
|
|
1913
|
+
const finalUsage = {
|
|
1914
|
+
...usage3,
|
|
1915
|
+
cost_usd: usage3.cost_usd + judgeCost,
|
|
1916
|
+
// A row is priced only when every call in it was; the field stays absent if the provider never said.
|
|
1917
|
+
...judgeUnpriced ? { priced: false } : {}
|
|
1918
|
+
};
|
|
1888
1919
|
return {
|
|
1889
1920
|
vars: p.row.vars,
|
|
1890
1921
|
output,
|
|
@@ -2565,7 +2596,7 @@ function createClient(opts) {
|
|
|
2565
2596
|
}
|
|
2566
2597
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
2567
2598
|
const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
|
|
2568
|
-
async function send(method,
|
|
2599
|
+
async function send(method, path21, payload, retries = DEFAULT_RETRIES) {
|
|
2569
2600
|
let last = {
|
|
2570
2601
|
kind: "unavailable",
|
|
2571
2602
|
status: null,
|
|
@@ -2576,7 +2607,7 @@ function createClient(opts) {
|
|
|
2576
2607
|
await respectPause(mine);
|
|
2577
2608
|
let res;
|
|
2578
2609
|
try {
|
|
2579
|
-
res = await doFetch(`${base}${
|
|
2610
|
+
res = await doFetch(`${base}${path21}`, {
|
|
2580
2611
|
method,
|
|
2581
2612
|
headers: { "content-type": payload?.type ?? "application/json", authorization: `Bearer ${opts.token}` },
|
|
2582
2613
|
// A DELETE carries none. Sending `"undefined"` as a body is what a naive
|
|
@@ -2615,10 +2646,10 @@ function createClient(opts) {
|
|
|
2615
2646
|
return { ok: false, ...last };
|
|
2616
2647
|
}
|
|
2617
2648
|
return {
|
|
2618
|
-
post: (
|
|
2619
|
-
upload: (
|
|
2620
|
-
del: (
|
|
2621
|
-
get: (
|
|
2649
|
+
post: (path21, body, retries) => send("POST", path21, { body: JSON.stringify(body), type: "application/json" }, retries),
|
|
2650
|
+
upload: (path21, bytes, contentType) => send("POST", path21, { body: bytes, type: contentType }, uploadRetries ?? DEFAULT_RETRIES),
|
|
2651
|
+
del: (path21) => send("DELETE", path21),
|
|
2652
|
+
get: (path21) => send("GET", path21),
|
|
2622
2653
|
async put(url, headers, source) {
|
|
2623
2654
|
const putRetries = uploadRetries ?? 3;
|
|
2624
2655
|
let last = { ok: false, status: null, code: "network" };
|
|
@@ -2899,12 +2930,12 @@ function machineless(raw, repoRoot, home) {
|
|
|
2899
2930
|
if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
2900
2931
|
return text2;
|
|
2901
2932
|
}
|
|
2902
|
-
function pathIn(
|
|
2903
|
-
const parts =
|
|
2933
|
+
function pathIn(path21) {
|
|
2934
|
+
const parts = path21.split(/[\\/]+/);
|
|
2904
2935
|
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
2905
2936
|
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
2906
2937
|
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
2907
|
-
const escaped = /^\/(?!\/)/.test(
|
|
2938
|
+
const escaped = /^\/(?!\/)/.test(path21) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
2908
2939
|
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
2909
2940
|
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
2910
2941
|
}
|
|
@@ -2950,12 +2981,12 @@ function placeOf(error, testFile) {
|
|
|
2950
2981
|
}
|
|
2951
2982
|
function fileOf(raw) {
|
|
2952
2983
|
if (!raw.startsWith("file://")) return raw;
|
|
2953
|
-
let
|
|
2984
|
+
let path21 = raw.slice("file://".length);
|
|
2954
2985
|
try {
|
|
2955
|
-
|
|
2986
|
+
path21 = decodeURIComponent(path21);
|
|
2956
2987
|
} catch {
|
|
2957
2988
|
}
|
|
2958
|
-
return /^\/[A-Za-z]:\//.test(
|
|
2989
|
+
return /^\/[A-Za-z]:\//.test(path21) ? path21.slice(1) : path21;
|
|
2959
2990
|
}
|
|
2960
2991
|
function fromRoot(place, root) {
|
|
2961
2992
|
if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
|
|
@@ -2995,14 +3026,14 @@ var init_failure_detail = __esm({
|
|
|
2995
3026
|
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
2996
3027
|
FRAME_MAX = 8192;
|
|
2997
3028
|
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
2998
|
-
normal = (
|
|
3029
|
+
normal = (path21) => import_node_path2.posix.normalize(path21.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
2999
3030
|
}
|
|
3000
3031
|
});
|
|
3001
3032
|
|
|
3002
3033
|
// src/reporter-core/files-batch.ts
|
|
3003
|
-
async function sha256File(
|
|
3034
|
+
async function sha256File(path21) {
|
|
3004
3035
|
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
3005
|
-
for await (const part of (0, import_node_fs4.createReadStream)(
|
|
3036
|
+
for await (const part of (0, import_node_fs4.createReadStream)(path21)) hash.update(part);
|
|
3006
3037
|
return hash.digest("hex");
|
|
3007
3038
|
}
|
|
3008
3039
|
var import_node_crypto2, import_node_fs4, FILES_BATCH_MAX, DIRECT_BATCH_BYTES, BATCH_PIPELINES, sha256Bytes, BUCKET_CODES;
|
|
@@ -3218,7 +3249,7 @@ async function startRun(passed, declared2) {
|
|
|
3218
3249
|
stats.deferred += results.length;
|
|
3219
3250
|
}
|
|
3220
3251
|
async function resolveKeys(values) {
|
|
3221
|
-
const unknown = values.filter((k) => !resolved.has(k
|
|
3252
|
+
const unknown = values.filter((k) => !resolved.has(keyId(k)));
|
|
3222
3253
|
if (unknown.length === 0 || offline) return;
|
|
3223
3254
|
for (const part of chunk(unknown, RESOLVE_CHUNK)) {
|
|
3224
3255
|
const out = await client.post(
|
|
@@ -3230,7 +3261,7 @@ async function startRun(passed, declared2) {
|
|
|
3230
3261
|
return;
|
|
3231
3262
|
}
|
|
3232
3263
|
for (const row of out.body.results) {
|
|
3233
|
-
if (typeof row?.key?.value === "string") resolved.set(row.key
|
|
3264
|
+
if (typeof row?.key?.value === "string") resolved.set(keyId(row.key), row.testCaseId);
|
|
3234
3265
|
}
|
|
3235
3266
|
}
|
|
3236
3267
|
}
|
|
@@ -3239,7 +3270,7 @@ async function startRun(passed, declared2) {
|
|
|
3239
3270
|
const stated2 = expected?.slice(0, EXPECTED_MAX) ?? [];
|
|
3240
3271
|
const configuration2 = stated2.length === 0 ? void 0 : {
|
|
3241
3272
|
expected: stated2.flatMap((keys) => {
|
|
3242
|
-
const hit = keys.map((k) => resolved.get(k
|
|
3273
|
+
const hit = keys.map((k) => resolved.get(keyId(k))).find((v) => typeof v === "string");
|
|
3243
3274
|
if (typeof hit === "string") return [{ testCaseId: hit }];
|
|
3244
3275
|
return keys[0] === void 0 ? [] : [{ externalKey: keys[0] }];
|
|
3245
3276
|
}),
|
|
@@ -3288,7 +3319,7 @@ async function startRun(passed, declared2) {
|
|
|
3288
3319
|
if (result.testCaseId !== void 0) return { kind: "found", testCaseId: result.testCaseId };
|
|
3289
3320
|
let unanswered = false;
|
|
3290
3321
|
for (const key of result.keys) {
|
|
3291
|
-
const hit = resolved.get(key
|
|
3322
|
+
const hit = resolved.get(keyId(key));
|
|
3292
3323
|
if (typeof hit === "string") return { kind: "found", testCaseId: hit };
|
|
3293
3324
|
if (hit === void 0) unanswered = true;
|
|
3294
3325
|
}
|
|
@@ -3300,14 +3331,14 @@ async function startRun(passed, declared2) {
|
|
|
3300
3331
|
if (result.keys.length === 0) return;
|
|
3301
3332
|
const title = boundTitle(result.title);
|
|
3302
3333
|
if (title !== result.title) shortened.add(result.title);
|
|
3303
|
-
discoveries.set(result.keys[0]
|
|
3334
|
+
discoveries.set(keyId(result.keys[0]), {
|
|
3304
3335
|
keys: result.keys,
|
|
3305
3336
|
title,
|
|
3306
3337
|
source: result.source,
|
|
3307
3338
|
specRef: result.specRef,
|
|
3308
3339
|
rawStatus: result.rawStatus
|
|
3309
3340
|
});
|
|
3310
|
-
|
|
3341
|
+
for (const key of result.keys) held.set(keyId(key), result);
|
|
3311
3342
|
}
|
|
3312
3343
|
async function offer() {
|
|
3313
3344
|
if (discoveries.size === 0 || offline || runId === null) return;
|
|
@@ -3331,9 +3362,9 @@ async function startRun(passed, declared2) {
|
|
|
3331
3362
|
const submissions = [];
|
|
3332
3363
|
const filesByKey = /* @__PURE__ */ new Map();
|
|
3333
3364
|
for (const r of made) {
|
|
3334
|
-
const pending = typeof r.id === "string" && r.id !== "" ? held.get(r.key
|
|
3365
|
+
const pending = typeof r.id === "string" && r.id !== "" && typeof r.key?.value === "string" ? held.get(keyId(r.key)) : void 0;
|
|
3335
3366
|
if (pending === void 0) continue;
|
|
3336
|
-
held.delete(
|
|
3367
|
+
for (const key of pending.keys) held.delete(keyId(key));
|
|
3337
3368
|
const { keys: _keys, files, ...rest } = pending;
|
|
3338
3369
|
submissions.push({ ...rest, testCaseId: r.id });
|
|
3339
3370
|
if (files !== void 0) filesByKey.set(pending.resultKey, files);
|
|
@@ -3964,7 +3995,7 @@ async function startRun(passed, declared2) {
|
|
|
3964
3995
|
}
|
|
3965
3996
|
};
|
|
3966
3997
|
}
|
|
3967
|
-
var import_node_crypto3, import_node_fs5, import_node_path3, import_node_util2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, SEND_CONCURRENCY, EXPECTED_MAX, KEY_MAX, TITLE_MAX, COUNTED, SCREENSHOT_TYPES, SCREENSHOT_BYTES, SCREENSHOTS_PER_RESULT, TEXT_BYTES, TEXTS_PER_RESULT, UTF8, TYPE_BY_EXTENSION, METADATA_TYPE2, NAME_MAX, SKIPPED, BUCKET_FOUND, KINDS, codeOf2, BATCH_BYTES, ENVELOPE_BYTES;
|
|
3998
|
+
var import_node_crypto3, import_node_fs5, import_node_path3, import_node_util2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, SEND_CONCURRENCY, EXPECTED_MAX, KEY_MAX, TITLE_MAX, COUNTED, SCREENSHOT_TYPES, SCREENSHOT_BYTES, SCREENSHOTS_PER_RESULT, TEXT_BYTES, TEXTS_PER_RESULT, UTF8, TYPE_BY_EXTENSION, METADATA_TYPE2, NAME_MAX, SKIPPED, BUCKET_FOUND, KINDS, codeOf2, keyId, BATCH_BYTES, ENVELOPE_BYTES;
|
|
3968
3999
|
var init_session = __esm({
|
|
3969
4000
|
"src/reporter-core/session.ts"() {
|
|
3970
4001
|
"use strict";
|
|
@@ -4024,6 +4055,7 @@ var init_session = __esm({
|
|
|
4024
4055
|
file: { label: "file", bytes: 0, room: 0, limit: "limit", size: "size" }
|
|
4025
4056
|
};
|
|
4026
4057
|
codeOf2 = (out) => out.code ?? (out.status === null ? "network" : `http_${out.status}`);
|
|
4058
|
+
keyId = (key) => `${key.kind ?? ""}\0${key.value}`;
|
|
4027
4059
|
BATCH_BYTES = 8 * 1024 * 1024;
|
|
4028
4060
|
ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
|
|
4029
4061
|
}
|
|
@@ -4301,7 +4333,9 @@ var init_run_result_schema = __esm({
|
|
|
4301
4333
|
usageSchema = import_zod3.z.object({
|
|
4302
4334
|
input_tokens: import_zod3.z.number(),
|
|
4303
4335
|
output_tokens: import_zod3.z.number(),
|
|
4304
|
-
cost_usd: import_zod3.z.number()
|
|
4336
|
+
cost_usd: import_zod3.z.number(),
|
|
4337
|
+
/** cli#151. Optional and additive — a RunResult written before it validates unchanged. */
|
|
4338
|
+
priced: import_zod3.z.boolean().optional()
|
|
4305
4339
|
});
|
|
4306
4340
|
runErrorSchema = import_zod3.z.object({
|
|
4307
4341
|
code: import_zod3.z.string(),
|
|
@@ -4632,10 +4666,10 @@ function platformGet(deps) {
|
|
|
4632
4666
|
const token = (deps.loadToken ?? loadToken)();
|
|
4633
4667
|
if (token === null) throw new NotLoggedInError();
|
|
4634
4668
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
4635
|
-
return async (
|
|
4669
|
+
return async (path21) => {
|
|
4636
4670
|
let res;
|
|
4637
4671
|
try {
|
|
4638
|
-
res = await doFetch(`${apiUrl}${
|
|
4672
|
+
res = await doFetch(`${apiUrl}${path21}`, { headers: { authorization: `Bearer ${token}` } });
|
|
4639
4673
|
} catch {
|
|
4640
4674
|
throw new SyncNetworkError(apiUrl);
|
|
4641
4675
|
}
|
|
@@ -4975,10 +5009,24 @@ var init_attachments = __esm({
|
|
|
4975
5009
|
}
|
|
4976
5010
|
});
|
|
4977
5011
|
|
|
5012
|
+
// src/importers/read-file.ts
|
|
5013
|
+
function readReportFile(file) {
|
|
5014
|
+
const text2 = fs11.readFileSync(file, "utf8");
|
|
5015
|
+
return text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
5016
|
+
}
|
|
5017
|
+
var fs11;
|
|
5018
|
+
var init_read_file = __esm({
|
|
5019
|
+
"src/importers/read-file.ts"() {
|
|
5020
|
+
"use strict";
|
|
5021
|
+
init_cjs_shims();
|
|
5022
|
+
fs11 = __toESM(require("fs"), 1);
|
|
5023
|
+
}
|
|
5024
|
+
});
|
|
5025
|
+
|
|
4978
5026
|
// src/importers/allure.ts
|
|
4979
|
-
function isFolder(
|
|
5027
|
+
function isFolder(path21) {
|
|
4980
5028
|
try {
|
|
4981
|
-
return
|
|
5029
|
+
return fs12.statSync(path21).isDirectory();
|
|
4982
5030
|
} catch {
|
|
4983
5031
|
return false;
|
|
4984
5032
|
}
|
|
@@ -4986,7 +5034,7 @@ function isFolder(path19) {
|
|
|
4986
5034
|
function load(file, maxBytes) {
|
|
4987
5035
|
let size;
|
|
4988
5036
|
try {
|
|
4989
|
-
size =
|
|
5037
|
+
size = fs12.statSync(file).size;
|
|
4990
5038
|
} catch (err) {
|
|
4991
5039
|
throw new AllureReportError(`${printable(file)} \u2014 cannot read \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
4992
5040
|
}
|
|
@@ -4995,7 +5043,7 @@ function load(file, maxBytes) {
|
|
|
4995
5043
|
}
|
|
4996
5044
|
let parsed;
|
|
4997
5045
|
try {
|
|
4998
|
-
parsed = JSON.parse(
|
|
5046
|
+
parsed = JSON.parse(readReportFile(file));
|
|
4999
5047
|
} catch (err) {
|
|
5000
5048
|
throw new AllureReportError(`${printable(file)} \u2014 not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
5001
5049
|
}
|
|
@@ -5181,16 +5229,17 @@ function pendingOf(r, retry, ctx) {
|
|
|
5181
5229
|
...files.length > 0 ? { files } : {}
|
|
5182
5230
|
};
|
|
5183
5231
|
}
|
|
5184
|
-
var
|
|
5232
|
+
var fs12, path12, SOURCE, TOKEN, AllureReportError, RESULT, CONTAINER, MAX_JSON_BYTES, MAX_DEPTH, MAX_TIME, namesIn, timeOf, textOf, labelOf, FAILED, declared, stopOf, compareText;
|
|
5185
5233
|
var init_allure = __esm({
|
|
5186
5234
|
"src/importers/allure.ts"() {
|
|
5187
5235
|
"use strict";
|
|
5188
5236
|
init_cjs_shims();
|
|
5189
|
-
|
|
5237
|
+
fs12 = __toESM(require("fs"), 1);
|
|
5190
5238
|
path12 = __toESM(require("path"), 1);
|
|
5191
5239
|
init_failure_detail();
|
|
5192
5240
|
init_result_key();
|
|
5193
5241
|
init_attachments();
|
|
5242
|
+
init_read_file();
|
|
5194
5243
|
SOURCE = "allure";
|
|
5195
5244
|
TOKEN = /@P([A-Za-z0-9_-]+)/;
|
|
5196
5245
|
AllureReportError = class extends Error {
|
|
@@ -5204,7 +5253,7 @@ var init_allure = __esm({
|
|
|
5204
5253
|
MAX_JSON_BYTES = 64 * 1024 * 1024;
|
|
5205
5254
|
MAX_DEPTH = 256;
|
|
5206
5255
|
MAX_TIME = 864e13;
|
|
5207
|
-
namesIn = (dir, pattern) =>
|
|
5256
|
+
namesIn = (dir, pattern) => fs12.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isFile() && pattern.test(d.name)).map((d) => d.name).sort();
|
|
5208
5257
|
timeOf = (v) => typeof v === "number" && Math.abs(v) <= MAX_TIME ? v : void 0;
|
|
5209
5258
|
textOf = (v) => typeof v === "string" ? v : "";
|
|
5210
5259
|
labelOf = (r, name) => textOf(listOf(r.labels).find((l) => l.name === name)?.value);
|
|
@@ -5491,10 +5540,10 @@ function attemptOf(result, file, ctx) {
|
|
|
5491
5540
|
...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
|
|
5492
5541
|
})),
|
|
5493
5542
|
steps: steps(result.steps),
|
|
5494
|
-
attachments: (result.attachments ?? []).map(({ name, contentType, path:
|
|
5543
|
+
attachments: (result.attachments ?? []).map(({ name, contentType, path: path21, body }) => ({
|
|
5495
5544
|
name: name ?? "",
|
|
5496
5545
|
...contentType !== void 0 ? { contentType } : {},
|
|
5497
|
-
...
|
|
5546
|
+
...path21 !== void 0 ? { path: path21 } : {},
|
|
5498
5547
|
// Decoded only where the platform would take it (plune-ai/plune#928): a report of screenshots kept as
|
|
5499
5548
|
// bodies is megabytes of base64 that nothing here will read.
|
|
5500
5549
|
...typeof body === "string" && isTextType(contentType) ? { body: Buffer.from(body, "base64") } : {}
|
|
@@ -5611,6 +5660,193 @@ var init_playwright_json = __esm({
|
|
|
5611
5660
|
}
|
|
5612
5661
|
});
|
|
5613
5662
|
|
|
5663
|
+
// src/importers/k6.ts
|
|
5664
|
+
function isObject(value2) {
|
|
5665
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
5666
|
+
}
|
|
5667
|
+
function isK6Summary(parsed) {
|
|
5668
|
+
if (!isObject(parsed) || !isObject(parsed["metrics"])) return false;
|
|
5669
|
+
if (isObject(parsed["root_group"])) return true;
|
|
5670
|
+
return Object.values(parsed["metrics"]).some((m) => isObject(m) && (typeof m["type"] === "string" || isObject(m["thresholds"])));
|
|
5671
|
+
}
|
|
5672
|
+
function passedOf(verdict) {
|
|
5673
|
+
if (isObject(verdict) && typeof verdict["ok"] === "boolean") return verdict["ok"];
|
|
5674
|
+
if (typeof verdict === "boolean") return !verdict;
|
|
5675
|
+
return void 0;
|
|
5676
|
+
}
|
|
5677
|
+
function measuredOf(metric) {
|
|
5678
|
+
const values = isObject(metric.values) ? metric.values : metric;
|
|
5679
|
+
const unit = metric.contains === "time" ? " ms" : "";
|
|
5680
|
+
return Object.entries(values).filter(([name, v]) => name !== "thresholds" && typeof v === "number").map(([name, v]) => `${name}=${Math.round(v * 100) / 100}${unit}`).join(", ");
|
|
5681
|
+
}
|
|
5682
|
+
function readK6(source, file) {
|
|
5683
|
+
let parsed;
|
|
5684
|
+
try {
|
|
5685
|
+
parsed = JSON.parse(source);
|
|
5686
|
+
} catch (err) {
|
|
5687
|
+
throw new JsonReportError(file, `not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
5688
|
+
}
|
|
5689
|
+
if (!isObject(parsed) || !isObject(parsed["metrics"])) {
|
|
5690
|
+
throw new JsonReportError(file, 'no "metrics" object \u2014 is this a k6 summary (k6 run --summary-export, or the data handleSummary receives)?');
|
|
5691
|
+
}
|
|
5692
|
+
const specRef = path13.basename(file);
|
|
5693
|
+
const out = [];
|
|
5694
|
+
for (const [name, raw] of Object.entries(parsed["metrics"])) {
|
|
5695
|
+
if (!isObject(raw) || !isObject(raw["thresholds"])) continue;
|
|
5696
|
+
const metric = raw;
|
|
5697
|
+
const measured = measuredOf(metric);
|
|
5698
|
+
for (const [expression, verdict] of Object.entries(metric.thresholds)) {
|
|
5699
|
+
const passed = passedOf(verdict);
|
|
5700
|
+
const status = passed === true ? "passed" : "failed";
|
|
5701
|
+
const said = passed === void 0 ? "no verdict in the summary" : status;
|
|
5702
|
+
const value2 = `${SOURCE4}#${specRef}#${name}#${expression}`;
|
|
5703
|
+
out.push({
|
|
5704
|
+
resultKey: resultKey(value2, 0),
|
|
5705
|
+
keys: [{ kind: "path-title", value: value2 }],
|
|
5706
|
+
source: SOURCE4,
|
|
5707
|
+
rawStatus: status,
|
|
5708
|
+
title: `${name} ${expression}`,
|
|
5709
|
+
specRef,
|
|
5710
|
+
errorContext: `k6 threshold "${expression}" on ${name}: ${said}.${measured !== "" ? ` Measured: ${measured}.` : ""}`
|
|
5711
|
+
});
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
if (out.length === 0) {
|
|
5715
|
+
throw new JsonReportError(
|
|
5716
|
+
file,
|
|
5717
|
+
"k6 defined no thresholds, so there is nothing to pass or fail. Add thresholds to the script's options (for example http_req_duration: ['p(95)<500']) and run it again."
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
return out;
|
|
5721
|
+
}
|
|
5722
|
+
var path13, SOURCE4;
|
|
5723
|
+
var init_k6 = __esm({
|
|
5724
|
+
"src/importers/k6.ts"() {
|
|
5725
|
+
"use strict";
|
|
5726
|
+
init_cjs_shims();
|
|
5727
|
+
path13 = __toESM(require("path"), 1);
|
|
5728
|
+
init_failure_detail();
|
|
5729
|
+
init_result_key();
|
|
5730
|
+
init_playwright_json();
|
|
5731
|
+
SOURCE4 = "k6";
|
|
5732
|
+
}
|
|
5733
|
+
});
|
|
5734
|
+
|
|
5735
|
+
// src/importers/lighthouse.ts
|
|
5736
|
+
function isObject2(value2) {
|
|
5737
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
5738
|
+
}
|
|
5739
|
+
function isLighthouseReport(parsed) {
|
|
5740
|
+
return isObject2(parsed) && typeof parsed["lighthouseVersion"] === "string" && isObject2(parsed["categories"]);
|
|
5741
|
+
}
|
|
5742
|
+
function idOf(key, category) {
|
|
5743
|
+
return typeof category.id === "string" && category.id !== "" ? category.id : key;
|
|
5744
|
+
}
|
|
5745
|
+
function executionOf2(report) {
|
|
5746
|
+
const started = typeof report["fetchTime"] === "string" ? new Date(report["fetchTime"]) : void 0;
|
|
5747
|
+
if (started === void 0 || Number.isNaN(started.getTime())) return void 0;
|
|
5748
|
+
const timing = report["timing"];
|
|
5749
|
+
const total = isObject2(timing) && typeof timing["total"] === "number" && timing["total"] >= 0 ? Math.round(timing["total"]) : 0;
|
|
5750
|
+
return {
|
|
5751
|
+
startedAt: started.toISOString(),
|
|
5752
|
+
finishedAt: new Date(started.getTime() + total).toISOString(),
|
|
5753
|
+
durationMs: total,
|
|
5754
|
+
retry: 0
|
|
5755
|
+
};
|
|
5756
|
+
}
|
|
5757
|
+
function runtimeErrorOf(report) {
|
|
5758
|
+
const err = report["runtimeError"];
|
|
5759
|
+
if (!isObject2(err)) return void 0;
|
|
5760
|
+
const code = typeof err["code"] === "string" ? err["code"] : void 0;
|
|
5761
|
+
if (code === "NO_ERROR") return void 0;
|
|
5762
|
+
const message = typeof err["message"] === "string" ? err["message"] : void 0;
|
|
5763
|
+
if (code === void 0 && message === void 0) return void 0;
|
|
5764
|
+
return `Lighthouse stopped with ${code ?? "a runtime error"}${message !== void 0 ? `: ${message}` : ""}`;
|
|
5765
|
+
}
|
|
5766
|
+
function readLighthouse(source, file, minScores = {}) {
|
|
5767
|
+
let parsed;
|
|
5768
|
+
try {
|
|
5769
|
+
parsed = JSON.parse(source);
|
|
5770
|
+
} catch (err) {
|
|
5771
|
+
throw new JsonReportError(file, `not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
5772
|
+
}
|
|
5773
|
+
if (!isLighthouseReport(parsed)) {
|
|
5774
|
+
throw new JsonReportError(file, 'no "lighthouseVersion" and "categories" \u2014 is this a Lighthouse JSON report (lighthouse <url> --output=json)?');
|
|
5775
|
+
}
|
|
5776
|
+
const report = parsed;
|
|
5777
|
+
const categories = report["categories"];
|
|
5778
|
+
const url = ["requestedUrl", "finalDisplayedUrl", "finalUrl"].map((k) => report[k]).find((u) => typeof u === "string" && u !== "");
|
|
5779
|
+
const settings = report["configSettings"];
|
|
5780
|
+
const formFactor = isObject2(settings) && typeof settings["formFactor"] === "string" && settings["formFactor"] !== "" ? settings["formFactor"] : void 0;
|
|
5781
|
+
const runtimeError = runtimeErrorOf(report);
|
|
5782
|
+
const execution = executionOf2(report);
|
|
5783
|
+
const specRef = path14.basename(file);
|
|
5784
|
+
const known = Object.entries(categories).filter(([, raw]) => isObject2(raw)).map(([key, raw]) => idOf(key, raw));
|
|
5785
|
+
if (known.length > 0) {
|
|
5786
|
+
for (const [id, score] of Object.entries(minScores)) {
|
|
5787
|
+
if (!known.includes(id)) {
|
|
5788
|
+
throw new JsonReportError(
|
|
5789
|
+
file,
|
|
5790
|
+
`--min-score ${printable(id)}=${score} names a category the report does not have; it has ${known.map(printable).join(", ")}.`
|
|
5791
|
+
);
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
const out = [];
|
|
5796
|
+
const result = (id, passed, message) => {
|
|
5797
|
+
const value2 = `${SOURCE5}#${url ?? ""}#${formFactor !== void 0 ? `${formFactor}#` : ""}${id}`;
|
|
5798
|
+
const where = [url, formFactor].filter((part) => part !== void 0);
|
|
5799
|
+
out.push({
|
|
5800
|
+
resultKey: resultKey(value2, 0),
|
|
5801
|
+
keys: [{ kind: "path-title", value: value2 }],
|
|
5802
|
+
source: SOURCE5,
|
|
5803
|
+
rawStatus: passed ? "passed" : "failed",
|
|
5804
|
+
title: where.length > 0 ? `${id} (${where.join(", ")})` : id,
|
|
5805
|
+
specRef,
|
|
5806
|
+
...execution !== void 0 ? { execution } : {},
|
|
5807
|
+
errorContext: message
|
|
5808
|
+
});
|
|
5809
|
+
};
|
|
5810
|
+
for (const [key, raw] of Object.entries(categories)) {
|
|
5811
|
+
if (!isObject2(raw)) continue;
|
|
5812
|
+
const category = raw;
|
|
5813
|
+
const id = idOf(key, category);
|
|
5814
|
+
const name = typeof category.title === "string" && category.title !== "" ? category.title : id;
|
|
5815
|
+
const bar = (Object.hasOwn(minScores, id) ? minScores[id] : void 0) ?? DEFAULT_MIN_SCORE;
|
|
5816
|
+
let passed = false;
|
|
5817
|
+
let message;
|
|
5818
|
+
if (runtimeError !== void 0) {
|
|
5819
|
+
message = runtimeError;
|
|
5820
|
+
} else if (typeof category.score !== "number") {
|
|
5821
|
+
message = `Lighthouse could not compute a ${name} score, so it cannot pass.`;
|
|
5822
|
+
} else {
|
|
5823
|
+
const shown = Math.round(category.score * 100);
|
|
5824
|
+
passed = shown >= bar;
|
|
5825
|
+
message = `Lighthouse ${name} score: ${shown}, ${passed ? "at least" : "below"} the ${bar} needed to pass.`;
|
|
5826
|
+
}
|
|
5827
|
+
result(id, passed, message);
|
|
5828
|
+
}
|
|
5829
|
+
if (out.length === 0 && runtimeError !== void 0) result("run", false, runtimeError);
|
|
5830
|
+
if (out.length === 0) {
|
|
5831
|
+
throw new JsonReportError(file, "the report has no categories, so there is nothing to pass or fail.");
|
|
5832
|
+
}
|
|
5833
|
+
return out;
|
|
5834
|
+
}
|
|
5835
|
+
var path14, SOURCE5, DEFAULT_MIN_SCORE;
|
|
5836
|
+
var init_lighthouse = __esm({
|
|
5837
|
+
"src/importers/lighthouse.ts"() {
|
|
5838
|
+
"use strict";
|
|
5839
|
+
init_cjs_shims();
|
|
5840
|
+
path14 = __toESM(require("path"), 1);
|
|
5841
|
+
init_failure_detail();
|
|
5842
|
+
init_result_key();
|
|
5843
|
+
init_playwright_json();
|
|
5844
|
+
init_min_score();
|
|
5845
|
+
SOURCE5 = "lighthouse";
|
|
5846
|
+
DEFAULT_MIN_SCORE = 90;
|
|
5847
|
+
}
|
|
5848
|
+
});
|
|
5849
|
+
|
|
5614
5850
|
// src/importers/index.ts
|
|
5615
5851
|
var importers_exports = {};
|
|
5616
5852
|
__export(importers_exports, {
|
|
@@ -5622,19 +5858,40 @@ __export(importers_exports, {
|
|
|
5622
5858
|
detectFormat: () => detectFormat,
|
|
5623
5859
|
isFolder: () => isFolder,
|
|
5624
5860
|
readFolder: () => readFolder,
|
|
5625
|
-
readReport: () => readReport
|
|
5861
|
+
readReport: () => readReport,
|
|
5862
|
+
readReportFile: () => readReportFile
|
|
5626
5863
|
});
|
|
5627
5864
|
function detectFormat(source, file) {
|
|
5628
5865
|
if (looksLikeJUnit(source)) return "junit";
|
|
5866
|
+
const perf = perfFormatOf(source);
|
|
5867
|
+
if (perf !== void 0) return perf;
|
|
5629
5868
|
if (looksLikePlaywrightJson(source)) return "playwright-json";
|
|
5630
5869
|
throw new UnknownFormatError(
|
|
5631
5870
|
file,
|
|
5632
|
-
'no <testsuite
|
|
5871
|
+
'no <testsuite>, no Playwright "suites", no k6 "metrics" and no Lighthouse "lighthouseVersion" \u2014 this is not a report we can read.'
|
|
5633
5872
|
);
|
|
5634
5873
|
}
|
|
5635
|
-
function
|
|
5636
|
-
if (
|
|
5637
|
-
|
|
5874
|
+
function perfFormatOf(source) {
|
|
5875
|
+
if (!/^\s*\{/.test(source) || !(source.includes('"lighthouseVersion"') || source.includes('"metrics"'))) return void 0;
|
|
5876
|
+
let parsed;
|
|
5877
|
+
try {
|
|
5878
|
+
parsed = JSON.parse(source);
|
|
5879
|
+
} catch {
|
|
5880
|
+
return void 0;
|
|
5881
|
+
}
|
|
5882
|
+
if (isLighthouseReport(parsed)) return "lighthouse";
|
|
5883
|
+
if (isK6Summary(parsed)) return "k6";
|
|
5884
|
+
return void 0;
|
|
5885
|
+
}
|
|
5886
|
+
function readFolder(dir, format, options = {}) {
|
|
5887
|
+
if (format === "playwright-json" || format === "k6" || format === "lighthouse") {
|
|
5888
|
+
throw new UnknownFormatError(dir, `this is a folder, and only allure-results or JUnit XML files are read from one, not ${format}.`);
|
|
5889
|
+
}
|
|
5890
|
+
if (options.minScores !== void 0 && Object.keys(options.minScores).length > 0) {
|
|
5891
|
+
throw new JsonReportError(
|
|
5892
|
+
dir,
|
|
5893
|
+
"--min-score is for Lighthouse reports, and this is a folder: there is no score here to hold to it. Drop --min-score for this folder."
|
|
5894
|
+
);
|
|
5638
5895
|
}
|
|
5639
5896
|
const missing = [];
|
|
5640
5897
|
const notes = [];
|
|
@@ -5645,10 +5902,10 @@ function readFolder(dir, format) {
|
|
|
5645
5902
|
const skipped = [];
|
|
5646
5903
|
let reports = 0;
|
|
5647
5904
|
for (const name of namesIn(dir, /\.xml$/)) {
|
|
5648
|
-
const file =
|
|
5905
|
+
const file = path15.join(dir, name);
|
|
5649
5906
|
let source;
|
|
5650
5907
|
try {
|
|
5651
|
-
source =
|
|
5908
|
+
source = readReportFile(file);
|
|
5652
5909
|
} catch {
|
|
5653
5910
|
throw new RunCommandError(`Cannot read ${printable(file)}.`);
|
|
5654
5911
|
}
|
|
@@ -5668,30 +5925,43 @@ function readFolder(dir, format) {
|
|
|
5668
5925
|
}
|
|
5669
5926
|
return { format: "junit", results, missing, notes };
|
|
5670
5927
|
}
|
|
5671
|
-
function readReport(source, file, format) {
|
|
5928
|
+
function readReport(source, file, format, options = {}) {
|
|
5672
5929
|
const chosen2 = format ?? detectFormat(source, file);
|
|
5673
5930
|
const missing = [];
|
|
5674
5931
|
if (chosen2 === "allure") throw new UnknownFormatError(file, "allure-results is a folder, and this is a file.");
|
|
5675
5932
|
const notes = [];
|
|
5933
|
+
if (chosen2 !== "lighthouse" && options.minScores !== void 0 && Object.keys(options.minScores).length > 0) {
|
|
5934
|
+
throw new JsonReportError(
|
|
5935
|
+
file,
|
|
5936
|
+
`--min-score is for Lighthouse reports, and this is a ${chosen2} report: there is no score here to hold to it. Drop --min-score for this file.`
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5676
5939
|
if (chosen2 === "junit") return { format: chosen2, results: readJUnit(source, file, missing, notes), missing, notes };
|
|
5940
|
+
if (chosen2 === "k6") return { format: chosen2, results: readK6(source, file), missing, notes };
|
|
5941
|
+
if (chosen2 === "lighthouse") {
|
|
5942
|
+
return { format: chosen2, results: readLighthouse(source, file, options.minScores), missing, notes };
|
|
5943
|
+
}
|
|
5677
5944
|
return { format: chosen2, ...readPlaywrightJson(source, file), missing, notes };
|
|
5678
5945
|
}
|
|
5679
|
-
var
|
|
5946
|
+
var path15, IMPORT_FORMATS, UnknownFormatError;
|
|
5680
5947
|
var init_importers = __esm({
|
|
5681
5948
|
"src/importers/index.ts"() {
|
|
5682
5949
|
"use strict";
|
|
5683
5950
|
init_cjs_shims();
|
|
5684
|
-
|
|
5685
|
-
path13 = __toESM(require("path"), 1);
|
|
5951
|
+
path15 = __toESM(require("path"), 1);
|
|
5686
5952
|
init_run_lifecycle();
|
|
5687
5953
|
init_failure_detail();
|
|
5688
5954
|
init_allure();
|
|
5689
5955
|
init_junit();
|
|
5690
5956
|
init_playwright_json();
|
|
5957
|
+
init_k6();
|
|
5958
|
+
init_lighthouse();
|
|
5959
|
+
init_read_file();
|
|
5691
5960
|
init_xml();
|
|
5692
5961
|
init_playwright_json();
|
|
5693
5962
|
init_allure();
|
|
5694
|
-
|
|
5963
|
+
init_read_file();
|
|
5964
|
+
IMPORT_FORMATS = ["junit", "playwright-json", "allure", "k6", "lighthouse"];
|
|
5695
5965
|
UnknownFormatError = class extends Error {
|
|
5696
5966
|
constructor(file, detail) {
|
|
5697
5967
|
super(`${file} \u2014 ${detail} Say which with --format ${IMPORT_FORMATS.join(" | ")}.`);
|
|
@@ -5727,15 +5997,19 @@ async function handleRunImport(options) {
|
|
|
5727
5997
|
`));
|
|
5728
5998
|
let read;
|
|
5729
5999
|
if (isFolder(options.file)) {
|
|
5730
|
-
read = readFolder(options.file, options.format
|
|
6000
|
+
read = readFolder(options.file, options.format, {
|
|
6001
|
+
...options.minScores !== void 0 ? { minScores: options.minScores } : {}
|
|
6002
|
+
});
|
|
5731
6003
|
} else {
|
|
5732
6004
|
let source;
|
|
5733
6005
|
try {
|
|
5734
|
-
source =
|
|
6006
|
+
source = readReportFile(options.file);
|
|
5735
6007
|
} catch {
|
|
5736
6008
|
throw new RunCommandError(`Cannot read ${printable(options.file)}.`);
|
|
5737
6009
|
}
|
|
5738
|
-
read = readReport(source, options.file, options.format
|
|
6010
|
+
read = readReport(source, options.file, options.format, {
|
|
6011
|
+
...options.minScores !== void 0 ? { minScores: options.minScores } : {}
|
|
6012
|
+
});
|
|
5739
6013
|
}
|
|
5740
6014
|
const { format, results, missing, notes, ciUrl, ci } = read;
|
|
5741
6015
|
if (results.length === 0) {
|
|
@@ -5884,12 +6158,11 @@ function describe(result, apiUrl) {
|
|
|
5884
6158
|
if (result.runId !== null) lines.push(runUrl2(apiUrl, result.runId));
|
|
5885
6159
|
return lines;
|
|
5886
6160
|
}
|
|
5887
|
-
var
|
|
6161
|
+
var ImportTimeoutError, NEXT;
|
|
5888
6162
|
var init_run_import = __esm({
|
|
5889
6163
|
"src/cli/commands/run-import.ts"() {
|
|
5890
6164
|
"use strict";
|
|
5891
6165
|
init_cjs_shims();
|
|
5892
|
-
fs13 = __toESM(require("fs"), 1);
|
|
5893
6166
|
init_session();
|
|
5894
6167
|
init_failure_detail();
|
|
5895
6168
|
init_importers();
|
|
@@ -5921,10 +6194,10 @@ function isRunResult(v) {
|
|
|
5921
6194
|
}
|
|
5922
6195
|
function handleReport(opts = {}) {
|
|
5923
6196
|
const cwd = opts.cwd ?? process.cwd();
|
|
5924
|
-
const file =
|
|
6197
|
+
const file = path16.join(cwd, ".plune", "last-run.json");
|
|
5925
6198
|
let raw;
|
|
5926
6199
|
try {
|
|
5927
|
-
raw =
|
|
6200
|
+
raw = fs13.readFileSync(file, "utf8");
|
|
5928
6201
|
} catch {
|
|
5929
6202
|
throw new ReportNotFoundError('No saved run found. Run "plune run" first.');
|
|
5930
6203
|
}
|
|
@@ -5939,13 +6212,13 @@ function handleReport(opts = {}) {
|
|
|
5939
6212
|
}
|
|
5940
6213
|
return parsed;
|
|
5941
6214
|
}
|
|
5942
|
-
var
|
|
6215
|
+
var fs13, path16, ReportNotFoundError;
|
|
5943
6216
|
var init_report = __esm({
|
|
5944
6217
|
"src/cli/commands/report.ts"() {
|
|
5945
6218
|
"use strict";
|
|
5946
6219
|
init_cjs_shims();
|
|
5947
|
-
|
|
5948
|
-
|
|
6220
|
+
fs13 = __toESM(require("fs"), 1);
|
|
6221
|
+
path16 = __toESM(require("path"), 1);
|
|
5949
6222
|
ReportNotFoundError = class extends Error {
|
|
5950
6223
|
code = "NO_SAVED_RUN";
|
|
5951
6224
|
constructor(message) {
|
|
@@ -6015,7 +6288,7 @@ function isRunResult2(v) {
|
|
|
6015
6288
|
function loadRunResult(file, label) {
|
|
6016
6289
|
let raw;
|
|
6017
6290
|
try {
|
|
6018
|
-
raw =
|
|
6291
|
+
raw = fs14.readFileSync(file, "utf8");
|
|
6019
6292
|
} catch {
|
|
6020
6293
|
throw new DiffInputError(`Cannot read ${label} run file: ${file}`);
|
|
6021
6294
|
}
|
|
@@ -6035,12 +6308,12 @@ function handleDiff(opts) {
|
|
|
6035
6308
|
const current = loadRunResult(opts.currentPath, "current");
|
|
6036
6309
|
return diffRuns(baseline, current);
|
|
6037
6310
|
}
|
|
6038
|
-
var
|
|
6311
|
+
var fs14, DiffInputError;
|
|
6039
6312
|
var init_diff2 = __esm({
|
|
6040
6313
|
"src/cli/commands/diff.ts"() {
|
|
6041
6314
|
"use strict";
|
|
6042
6315
|
init_cjs_shims();
|
|
6043
|
-
|
|
6316
|
+
fs14 = __toESM(require("fs"), 1);
|
|
6044
6317
|
init_diff();
|
|
6045
6318
|
DiffInputError = class extends Error {
|
|
6046
6319
|
code = "DIFF_INPUT";
|
|
@@ -6165,8 +6438,8 @@ async function runInitWizard(cwd) {
|
|
|
6165
6438
|
process.exit(1);
|
|
6166
6439
|
return;
|
|
6167
6440
|
}
|
|
6168
|
-
const outputPath =
|
|
6169
|
-
if (
|
|
6441
|
+
const outputPath = path17.join(cwd, "plune.yaml");
|
|
6442
|
+
if (fs15.existsSync(outputPath)) {
|
|
6170
6443
|
const overwrite = await clack.confirm({
|
|
6171
6444
|
message: "plune.yaml already exists. Overwrite?"
|
|
6172
6445
|
});
|
|
@@ -6205,18 +6478,18 @@ async function runInitWizard(cwd) {
|
|
|
6205
6478
|
`;
|
|
6206
6479
|
const spin = clack.spinner();
|
|
6207
6480
|
spin.start("Writing plune.yaml\u2026");
|
|
6208
|
-
|
|
6481
|
+
fs15.writeFileSync(outputPath, header + "\n" + (0, import_yaml2.stringify)(config), "utf8");
|
|
6209
6482
|
spin.stop("plune.yaml created!");
|
|
6210
6483
|
clack.outro("plune.yaml is ready.");
|
|
6211
6484
|
}
|
|
6212
|
-
var clack,
|
|
6485
|
+
var clack, fs15, path17, import_yaml2;
|
|
6213
6486
|
var init_wizard = __esm({
|
|
6214
6487
|
"src/config/init/wizard.ts"() {
|
|
6215
6488
|
"use strict";
|
|
6216
6489
|
init_cjs_shims();
|
|
6217
6490
|
clack = __toESM(require("@clack/prompts"), 1);
|
|
6218
|
-
|
|
6219
|
-
|
|
6491
|
+
fs15 = __toESM(require("fs"), 1);
|
|
6492
|
+
path17 = __toESM(require("path"), 1);
|
|
6220
6493
|
import_yaml2 = require("yaml");
|
|
6221
6494
|
init_errors();
|
|
6222
6495
|
}
|
|
@@ -6224,21 +6497,21 @@ var init_wizard = __esm({
|
|
|
6224
6497
|
|
|
6225
6498
|
// src/cli/templates/index.ts
|
|
6226
6499
|
function writeTemplateFile(cwd, rel, content, force) {
|
|
6227
|
-
const target =
|
|
6228
|
-
if (!force &&
|
|
6500
|
+
const target = path18.join(cwd, rel);
|
|
6501
|
+
if (!force && fs16.existsSync(target)) {
|
|
6229
6502
|
return "skipped";
|
|
6230
6503
|
}
|
|
6231
|
-
|
|
6232
|
-
|
|
6504
|
+
fs16.mkdirSync(path18.dirname(target), { recursive: true });
|
|
6505
|
+
fs16.writeFileSync(target, content, "utf8");
|
|
6233
6506
|
return "written";
|
|
6234
6507
|
}
|
|
6235
|
-
var
|
|
6508
|
+
var fs16, path18, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
|
|
6236
6509
|
var init_templates = __esm({
|
|
6237
6510
|
"src/cli/templates/index.ts"() {
|
|
6238
6511
|
"use strict";
|
|
6239
6512
|
init_cjs_shims();
|
|
6240
|
-
|
|
6241
|
-
|
|
6513
|
+
fs16 = __toESM(require("fs"), 1);
|
|
6514
|
+
path18 = __toESM(require("path"), 1);
|
|
6242
6515
|
ENV_EXAMPLE_TPL = `# Copy this file to .env and replace the placeholders with your real keys.
|
|
6243
6516
|
# .env is gitignored; .env.example is safe to commit.
|
|
6244
6517
|
# Plune reads whichever key matches your configured provider.
|
|
@@ -6418,14 +6691,14 @@ var init_logout = __esm({
|
|
|
6418
6691
|
|
|
6419
6692
|
// src/cli/commands/cairn-artifact.ts
|
|
6420
6693
|
async function findLatestCairnRun(runsDir = "runs") {
|
|
6421
|
-
const entries = await
|
|
6694
|
+
const entries = await fs17.readdir(runsDir, { withFileTypes: true }).catch(() => null);
|
|
6422
6695
|
if (entries === null) throw new NoCairnRunsError(runsDir);
|
|
6423
6696
|
let newest;
|
|
6424
6697
|
for (const entry of entries) {
|
|
6425
6698
|
if (!entry.isDirectory()) continue;
|
|
6426
|
-
const dir =
|
|
6699
|
+
const dir = path19.join(runsDir, entry.name);
|
|
6427
6700
|
try {
|
|
6428
|
-
const report = await
|
|
6701
|
+
const report = await fs17.lstat(path19.join(dir, "report.json"));
|
|
6429
6702
|
if (!report.isFile()) continue;
|
|
6430
6703
|
if (newest === void 0 || report.mtimeMs > newest.at) {
|
|
6431
6704
|
newest = { dir, at: report.mtimeMs };
|
|
@@ -6438,13 +6711,13 @@ async function findLatestCairnRun(runsDir = "runs") {
|
|
|
6438
6711
|
}
|
|
6439
6712
|
async function readJson(file) {
|
|
6440
6713
|
try {
|
|
6441
|
-
return JSON.parse(await
|
|
6714
|
+
return JSON.parse(await fs17.readFile(file, "utf8"));
|
|
6442
6715
|
} catch {
|
|
6443
6716
|
return void 0;
|
|
6444
6717
|
}
|
|
6445
6718
|
}
|
|
6446
6719
|
async function defaultReadEvidence(dir) {
|
|
6447
|
-
const raw = await readJson(
|
|
6720
|
+
const raw = await readJson(path19.join(dir, "api-evidence.json"));
|
|
6448
6721
|
if (!Array.isArray(raw)) return void 0;
|
|
6449
6722
|
return raw.map((r) => {
|
|
6450
6723
|
const rec = asRecord(r);
|
|
@@ -6487,7 +6760,7 @@ function casesFromApi(report, verdicts) {
|
|
|
6487
6760
|
});
|
|
6488
6761
|
}
|
|
6489
6762
|
async function readCairnRun(dir, deps = {}) {
|
|
6490
|
-
const raw = await readJson(
|
|
6763
|
+
const raw = await readJson(path19.join(dir, "report.json"));
|
|
6491
6764
|
if (raw === void 0) throw new CairnRunNotFoundError(dir);
|
|
6492
6765
|
const report = asRecord(raw);
|
|
6493
6766
|
if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
|
|
@@ -6517,20 +6790,20 @@ async function readCairnRun(dir, deps = {}) {
|
|
|
6517
6790
|
return {
|
|
6518
6791
|
schemaVersion: INGEST_SCHEMA_VERSION,
|
|
6519
6792
|
run: {
|
|
6520
|
-
runId: str(report["runId"],
|
|
6793
|
+
runId: str(report["runId"], path19.basename(dir)),
|
|
6521
6794
|
mode,
|
|
6522
6795
|
target: str(report["url"], "unknown")
|
|
6523
6796
|
},
|
|
6524
6797
|
cases
|
|
6525
6798
|
};
|
|
6526
6799
|
}
|
|
6527
|
-
var
|
|
6800
|
+
var fs17, path19, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
|
|
6528
6801
|
var init_cairn_artifact = __esm({
|
|
6529
6802
|
"src/cli/commands/cairn-artifact.ts"() {
|
|
6530
6803
|
"use strict";
|
|
6531
6804
|
init_cjs_shims();
|
|
6532
|
-
|
|
6533
|
-
|
|
6805
|
+
fs17 = __toESM(require("fs/promises"), 1);
|
|
6806
|
+
path19 = __toESM(require("path"), 1);
|
|
6534
6807
|
SUPPORTED_ARTIFACT_VERSION = 1;
|
|
6535
6808
|
INGEST_SCHEMA_VERSION = 1;
|
|
6536
6809
|
NoCairnRunsError = class extends Error {
|
|
@@ -6602,7 +6875,7 @@ async function handleIngest(deps) {
|
|
|
6602
6875
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
6603
6876
|
const endpoint = `${apiUrl}/v1/ingest/cairn`;
|
|
6604
6877
|
const job = new JobReporter(doFetch, apiUrl, token, "ingest");
|
|
6605
|
-
await job.open(
|
|
6878
|
+
await job.open(path20.basename(path20.resolve(dir)), payload.cases.length);
|
|
6606
6879
|
try {
|
|
6607
6880
|
const result = await upload();
|
|
6608
6881
|
await job.uploaded({ subjectId: result.runId || void 0 });
|
|
@@ -6661,12 +6934,12 @@ function formatIngestResult(r) {
|
|
|
6661
6934
|
return `${lines.join("\n")}
|
|
6662
6935
|
`;
|
|
6663
6936
|
}
|
|
6664
|
-
var
|
|
6937
|
+
var path20, IngestNotLoggedInError, IngestTokenRejectedError, IngestNetworkError, IngestHttpError;
|
|
6665
6938
|
var init_ingest = __esm({
|
|
6666
6939
|
"src/cli/commands/ingest.ts"() {
|
|
6667
6940
|
"use strict";
|
|
6668
6941
|
init_cjs_shims();
|
|
6669
|
-
|
|
6942
|
+
path20 = __toESM(require("path"), 1);
|
|
6670
6943
|
init_api_url();
|
|
6671
6944
|
init_credentials();
|
|
6672
6945
|
init_cairn_artifact();
|
|
@@ -7345,6 +7618,7 @@ function parseDuration(text2) {
|
|
|
7345
7618
|
}
|
|
7346
7619
|
|
|
7347
7620
|
// src/cli.ts
|
|
7621
|
+
init_min_score();
|
|
7348
7622
|
var __dirname = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
7349
7623
|
var pkg = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
7350
7624
|
function toFormat(s) {
|
|
@@ -7616,10 +7890,20 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
|
|
|
7616
7890
|
});
|
|
7617
7891
|
runCommand.command("import").argument(
|
|
7618
7892
|
"<file>",
|
|
7619
|
-
"A JUnit XML or Playwright JSON report, a folder of JUnit XML files (target/surefire-reports, build/test-results/test), or an Allure allure-results folder"
|
|
7893
|
+
"A JUnit XML or Playwright JSON report, a k6 summary or Lighthouse JSON report, a folder of JUnit XML files (target/surefire-reports, build/test-results/test), or an Allure allure-results folder"
|
|
7620
7894
|
).description("Turn a report another runner wrote into a run in Plune \u2014 no provider key needed").option(
|
|
7621
7895
|
"--format <fmt>",
|
|
7622
|
-
"junit | playwright-json | allure; detected when omitted (a folder with a *-result.json is allure, any other folder is its *.xml files as junit)"
|
|
7896
|
+
"junit | playwright-json | allure | k6 | lighthouse; detected when omitted (a folder with a *-result.json is allure, any other folder is its *.xml files as junit)"
|
|
7897
|
+
).option(
|
|
7898
|
+
"--min-score <category=score>",
|
|
7899
|
+
"Lighthouse: the score (0-100) a category needs to pass, e.g. performance=90; repeat it per category. A category not named needs 90",
|
|
7900
|
+
(v, previous) => {
|
|
7901
|
+
try {
|
|
7902
|
+
return parseMinScore(v, previous);
|
|
7903
|
+
} catch (err) {
|
|
7904
|
+
throw new import_commander.InvalidArgumentError(err.message);
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7623
7907
|
).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(
|
|
7624
7908
|
"--fail-on-missing-attachments",
|
|
7625
7909
|
"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",
|
|
@@ -7663,6 +7947,7 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
|
|
|
7663
7947
|
...format !== void 0 ? { format } : {},
|
|
7664
7948
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
7665
7949
|
...options.name !== void 0 ? { name: options.name } : {},
|
|
7950
|
+
...options.minScore !== void 0 ? { minScores: options.minScore } : {},
|
|
7666
7951
|
...options.create ? { create: true } : {},
|
|
7667
7952
|
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
7668
7953
|
...options.timeout !== void 0 ? { timeoutSec: options.timeout } : {},
|
|
@@ -7750,10 +8035,10 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
|
|
|
7750
8035
|
const { handleLogin: handleLogin2, reportLoginFailure: reportLoginFailure2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
7751
8036
|
try {
|
|
7752
8037
|
const token = options.token ?? await readTokenFromStdin();
|
|
7753
|
-
const { path:
|
|
8038
|
+
const { path: path21, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
|
|
7754
8039
|
process.stdout.write(
|
|
7755
|
-
verified ? `Logged in. Token checked against the API and saved to ${
|
|
7756
|
-
` : `Token saved to ${
|
|
8040
|
+
verified ? `Logged in. Token checked against the API and saved to ${path21}
|
|
8041
|
+
` : `Token saved to ${path21} WITHOUT being checked (--skip-verify).
|
|
7757
8042
|
`
|
|
7758
8043
|
);
|
|
7759
8044
|
} catch (err) {
|
|
@@ -7770,9 +8055,9 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
|
|
|
7770
8055
|
const verbose = command.optsWithGlobals().verbose === true;
|
|
7771
8056
|
const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
|
|
7772
8057
|
try {
|
|
7773
|
-
const { removed, path:
|
|
8058
|
+
const { removed, path: path21 } = handleLogout2();
|
|
7774
8059
|
process.stdout.write(
|
|
7775
|
-
removed ? `Logged out. Removed ${
|
|
8060
|
+
removed ? `Logged out. Removed ${path21}
|
|
7776
8061
|
` : "Not logged in \u2014 nothing to remove.\n"
|
|
7777
8062
|
);
|
|
7778
8063
|
} catch (err) {
|