@plune-ai/cli 0.16.0 → 0.17.1
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 +121 -1
- package/dist/cli.cjs +1073 -351
- package/dist/index.cjs +105 -42
- package/dist/index.d.cts +188 -1
- package/dist/index.d.ts +188 -1
- package/dist/index.js +101 -41
- package/dist/reporter-core.cjs +441 -176
- package/dist/reporter-core.d.cts +65 -6
- package/dist/reporter-core.d.ts +65 -6
- package/dist/reporter-core.js +438 -173
- package/package.json +2 -1
package/dist/cli.cjs
CHANGED
|
@@ -512,7 +512,7 @@ var init_retry = __esm({
|
|
|
512
512
|
"use strict";
|
|
513
513
|
init_cjs_shims();
|
|
514
514
|
init_errors2();
|
|
515
|
-
defaultSleep = (ms) => new Promise((
|
|
515
|
+
defaultSleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
516
516
|
}
|
|
517
517
|
});
|
|
518
518
|
|
|
@@ -874,7 +874,7 @@ var init_embedder = __esm({
|
|
|
874
874
|
throw new Error(
|
|
875
875
|
"The `semantic-similarity` and RAG assertions need local embeddings, which ship separately: install @huggingface/transformers to enable them. Every other assertion works without it."
|
|
876
876
|
);
|
|
877
|
-
}).then(({ pipeline }) =>
|
|
877
|
+
}).then(({ pipeline: pipeline2 }) => pipeline2("feature-extraction", MODEL_ID));
|
|
878
878
|
return this.extractor;
|
|
879
879
|
}
|
|
880
880
|
};
|
|
@@ -2226,7 +2226,7 @@ var init_credentials = __esm({
|
|
|
2226
2226
|
|
|
2227
2227
|
// src/reporter-core/client.ts
|
|
2228
2228
|
function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
2229
|
-
return new Promise((
|
|
2229
|
+
return new Promise((resolve9, reject) => {
|
|
2230
2230
|
const target = new URL(url);
|
|
2231
2231
|
const send = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2232
2232
|
const req = send(target, { method: init.method, headers: init.headers }, (res) => {
|
|
@@ -2236,7 +2236,7 @@ function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
|
2236
2236
|
res.on("end", () => {
|
|
2237
2237
|
const status = res.statusCode ?? 0;
|
|
2238
2238
|
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
2239
|
-
|
|
2239
|
+
resolve9({
|
|
2240
2240
|
ok: status >= 200 && status <= 299,
|
|
2241
2241
|
status,
|
|
2242
2242
|
headers: {
|
|
@@ -2254,12 +2254,42 @@ function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
|
2254
2254
|
req.end(init.body);
|
|
2255
2255
|
});
|
|
2256
2256
|
}
|
|
2257
|
+
function nodePut(url, headers, source, idleMs) {
|
|
2258
|
+
return new Promise((resolve9, reject) => {
|
|
2259
|
+
const target = new URL(url);
|
|
2260
|
+
const send = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2261
|
+
const length = "body" in source ? source.body.length : source.size;
|
|
2262
|
+
let answered;
|
|
2263
|
+
const settle = (err) => answered !== void 0 ? resolve9(answered) : reject(err);
|
|
2264
|
+
const req = send(
|
|
2265
|
+
target,
|
|
2266
|
+
{ method: "PUT", headers: { ...withoutLength(headers), "content-length": String(length) } },
|
|
2267
|
+
(res) => {
|
|
2268
|
+
const retry = res.headers["retry-after"];
|
|
2269
|
+
answered = { status: res.statusCode ?? 0, retryAfter: Array.isArray(retry) ? retry[0] ?? null : retry ?? null };
|
|
2270
|
+
res.on("error", settle);
|
|
2271
|
+
res.on("close", () => {
|
|
2272
|
+
resolve9(answered);
|
|
2273
|
+
req.destroy();
|
|
2274
|
+
});
|
|
2275
|
+
res.resume();
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
2278
|
+
req.setTimeout(idleMs, () => req.destroy(Object.assign(new Error("idle"), { code: "ETIMEDOUT" })));
|
|
2279
|
+
req.on("error", settle);
|
|
2280
|
+
if ("body" in source) req.end(source.body);
|
|
2281
|
+
else (0, import_node_stream.pipeline)((0, import_node_fs2.createReadStream)(source.path), req, (err) => err ? settle(err) : void 0);
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2257
2284
|
async function detailOf(res) {
|
|
2258
2285
|
try {
|
|
2259
2286
|
const body = await res.json();
|
|
2260
|
-
return
|
|
2287
|
+
return {
|
|
2288
|
+
detail: typeof body.error === "string" ? body.error : "",
|
|
2289
|
+
...typeof body.code === "string" && /^[a-z0-9_]{1,60}$/.test(body.code) ? { code: body.code } : {}
|
|
2290
|
+
};
|
|
2261
2291
|
} catch {
|
|
2262
|
-
return "";
|
|
2292
|
+
return { detail: "" };
|
|
2263
2293
|
}
|
|
2264
2294
|
}
|
|
2265
2295
|
function retryAfterMs2(res) {
|
|
@@ -2280,7 +2310,7 @@ function createClient(opts) {
|
|
|
2280
2310
|
const wait = opts.wait ?? sleep;
|
|
2281
2311
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
2282
2312
|
const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
|
|
2283
|
-
async function send(method,
|
|
2313
|
+
async function send(method, path17, payload) {
|
|
2284
2314
|
let last = {
|
|
2285
2315
|
kind: "unavailable",
|
|
2286
2316
|
status: null,
|
|
@@ -2289,7 +2319,7 @@ function createClient(opts) {
|
|
|
2289
2319
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
|
|
2290
2320
|
let res;
|
|
2291
2321
|
try {
|
|
2292
|
-
res = await doFetch(`${base}${
|
|
2322
|
+
res = await doFetch(`${base}${path17}`, {
|
|
2293
2323
|
method,
|
|
2294
2324
|
headers: { "content-type": payload?.type ?? "application/json", authorization: `Bearer ${opts.token}` },
|
|
2295
2325
|
// A DELETE carries none. Sending `"undefined"` as a body is what a naive
|
|
@@ -2310,10 +2340,16 @@ function createClient(opts) {
|
|
|
2310
2340
|
return { ok: false, kind: "other", status: res.status, detail: "the answer was not JSON" };
|
|
2311
2341
|
}
|
|
2312
2342
|
}
|
|
2313
|
-
const
|
|
2343
|
+
const said = await detailOf(res);
|
|
2314
2344
|
const kind = classify3(res.status);
|
|
2315
|
-
last = { kind, status: res.status, detail };
|
|
2345
|
+
last = { kind, status: res.status, detail: scrub(said.detail), ...said.code !== void 0 ? { code: said.code } : {} };
|
|
2316
2346
|
if (kind !== "unavailable") break;
|
|
2347
|
+
if (res.status === 429 && retryAfterMs2(res) === null) {
|
|
2348
|
+
last.kind = "invalid";
|
|
2349
|
+
last.detail = "quota exceeded (no Retry-After)";
|
|
2350
|
+
if (said.detail) last.detail += ` \u2014 ${said.detail}`;
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2317
2353
|
if (attempt < RETRY_DELAYS_MS.length) {
|
|
2318
2354
|
await wait(retryAfterMs2(res) ?? RETRY_DELAYS_MS[attempt]);
|
|
2319
2355
|
}
|
|
@@ -2321,20 +2357,57 @@ function createClient(opts) {
|
|
|
2321
2357
|
return { ok: false, ...last };
|
|
2322
2358
|
}
|
|
2323
2359
|
return {
|
|
2324
|
-
post: (
|
|
2325
|
-
upload: (
|
|
2326
|
-
del: (
|
|
2360
|
+
post: (path17, body) => send("POST", path17, { body: JSON.stringify(body), type: "application/json" }),
|
|
2361
|
+
upload: (path17, bytes, contentType) => send("POST", path17, { body: bytes, type: contentType }),
|
|
2362
|
+
del: (path17) => send("DELETE", path17),
|
|
2363
|
+
async put(url, headers, source) {
|
|
2364
|
+
let last = { ok: false, status: null, code: "network" };
|
|
2365
|
+
for (let attempt = 0; attempt <= PUT_DELAYS_MS.length; attempt += 1) {
|
|
2366
|
+
try {
|
|
2367
|
+
let status;
|
|
2368
|
+
let retryAfter;
|
|
2369
|
+
if (opts.fetchImpl === void 0) ({ status, retryAfter } = await nodePut(url, headers, source, IDLE_MS));
|
|
2370
|
+
else {
|
|
2371
|
+
const body = "body" in source ? source.body : await (0, import_node_fs2.openAsBlob)(source.path);
|
|
2372
|
+
const res = await opts.fetchImpl(url, { method: "PUT", headers: withoutLength(headers), body });
|
|
2373
|
+
await res.arrayBuffer().catch(() => void 0);
|
|
2374
|
+
status = res.status;
|
|
2375
|
+
retryAfter = res.headers.get("retry-after");
|
|
2376
|
+
}
|
|
2377
|
+
if (status >= 200 && status <= 299) return { ok: true };
|
|
2378
|
+
last = { ok: false, status, code: `bucket_${status}` };
|
|
2379
|
+
if (status < 500 && status !== 429) return last;
|
|
2380
|
+
const seconds = retryAfter === null ? NaN : Number(retryAfter.trim());
|
|
2381
|
+
if (attempt < PUT_DELAYS_MS.length) {
|
|
2382
|
+
await wait(Number.isFinite(seconds) && seconds >= 0 ? Math.min(seconds, 60) * 1e3 : PUT_DELAYS_MS[attempt]);
|
|
2383
|
+
}
|
|
2384
|
+
continue;
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
last = { ok: false, status: null, code: codeOfError(err) };
|
|
2387
|
+
}
|
|
2388
|
+
if (attempt < PUT_DELAYS_MS.length) await wait(PUT_DELAYS_MS[attempt]);
|
|
2389
|
+
}
|
|
2390
|
+
return last;
|
|
2391
|
+
}
|
|
2327
2392
|
};
|
|
2328
2393
|
}
|
|
2329
|
-
var import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, sleep;
|
|
2394
|
+
var import_node_fs2, import_node_stream, import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, PUT_DELAYS_MS, withoutLength, codeOfError, sleep;
|
|
2330
2395
|
var init_client = __esm({
|
|
2331
2396
|
"src/reporter-core/client.ts"() {
|
|
2332
2397
|
"use strict";
|
|
2333
2398
|
init_cjs_shims();
|
|
2399
|
+
import_node_fs2 = require("fs");
|
|
2400
|
+
import_node_stream = require("stream");
|
|
2334
2401
|
import_node_http = require("http");
|
|
2335
2402
|
import_node_https = require("https");
|
|
2336
2403
|
RETRY_DELAYS_MS = [300, 900];
|
|
2337
2404
|
IDLE_MS = 3e5;
|
|
2405
|
+
PUT_DELAYS_MS = [500, 1500, 4e3];
|
|
2406
|
+
withoutLength = (headers) => Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== "content-length"));
|
|
2407
|
+
codeOfError = (err) => {
|
|
2408
|
+
const code = err?.code;
|
|
2409
|
+
return typeof code === "string" && /^[A-Z0-9_]{2,40}$/.test(code) ? code : "network";
|
|
2410
|
+
};
|
|
2338
2411
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2339
2412
|
}
|
|
2340
2413
|
});
|
|
@@ -2456,11 +2529,150 @@ var init_fallback = __esm({
|
|
|
2456
2529
|
}
|
|
2457
2530
|
});
|
|
2458
2531
|
|
|
2532
|
+
// src/reporter-core/failure-detail.ts
|
|
2533
|
+
function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
2534
|
+
const error = chosen(attempt);
|
|
2535
|
+
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
2536
|
+
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
2537
|
+
const steps = chainOf(attempt.steps);
|
|
2538
|
+
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
2539
|
+
const artifacts = attempt.attachments.filter((a) => a.path !== void 0 && !a.name.startsWith("_") && a.contentType !== METADATA_TYPE).map(({ name, contentType }) => ({ name: fileNameOf(name), ...contentType ? { contentType } : {} })).filter((a) => a.name !== "");
|
|
2540
|
+
const ciUrl = webLink(attempt.buildHref);
|
|
2541
|
+
const failure = {
|
|
2542
|
+
...headline !== void 0 ? { headline } : {},
|
|
2543
|
+
...steps.length > 0 ? { steps } : {},
|
|
2544
|
+
...location !== void 0 ? { location } : {},
|
|
2545
|
+
...artifacts.length > 0 ? { artifacts } : {},
|
|
2546
|
+
...ciUrl !== void 0 ? { ciUrl } : {}
|
|
2547
|
+
};
|
|
2548
|
+
return Object.keys(failure).length === 0 ? void 0 : failure;
|
|
2549
|
+
}
|
|
2550
|
+
function repoRootOf(dir, stopAt) {
|
|
2551
|
+
for (let here = (0, import_node_path2.resolve)(dir); ; here = (0, import_node_path2.dirname)(here)) {
|
|
2552
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path2.join)(here, ".git"))) return here;
|
|
2553
|
+
if (here === stopAt || (0, import_node_path2.dirname)(here) === here) return void 0;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
2557
|
+
const text2 = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
2558
|
+
return cut(machineless(text2, attempt.repoRoot, home));
|
|
2559
|
+
}
|
|
2560
|
+
function filesOf(attempt) {
|
|
2561
|
+
return attempt.attachments.filter((a) => a.path !== void 0 || a.body !== void 0 && isTextType(a.contentType));
|
|
2562
|
+
}
|
|
2563
|
+
function machineless(raw, repoRoot, home) {
|
|
2564
|
+
let text2 = (0, import_node_util.stripVTControlCharacters)(raw).replaceAll("\x1B", "");
|
|
2565
|
+
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
2566
|
+
if (root !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
2567
|
+
const own = pathIn(home);
|
|
2568
|
+
if (own !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
2569
|
+
if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
2570
|
+
return text2;
|
|
2571
|
+
}
|
|
2572
|
+
function pathIn(path17) {
|
|
2573
|
+
const parts = path17.split(/[\\/]+/);
|
|
2574
|
+
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
2575
|
+
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
2576
|
+
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
2577
|
+
const escaped = /^\/(?!\/)/.test(path17) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
2578
|
+
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
2579
|
+
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
2580
|
+
}
|
|
2581
|
+
function cut(text2) {
|
|
2582
|
+
if (jsonBytes(text2) <= TEXT_LIMIT) return text2;
|
|
2583
|
+
const lines = text2.split("\n");
|
|
2584
|
+
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
2585
|
+
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
2586
|
+
let used = 0;
|
|
2587
|
+
let head = 0;
|
|
2588
|
+
let tail = lines.length;
|
|
2589
|
+
while (head < tail && used + size[head] <= budget / 2) used += size[head++];
|
|
2590
|
+
while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
|
|
2591
|
+
return [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n");
|
|
2592
|
+
}
|
|
2593
|
+
function chosen({ status, errors }) {
|
|
2594
|
+
if (status === "timedOut") {
|
|
2595
|
+
const timeout = errors.findIndex((e) => firstLine(e.text)?.startsWith("Test timeout of ") === true);
|
|
2596
|
+
if (timeout !== -1 && timeout + 1 < errors.length) return errors[timeout + 1];
|
|
2597
|
+
}
|
|
2598
|
+
return errors[0];
|
|
2599
|
+
}
|
|
2600
|
+
function chainOf(steps) {
|
|
2601
|
+
const chain = [];
|
|
2602
|
+
for (let failed = steps.find((s) => s.failed); failed !== void 0; failed = failed.steps.find((s) => s.failed)) {
|
|
2603
|
+
chain.push(failed.title);
|
|
2604
|
+
}
|
|
2605
|
+
return chain;
|
|
2606
|
+
}
|
|
2607
|
+
function placeOf(error, testFile) {
|
|
2608
|
+
const own = normal(testFile);
|
|
2609
|
+
let first;
|
|
2610
|
+
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
2611
|
+
if (line.length > FRAME_MAX) continue;
|
|
2612
|
+
const frame = FRAME.exec(line);
|
|
2613
|
+
if (frame === null) continue;
|
|
2614
|
+
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
2615
|
+
if (normal(place.file) === own) return place;
|
|
2616
|
+
if (first === void 0 && !normal(place.file).includes("/node_modules/")) first = place;
|
|
2617
|
+
}
|
|
2618
|
+
return error.location ?? first;
|
|
2619
|
+
}
|
|
2620
|
+
function fileOf(raw) {
|
|
2621
|
+
if (!raw.startsWith("file://")) return raw;
|
|
2622
|
+
let path17 = raw.slice("file://".length);
|
|
2623
|
+
try {
|
|
2624
|
+
path17 = decodeURIComponent(path17);
|
|
2625
|
+
} catch {
|
|
2626
|
+
}
|
|
2627
|
+
return /^\/[A-Za-z]:\//.test(path17) ? path17.slice(1) : path17;
|
|
2628
|
+
}
|
|
2629
|
+
function fromRoot(place, root) {
|
|
2630
|
+
if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
|
|
2631
|
+
const base = normal(root).replace(/\/+$/, "");
|
|
2632
|
+
const file = normal(place.file);
|
|
2633
|
+
if (!file.startsWith(`${base}/`)) return void 0;
|
|
2634
|
+
const relative3 = file.slice(base.length + 1);
|
|
2635
|
+
if (!REPO_RELATIVE.test(relative3)) return void 0;
|
|
2636
|
+
const column = place.column !== void 0 && Number.isInteger(place.column) && place.column >= 1 ? place.column : void 0;
|
|
2637
|
+
return column === void 0 ? { file: relative3, line: place.line } : { file: relative3, line: place.line, column };
|
|
2638
|
+
}
|
|
2639
|
+
function webLink(href) {
|
|
2640
|
+
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
2641
|
+
}
|
|
2642
|
+
var import_node_fs3, import_node_os2, import_node_path2, import_node_util, printable, mediaTypeOf, TEXT_TYPES, isTextType, PATH_START, TEXT_LIMIT, HEADLINE_LIMIT, jsonBytes, ANY_HOME, fileNameOf, omitted, METADATA_TYPE, REPO_RELATIVE, FRAME, FRAME_MAX, firstLine, normal;
|
|
2643
|
+
var init_failure_detail = __esm({
|
|
2644
|
+
"src/reporter-core/failure-detail.ts"() {
|
|
2645
|
+
"use strict";
|
|
2646
|
+
init_cjs_shims();
|
|
2647
|
+
import_node_fs3 = require("fs");
|
|
2648
|
+
import_node_os2 = require("os");
|
|
2649
|
+
import_node_path2 = require("path");
|
|
2650
|
+
import_node_util = require("util");
|
|
2651
|
+
printable = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, "?");
|
|
2652
|
+
mediaTypeOf = (contentType) => contentType?.split(";")[0]?.trim().toLowerCase() || void 0;
|
|
2653
|
+
TEXT_TYPES = /* @__PURE__ */ new Set(["application/json", "text/plain"]);
|
|
2654
|
+
isTextType = (contentType) => TEXT_TYPES.has(mediaTypeOf(contentType) ?? "");
|
|
2655
|
+
PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
2656
|
+
TEXT_LIMIT = 524288;
|
|
2657
|
+
HEADLINE_LIMIT = 8 * 1024;
|
|
2658
|
+
jsonBytes = (text2) => Buffer.byteLength(JSON.stringify(text2)) - 2;
|
|
2659
|
+
ANY_HOME = /(?<=^|[\s'"`(=,[])(?:(?:file|webpack|webpack-internal):(?:\\*\/){2,3})?(?:\\*\/home\\*\/|\\*\/Users\\*\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
2660
|
+
fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
2661
|
+
omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
2662
|
+
METADATA_TYPE = "application/plune.metadata+json";
|
|
2663
|
+
REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
2664
|
+
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
2665
|
+
FRAME_MAX = 8192;
|
|
2666
|
+
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
2667
|
+
normal = (path17) => import_node_path2.posix.normalize(path17.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
2668
|
+
}
|
|
2669
|
+
});
|
|
2670
|
+
|
|
2459
2671
|
// src/reporter-core/session.ts
|
|
2460
2672
|
function nameOf(file) {
|
|
2461
|
-
const own = file.name.split(/[\\/]/).pop()?.trim() || (0,
|
|
2462
|
-
const fileExt = (0,
|
|
2463
|
-
const ownExt = (0,
|
|
2673
|
+
const own = file.name.split(/[\\/]/).pop()?.trim() || (file.path === void 0 ? "" : (0, import_node_path3.basename)(file.path));
|
|
2674
|
+
const fileExt = file.path === void 0 ? "" : (0, import_node_path3.extname)(file.path);
|
|
2675
|
+
const ownExt = (0, import_node_path3.extname)(own);
|
|
2464
2676
|
const typed = ownExt !== "" && (ownExt.toLowerCase() === fileExt.toLowerCase() || TYPE_BY_EXTENSION[ownExt.toLowerCase()] !== void 0);
|
|
2465
2677
|
const ext = typed ? ownExt : fileExt;
|
|
2466
2678
|
let stem = typed ? own.slice(0, -ownExt.length) : own;
|
|
@@ -2470,6 +2682,41 @@ function nameOf(file) {
|
|
|
2470
2682
|
}
|
|
2471
2683
|
return `${stem}${ext}`;
|
|
2472
2684
|
}
|
|
2685
|
+
function kindOf(type) {
|
|
2686
|
+
if (type.startsWith("image/")) return "screenshot";
|
|
2687
|
+
return TEXT_TYPES.has(type) ? "text" : "file";
|
|
2688
|
+
}
|
|
2689
|
+
function linkOf(item) {
|
|
2690
|
+
const link = item;
|
|
2691
|
+
if (typeof link?.id !== "string" || typeof link.url !== "string") return void 0;
|
|
2692
|
+
const headers = typeof link.headers === "object" && link.headers !== null ? link.headers : {};
|
|
2693
|
+
return { id: link.id, url: link.url, headers };
|
|
2694
|
+
}
|
|
2695
|
+
function textTypeOf(contentType, type) {
|
|
2696
|
+
const charset = /;\s*charset\s*=\s*"?([^\s";]+)"?/i.exec(contentType)?.[1];
|
|
2697
|
+
return charset?.toLowerCase() === "utf-8" ? `${type}; charset=utf-8` : type;
|
|
2698
|
+
}
|
|
2699
|
+
function textRefusal(bytes) {
|
|
2700
|
+
if (bytes.length > TEXT_BYTES) return "textSize";
|
|
2701
|
+
if (bytes.length === 0) return "empty";
|
|
2702
|
+
if (bytes.includes(0)) return "binary";
|
|
2703
|
+
try {
|
|
2704
|
+
UTF8.decode(bytes);
|
|
2705
|
+
} catch {
|
|
2706
|
+
return "encoding";
|
|
2707
|
+
}
|
|
2708
|
+
return void 0;
|
|
2709
|
+
}
|
|
2710
|
+
function onDisk(result) {
|
|
2711
|
+
if (!("files" in result) || result.files === void 0) return result;
|
|
2712
|
+
const { files, ...rest } = result;
|
|
2713
|
+
const kept = files.filter((f) => f.path !== void 0).map(({ body: _body, ...file }) => file);
|
|
2714
|
+
return kept.length === 0 ? rest : { ...rest, files: kept };
|
|
2715
|
+
}
|
|
2716
|
+
function tally(counts, noun) {
|
|
2717
|
+
if (counts.uploaded + counts.skipped + counts.failed === 0) return void 0;
|
|
2718
|
+
return `${counts.uploaded} ${noun}(s) uploaded` + (counts.skipped > 0 ? `, ${counts.skipped} skipped` : "") + (counts.failed > 0 ? `, ${counts.failed} failed to upload` : "");
|
|
2719
|
+
}
|
|
2473
2720
|
function boundKey(key) {
|
|
2474
2721
|
if (key.value.length <= KEY_MAX) return key;
|
|
2475
2722
|
const digest = (0, import_node_crypto2.createHash)("sha256").update(key.value).digest("hex").slice(0, 16);
|
|
@@ -2516,7 +2763,7 @@ function warnIfDropped(sent, run, log) {
|
|
|
2516
2763
|
`plune: this deployment stored none of ${names.join(", ")} \u2014 it is older than the fields. The run is otherwise reported in full.`
|
|
2517
2764
|
);
|
|
2518
2765
|
}
|
|
2519
|
-
async function startRun(passed,
|
|
2766
|
+
async function startRun(passed, declared2) {
|
|
2520
2767
|
const env = readEnv();
|
|
2521
2768
|
const stated = Object.fromEntries(Object.entries(passed).filter(([, v]) => v !== void 0));
|
|
2522
2769
|
const cfg = { ...env.config, ...stated };
|
|
@@ -2543,7 +2790,9 @@ async function startRun(passed, declared) {
|
|
|
2543
2790
|
created: 0,
|
|
2544
2791
|
unoffered: 0,
|
|
2545
2792
|
deferred: 0,
|
|
2546
|
-
screenshots: { uploaded: 0, skipped: 0, failed: 0 }
|
|
2793
|
+
screenshots: { uploaded: 0, skipped: 0, failed: 0 },
|
|
2794
|
+
textFiles: { uploaded: 0, skipped: 0, failed: 0 },
|
|
2795
|
+
otherFiles: { uploaded: 0, skipped: 0, failed: 0 }
|
|
2547
2796
|
};
|
|
2548
2797
|
const resolved = /* @__PURE__ */ new Map();
|
|
2549
2798
|
let buffer = [];
|
|
@@ -2554,6 +2803,13 @@ async function startRun(passed, declared) {
|
|
|
2554
2803
|
let done = false;
|
|
2555
2804
|
const shortened = /* @__PURE__ */ new Set();
|
|
2556
2805
|
const skipsSaid = /* @__PURE__ */ new Set();
|
|
2806
|
+
const countsOf = (kind) => kind === "text" ? stats.textFiles : kind === "file" ? stats.otherFiles : stats.screenshots;
|
|
2807
|
+
let bucketSeen = false;
|
|
2808
|
+
let noStorage = 0;
|
|
2809
|
+
let uploadsMissing = false;
|
|
2810
|
+
const failedSaid = /* @__PURE__ */ new Set();
|
|
2811
|
+
const codesSaid = /* @__PURE__ */ new Set();
|
|
2812
|
+
const rooms = /* @__PURE__ */ new Map();
|
|
2557
2813
|
function bound(keys) {
|
|
2558
2814
|
return keys.map((key) => {
|
|
2559
2815
|
const fit = boundKey(key);
|
|
@@ -2561,14 +2817,14 @@ async function startRun(passed, declared) {
|
|
|
2561
2817
|
shortened.add(key.value);
|
|
2562
2818
|
if (shortened.size === 1) {
|
|
2563
2819
|
log(
|
|
2564
|
-
`plune: a test name longer than ${KEY_MAX} characters was shortened to fit its key \u2014
|
|
2820
|
+
`plune: a test name longer than ${KEY_MAX} characters was shortened to fit its key \u2014 ${JSON.stringify(printable(key.value.slice(0, 80)))}\u2026 Give the test a shorter name; until then this is its identity.`
|
|
2565
2821
|
);
|
|
2566
2822
|
}
|
|
2567
2823
|
}
|
|
2568
2824
|
return fit;
|
|
2569
2825
|
});
|
|
2570
2826
|
}
|
|
2571
|
-
const expected =
|
|
2827
|
+
const expected = declared2?.map(bound);
|
|
2572
2828
|
if (offline) {
|
|
2573
2829
|
log('plune: no API token \u2014 run "plune login" first. Results will be written to the fallback file.');
|
|
2574
2830
|
}
|
|
@@ -2581,7 +2837,7 @@ async function startRun(passed, declared) {
|
|
|
2581
2837
|
externalKey,
|
|
2582
2838
|
session: sessionMarker,
|
|
2583
2839
|
...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
|
|
2584
|
-
results
|
|
2840
|
+
results: results.map(onDisk)
|
|
2585
2841
|
});
|
|
2586
2842
|
stats.deferred += results.length;
|
|
2587
2843
|
}
|
|
@@ -2689,57 +2945,173 @@ async function startRun(passed, declared) {
|
|
|
2689
2945
|
stats.created += out.body.results.filter((r) => r?.outcome === "created").length;
|
|
2690
2946
|
}
|
|
2691
2947
|
}
|
|
2692
|
-
function skip(name, why) {
|
|
2693
|
-
|
|
2694
|
-
if (
|
|
2695
|
-
|
|
2696
|
-
|
|
2948
|
+
function skip(kind, name, why) {
|
|
2949
|
+
countsOf(kind).skipped += 1;
|
|
2950
|
+
if (why === "noStorage") noStorage += 1;
|
|
2951
|
+
const said = why === "noStorage" ? why : `${kind}/${why}`;
|
|
2952
|
+
if (skipsSaid.has(said)) return;
|
|
2953
|
+
skipsSaid.add(said);
|
|
2954
|
+
if (why === "noStorage") log(`plune: "${name}" not stored \u2014 ${SKIPPED[why]}.`);
|
|
2955
|
+
else log(`plune: ${KINDS[kind].label} "${name}" not uploaded \u2014 ${SKIPPED[why]}.`);
|
|
2956
|
+
}
|
|
2957
|
+
function ask(job, size) {
|
|
2958
|
+
return client.post(`/v1/results/${encodeURIComponent(job.id)}/files/uploads`, {
|
|
2959
|
+
files: [{ name: job.name, contentType: job.type, size }]
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
function failed(job, why) {
|
|
2963
|
+
countsOf(job.kind).failed += 1;
|
|
2964
|
+
if (!failedSaid.has(job.kind)) {
|
|
2965
|
+
failedSaid.add(job.kind);
|
|
2966
|
+
log(`plune: could not upload a ${KINDS[job.kind].label} (${why}) \u2014 its result is reported without it.`);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function failedToBucket(job, code) {
|
|
2970
|
+
countsOf(job.kind).failed += 1;
|
|
2971
|
+
if (codesSaid.has(code)) return;
|
|
2972
|
+
codesSaid.add(code);
|
|
2973
|
+
log(`plune: "${job.name}" not uploaded to your bucket (${code}) \u2014 its result is reported without it.`);
|
|
2697
2974
|
}
|
|
2698
|
-
|
|
2975
|
+
function refusedHere(job, size) {
|
|
2976
|
+
if (job.kind === "screenshot") {
|
|
2977
|
+
if (!SCREENSHOT_TYPES.has(job.type)) return "type";
|
|
2978
|
+
if (size > SCREENSHOT_BYTES) return "size";
|
|
2979
|
+
}
|
|
2980
|
+
if (job.kind === "text" && size > TEXT_BYTES) return "textSize";
|
|
2981
|
+
return void 0;
|
|
2982
|
+
}
|
|
2983
|
+
async function uploadDirect(job, size) {
|
|
2984
|
+
const { kind } = job;
|
|
2985
|
+
if (kind === "file") return skip(kind, job.name, "noStorage");
|
|
2986
|
+
const { bytes: ceiling, size: tooBig } = KINDS[kind];
|
|
2987
|
+
if (size > ceiling) return skip(kind, job.name, tooBig);
|
|
2699
2988
|
let bytes;
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2989
|
+
if (job.body !== void 0) bytes = job.body;
|
|
2990
|
+
else {
|
|
2991
|
+
try {
|
|
2992
|
+
bytes = await import_node_fs4.promises.readFile(job.path);
|
|
2993
|
+
} catch {
|
|
2994
|
+
return skip(kind, job.name, "missing");
|
|
2995
|
+
}
|
|
2705
2996
|
}
|
|
2997
|
+
const refusal = kind === "text" ? textRefusal(bytes) : void 0;
|
|
2998
|
+
if (refusal !== void 0) return skip(kind, job.name, refusal);
|
|
2706
2999
|
const out = await client.upload(
|
|
2707
|
-
`/v1/results/${encodeURIComponent(
|
|
3000
|
+
`/v1/results/${encodeURIComponent(job.id)}/files?name=${encodeURIComponent(job.name)}`,
|
|
2708
3001
|
bytes,
|
|
2709
|
-
|
|
3002
|
+
job.type
|
|
2710
3003
|
);
|
|
2711
|
-
if (out.ok)
|
|
2712
|
-
|
|
2713
|
-
|
|
3004
|
+
if (out.ok) countsOf(kind).uploaded += 1;
|
|
3005
|
+
else failed(job, out.detail || out.kind);
|
|
3006
|
+
}
|
|
3007
|
+
async function uploadToBucket(job, size, first) {
|
|
3008
|
+
const source = job.body !== void 0 ? { body: job.body } : { path: job.path, size };
|
|
3009
|
+
let link = first;
|
|
3010
|
+
for (let renewed = false; ; ) {
|
|
3011
|
+
const put = await client.put(link.url, link.headers, source);
|
|
3012
|
+
if (put.ok) break;
|
|
3013
|
+
if (put.status !== 403 || renewed) return failedToBucket(job, put.code);
|
|
3014
|
+
renewed = true;
|
|
3015
|
+
const again = await ask(job, size);
|
|
3016
|
+
if (!again.ok) return failedToBucket(job, codeOf2(again));
|
|
3017
|
+
const fresh = linkOf(again.body?.files?.[0]);
|
|
3018
|
+
if (fresh === void 0) return failedToBucket(job, "bad_answer");
|
|
3019
|
+
link = fresh;
|
|
3020
|
+
}
|
|
3021
|
+
const done2 = await client.post(`/v1/files/${encodeURIComponent(link.id)}/complete`, {});
|
|
3022
|
+
if (!done2.ok) return failedToBucket(job, codeOf2(done2));
|
|
3023
|
+
countsOf(job.kind).uploaded += 1;
|
|
3024
|
+
}
|
|
3025
|
+
let free = UPLOADS_AT_ONCE;
|
|
3026
|
+
const waiting = [];
|
|
3027
|
+
async function gated(work) {
|
|
3028
|
+
if (free === 0) await new Promise((resolve9) => waiting.push(resolve9));
|
|
3029
|
+
else free -= 1;
|
|
3030
|
+
try {
|
|
3031
|
+
return await work();
|
|
3032
|
+
} finally {
|
|
3033
|
+
const next = waiting.shift();
|
|
3034
|
+
if (next !== void 0) next();
|
|
3035
|
+
else free += 1;
|
|
2714
3036
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3037
|
+
}
|
|
3038
|
+
async function plan(job) {
|
|
3039
|
+
const { kind } = job;
|
|
3040
|
+
let size;
|
|
3041
|
+
if (job.body !== void 0) size = job.body.length;
|
|
3042
|
+
else if (job.path === void 0) return void skip(kind, job.name, "missing");
|
|
3043
|
+
else {
|
|
3044
|
+
try {
|
|
3045
|
+
size = (await import_node_fs4.promises.stat(job.path)).size;
|
|
3046
|
+
} catch {
|
|
3047
|
+
return void skip(kind, job.name, "missing");
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
if (size === 0) return void skip(kind, job.name, "empty");
|
|
3051
|
+
const direct = () => {
|
|
3052
|
+
if (kind === "file") return void skip(kind, job.name, "noStorage");
|
|
3053
|
+
const roomKey = `${job.id}/${kind}`;
|
|
3054
|
+
const left = rooms.get(roomKey) ?? KINDS[kind].room;
|
|
3055
|
+
if (left === 0) return void skip(kind, job.name, KINDS[kind].limit);
|
|
3056
|
+
rooms.set(roomKey, left - 1);
|
|
3057
|
+
return () => uploadDirect(job, size);
|
|
3058
|
+
};
|
|
3059
|
+
if (uploadsMissing) return direct();
|
|
3060
|
+
const out = await gated(() => ask(job, size));
|
|
3061
|
+
if (!out.ok) {
|
|
3062
|
+
if (out.status === 404 && out.detail === "" && out.code === void 0) {
|
|
3063
|
+
uploadsMissing = true;
|
|
3064
|
+
return direct();
|
|
3065
|
+
}
|
|
3066
|
+
if (out.code !== void 0) failedToBucket(job, out.code);
|
|
3067
|
+
else failed(job, out.detail || out.kind);
|
|
3068
|
+
return void 0;
|
|
3069
|
+
}
|
|
3070
|
+
const item = out.body?.files?.[0];
|
|
3071
|
+
if (item?.skipped === "no_storage") return void skip(kind, job.name, refusedHere(job, size) ?? "noStorage");
|
|
3072
|
+
if (item?.direct === true) return direct();
|
|
3073
|
+
const link = linkOf(item);
|
|
3074
|
+
if (link === void 0) return void failed(job, "bad_answer");
|
|
3075
|
+
if (!bucketSeen) {
|
|
3076
|
+
bucketSeen = true;
|
|
3077
|
+
log(BUCKET_FOUND);
|
|
2718
3078
|
}
|
|
3079
|
+
return () => uploadToBucket(job, size, link);
|
|
2719
3080
|
}
|
|
2720
3081
|
async function upload(items, filesByKey) {
|
|
2721
3082
|
if (filesByKey.size === 0 || !Array.isArray(items)) return;
|
|
2722
|
-
const
|
|
3083
|
+
const jobs = [];
|
|
2723
3084
|
for (const item of items) {
|
|
2724
3085
|
if (item?.status !== "accepted" || typeof item.id !== "string" || typeof item.resultKey !== "string") continue;
|
|
2725
|
-
let room = SCREENSHOTS_PER_RESULT;
|
|
2726
3086
|
for (const file of filesByKey.get(item.resultKey) ?? []) {
|
|
2727
|
-
const type = file.contentType
|
|
2728
|
-
if (
|
|
3087
|
+
const type = mediaTypeOf(file.contentType) ?? (file.path === void 0 ? void 0 : TYPE_BY_EXTENSION[(0, import_node_path3.extname)(file.path).toLowerCase()]);
|
|
3088
|
+
if (type === void 0 || type === METADATA_TYPE2) continue;
|
|
3089
|
+
const kind = kindOf(type);
|
|
3090
|
+
if (kind !== "text" && file.path === void 0) continue;
|
|
2729
3091
|
const name = nameOf(file);
|
|
2730
|
-
if (file.name.startsWith("_")) skip(name, "hidden");
|
|
2731
|
-
else if (!SCREENSHOT_TYPES.has(type)) skip(name, "type");
|
|
2732
|
-
else if (room === 0) skip(name, "limit");
|
|
3092
|
+
if (file.name.startsWith("_")) skip(kind, name, "hidden");
|
|
2733
3093
|
else {
|
|
2734
|
-
|
|
2735
|
-
|
|
3094
|
+
jobs.push({
|
|
3095
|
+
id: item.id,
|
|
3096
|
+
kind,
|
|
3097
|
+
name,
|
|
3098
|
+
type: kind === "text" ? textTypeOf(file.contentType ?? "", type) : type,
|
|
3099
|
+
...file.body !== void 0 ? { body: file.body } : {},
|
|
3100
|
+
...file.path !== void 0 ? { path: file.path } : {}
|
|
3101
|
+
});
|
|
2736
3102
|
}
|
|
2737
3103
|
}
|
|
2738
3104
|
}
|
|
2739
|
-
|
|
3105
|
+
const byResult = /* @__PURE__ */ new Map();
|
|
3106
|
+
for (const job of jobs) byResult.set(job.id, [...byResult.get(job.id) ?? [], job]);
|
|
2740
3107
|
await Promise.all(
|
|
2741
|
-
|
|
2742
|
-
|
|
3108
|
+
[...byResult.values()].map(async (group) => {
|
|
3109
|
+
const work = [];
|
|
3110
|
+
for (const job of group) {
|
|
3111
|
+
const act = await plan(job);
|
|
3112
|
+
if (act !== void 0) work.push(gated(act));
|
|
3113
|
+
}
|
|
3114
|
+
await Promise.all(work);
|
|
2743
3115
|
})
|
|
2744
3116
|
);
|
|
2745
3117
|
}
|
|
@@ -2817,11 +3189,20 @@ async function startRun(passed, declared) {
|
|
|
2817
3189
|
if (stats.unresolved > 0) parts.push(`${stats.unresolved} with no matching test case`);
|
|
2818
3190
|
if (stats.offered > 0) parts.push(`${stats.offered} offered for review`);
|
|
2819
3191
|
if (stats.created > 0) parts.push(`${stats.created} added as cases (trusted source)`);
|
|
2820
|
-
const
|
|
2821
|
-
|
|
3192
|
+
const kinds = [stats.screenshots, stats.textFiles, stats.otherFiles];
|
|
3193
|
+
const sum = (what) => kinds.reduce((n, counts) => n + counts[what], 0);
|
|
3194
|
+
if (bucketSeen) {
|
|
3195
|
+
const other = sum("skipped") - noStorage;
|
|
2822
3196
|
parts.push(
|
|
2823
|
-
`${
|
|
3197
|
+
`${sum("uploaded")} file(s) uploaded to your bucket` + (noStorage > 0 ? `, ${noStorage} skipped (no bucket connected)` : "") + (other > 0 ? `, ${other} skipped` : "") + (sum("failed") > 0 ? `, ${sum("failed")} failed to upload` : "")
|
|
2824
3198
|
);
|
|
3199
|
+
} else {
|
|
3200
|
+
for (const said of [tally(stats.screenshots, "screenshot"), tally(stats.textFiles, "text file")]) {
|
|
3201
|
+
if (said !== void 0) parts.push(said);
|
|
3202
|
+
}
|
|
3203
|
+
const other = stats.otherFiles;
|
|
3204
|
+
if (other.skipped > 0) parts.push(`${other.skipped} file(s) not stored (no bucket connected)`);
|
|
3205
|
+
if (other.failed > 0) parts.push(`${other.failed} file(s) failed to upload`);
|
|
2825
3206
|
}
|
|
2826
3207
|
if (stats.deferred > 0) parts.push(`${stats.deferred} not delivered \u2014 written to ${fallbackPath}`);
|
|
2827
3208
|
if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
|
|
@@ -2874,18 +3255,20 @@ async function startRun(passed, declared) {
|
|
|
2874
3255
|
}
|
|
2875
3256
|
};
|
|
2876
3257
|
}
|
|
2877
|
-
var import_node_crypto2,
|
|
3258
|
+
var import_node_crypto2, import_node_fs4, import_node_path3, import_node_util2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, 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, UPLOADS_AT_ONCE, NAME_MAX, SKIPPED, BUCKET_FOUND, KINDS, codeOf2, BATCH_BYTES, ENVELOPE_BYTES;
|
|
2878
3259
|
var init_session = __esm({
|
|
2879
3260
|
"src/reporter-core/session.ts"() {
|
|
2880
3261
|
"use strict";
|
|
2881
3262
|
init_cjs_shims();
|
|
2882
3263
|
import_node_crypto2 = require("crypto");
|
|
2883
|
-
|
|
2884
|
-
|
|
3264
|
+
import_node_fs4 = require("fs");
|
|
3265
|
+
import_node_path3 = require("path");
|
|
3266
|
+
import_node_util2 = require("util");
|
|
2885
3267
|
init_api_url();
|
|
2886
3268
|
init_credentials();
|
|
2887
3269
|
init_client();
|
|
2888
3270
|
init_env2();
|
|
3271
|
+
init_failure_detail();
|
|
2889
3272
|
init_fallback();
|
|
2890
3273
|
RESOLVE_CHUNK = 500;
|
|
2891
3274
|
BATCH_MAX = 500;
|
|
@@ -2898,12 +3281,16 @@ var init_session = __esm({
|
|
|
2898
3281
|
SCREENSHOT_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
2899
3282
|
SCREENSHOT_BYTES = 2 * 1024 * 1024;
|
|
2900
3283
|
SCREENSHOTS_PER_RESULT = 20;
|
|
3284
|
+
TEXT_BYTES = 512 * 1024;
|
|
3285
|
+
TEXTS_PER_RESULT = 10;
|
|
3286
|
+
UTF8 = new import_node_util2.TextDecoder("utf-8", { fatal: true });
|
|
2901
3287
|
TYPE_BY_EXTENSION = {
|
|
2902
3288
|
".png": "image/png",
|
|
2903
3289
|
".jpg": "image/jpeg",
|
|
2904
3290
|
".jpeg": "image/jpeg",
|
|
2905
3291
|
".webp": "image/webp"
|
|
2906
3292
|
};
|
|
3293
|
+
METADATA_TYPE2 = "application/plune.metadata+json";
|
|
2907
3294
|
UPLOADS_AT_ONCE = 4;
|
|
2908
3295
|
NAME_MAX = 200;
|
|
2909
3296
|
SKIPPED = {
|
|
@@ -2911,8 +3298,22 @@ var init_session = __esm({
|
|
|
2911
3298
|
type: "Plune takes PNG, JPEG and WebP",
|
|
2912
3299
|
limit: `a result holds at most ${SCREENSHOTS_PER_RESULT}`,
|
|
2913
3300
|
missing: "the file cannot be read on this machine",
|
|
2914
|
-
size: "Plune takes a file of at most 2 MB"
|
|
3301
|
+
size: "Plune takes a file of at most 2 MB",
|
|
3302
|
+
textLimit: `a result holds at most ${TEXTS_PER_RESULT} text files`,
|
|
3303
|
+
textSize: "Plune takes a text file of at most 512 KiB",
|
|
3304
|
+
empty: "the file is empty",
|
|
3305
|
+
binary: "the file holds a NUL byte, and Plune takes UTF-8 text without one",
|
|
3306
|
+
encoding: "Plune takes UTF-8 text only",
|
|
3307
|
+
noStorage: "no storage bucket is connected to this project"
|
|
2915
3308
|
};
|
|
3309
|
+
BUCKET_FOUND = "plune: storage bucket found for this project \u2014 screenshots, text, trace and video go straight to it over short-lived links.";
|
|
3310
|
+
KINDS = {
|
|
3311
|
+
screenshot: { label: "screenshot", bytes: SCREENSHOT_BYTES, room: SCREENSHOTS_PER_RESULT, limit: "limit", size: "size" },
|
|
3312
|
+
text: { label: "text file", bytes: TEXT_BYTES, room: TEXTS_PER_RESULT, limit: "textLimit", size: "textSize" },
|
|
3313
|
+
// Only the label is used: a file of this kind has no home in the database, so no ceiling of its own is known here.
|
|
3314
|
+
file: { label: "file", bytes: 0, room: 0, limit: "limit", size: "size" }
|
|
3315
|
+
};
|
|
3316
|
+
codeOf2 = (out) => out.code ?? (out.status === null ? "network" : `http_${out.status}`);
|
|
2916
3317
|
BATCH_BYTES = 8 * 1024 * 1024;
|
|
2917
3318
|
ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
|
|
2918
3319
|
}
|
|
@@ -3003,21 +3404,21 @@ async function handleRunDelete(id, options = {}) {
|
|
|
3003
3404
|
);
|
|
3004
3405
|
}
|
|
3005
3406
|
write(`Run ${id} deleted, with its results and the review-queue entries it produced.`);
|
|
3006
|
-
write("Recoverable for
|
|
3407
|
+
write("Recoverable for 30 days \u2014 ask Plune to put it back.");
|
|
3007
3408
|
}
|
|
3008
3409
|
async function handleRunExec(options) {
|
|
3009
3410
|
const [command, ...args] = options.argv;
|
|
3010
3411
|
if (command === void 0) throw new RunCommandError("Nothing to run \u2014 pass a command after `--`.");
|
|
3011
3412
|
const run = await handleRunStart(options);
|
|
3012
3413
|
const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
|
|
3013
|
-
const code = await new Promise((
|
|
3414
|
+
const code = await new Promise((resolve9) => {
|
|
3014
3415
|
const child = spawnImpl(command, args, {
|
|
3015
3416
|
stdio: "inherit",
|
|
3016
3417
|
shell: process.platform === "win32",
|
|
3017
3418
|
env: { ...process.env, PLUNE_RUN: run.externalKey, PLUNE_PROCEED: "1" }
|
|
3018
3419
|
});
|
|
3019
|
-
child.on("close", (exitCode) =>
|
|
3020
|
-
child.on("error", () =>
|
|
3420
|
+
child.on("close", (exitCode) => resolve9(exitCode ?? 1));
|
|
3421
|
+
child.on("error", () => resolve9(1));
|
|
3021
3422
|
});
|
|
3022
3423
|
await handleRunFinish(run.id, options);
|
|
3023
3424
|
return code;
|
|
@@ -3148,6 +3549,346 @@ var init_result_key = __esm({
|
|
|
3148
3549
|
}
|
|
3149
3550
|
});
|
|
3150
3551
|
|
|
3552
|
+
// src/importers/attachments.ts
|
|
3553
|
+
function reportFolder(report) {
|
|
3554
|
+
const folder = path9.dirname(path9.resolve(report));
|
|
3555
|
+
try {
|
|
3556
|
+
return fs8.realpathSync(folder);
|
|
3557
|
+
} catch {
|
|
3558
|
+
return folder;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
function reportFolders(report) {
|
|
3562
|
+
return { lexical: path9.dirname(path9.resolve(report)), real: reportFolder(report) };
|
|
3563
|
+
}
|
|
3564
|
+
function within(folder, target) {
|
|
3565
|
+
const rel = path9.relative(folder, target);
|
|
3566
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel);
|
|
3567
|
+
}
|
|
3568
|
+
function resolveAttachment(folder, ref, lexical = folder) {
|
|
3569
|
+
if (/^[\\/]{2}/.test(ref) || ref.includes("\0")) return { why: "outside" };
|
|
3570
|
+
const face = path9.resolve(lexical, ref);
|
|
3571
|
+
if (!within(lexical, face) && !within(folder, face)) return { why: "outside" };
|
|
3572
|
+
let real;
|
|
3573
|
+
try {
|
|
3574
|
+
real = fs8.realpathSync(path9.resolve(folder, ref));
|
|
3575
|
+
} catch {
|
|
3576
|
+
return { why: "absent" };
|
|
3577
|
+
}
|
|
3578
|
+
if (!within(folder, real)) return { why: "outside" };
|
|
3579
|
+
try {
|
|
3580
|
+
return fs8.statSync(real).isFile() ? { file: real } : { why: "not-a-file" };
|
|
3581
|
+
} catch {
|
|
3582
|
+
return { why: "absent" };
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
function markersOf(test) {
|
|
3586
|
+
const refs = /* @__PURE__ */ new Set();
|
|
3587
|
+
for (const stream of test.children) {
|
|
3588
|
+
if (stream.name !== "system-out" && stream.name !== "system-err") continue;
|
|
3589
|
+
for (const match of stream.text.matchAll(MARKER)) {
|
|
3590
|
+
const ref = match[1]?.trim() ?? "";
|
|
3591
|
+
if (ref !== "") refs.add(ref);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return [...refs];
|
|
3595
|
+
}
|
|
3596
|
+
function filesOfTest(test, folder, title, missing, options = {}) {
|
|
3597
|
+
const files = [];
|
|
3598
|
+
const all = markersOf(test);
|
|
3599
|
+
if (all.length > MARKERS_PER_TEST) {
|
|
3600
|
+
options.notes?.push(
|
|
3601
|
+
`plune: ${JSON.stringify(printable(title))}: ${all.length - MARKERS_PER_TEST} more markers ignored (at most ${MARKERS_PER_TEST} per test)`
|
|
3602
|
+
);
|
|
3603
|
+
}
|
|
3604
|
+
for (const ref of all.slice(0, MARKERS_PER_TEST)) {
|
|
3605
|
+
const found = resolveAttachment(folder, ref, options.lexical);
|
|
3606
|
+
if (!("file" in found)) {
|
|
3607
|
+
missing.push({ title, path: ref, ...options.resultKey !== void 0 ? { resultKey: options.resultKey } : {} });
|
|
3608
|
+
continue;
|
|
3609
|
+
}
|
|
3610
|
+
const real = found.file;
|
|
3611
|
+
if (!files.some((f) => f.path === real)) {
|
|
3612
|
+
files.push({ name: printable(ref.split(/[\\/]/).pop() || path9.basename(real)), contentType: typeOfFile(real), path: real });
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
return files;
|
|
3616
|
+
}
|
|
3617
|
+
var fs8, path9, MARKER, TYPES, typeOfFile, MARKERS_PER_TEST;
|
|
3618
|
+
var init_attachments = __esm({
|
|
3619
|
+
"src/importers/attachments.ts"() {
|
|
3620
|
+
"use strict";
|
|
3621
|
+
init_cjs_shims();
|
|
3622
|
+
fs8 = __toESM(require("fs"), 1);
|
|
3623
|
+
path9 = __toESM(require("path"), 1);
|
|
3624
|
+
init_failure_detail();
|
|
3625
|
+
MARKER = /^[ \t]*\[\[ATTACHMENT\|([^\r\n]*?)\]\][ \t]*$/gm;
|
|
3626
|
+
TYPES = {
|
|
3627
|
+
".png": "image/png",
|
|
3628
|
+
".jpg": "image/jpeg",
|
|
3629
|
+
".jpeg": "image/jpeg",
|
|
3630
|
+
".webp": "image/webp",
|
|
3631
|
+
".gif": "image/gif",
|
|
3632
|
+
".svg": "image/svg+xml",
|
|
3633
|
+
".json": "application/json",
|
|
3634
|
+
".txt": "text/plain",
|
|
3635
|
+
".log": "text/plain",
|
|
3636
|
+
".md": "text/plain",
|
|
3637
|
+
".csv": "text/csv",
|
|
3638
|
+
".html": "text/html",
|
|
3639
|
+
".htm": "text/html",
|
|
3640
|
+
".xml": "application/xml",
|
|
3641
|
+
".pdf": "application/pdf",
|
|
3642
|
+
".zip": "application/zip",
|
|
3643
|
+
".webm": "video/webm",
|
|
3644
|
+
".mp4": "video/mp4",
|
|
3645
|
+
".har": "application/json"
|
|
3646
|
+
};
|
|
3647
|
+
typeOfFile = (file) => TYPES[path9.extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
3648
|
+
MARKERS_PER_TEST = 100;
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
|
|
3652
|
+
// src/importers/allure.ts
|
|
3653
|
+
function isFolder(path17) {
|
|
3654
|
+
try {
|
|
3655
|
+
return fs9.statSync(path17).isDirectory();
|
|
3656
|
+
} catch {
|
|
3657
|
+
return false;
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
function load(file, maxBytes) {
|
|
3661
|
+
let size;
|
|
3662
|
+
try {
|
|
3663
|
+
size = fs9.statSync(file).size;
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
throw new AllureReportError(`${printable(file)} \u2014 cannot read \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
3666
|
+
}
|
|
3667
|
+
if (size > maxBytes) {
|
|
3668
|
+
throw new AllureReportError(`${printable(file)} \u2014 ${size} bytes, over the ${maxBytes} an Allure result may have; not read.`);
|
|
3669
|
+
}
|
|
3670
|
+
let parsed;
|
|
3671
|
+
try {
|
|
3672
|
+
parsed = JSON.parse(fs9.readFileSync(file, "utf8"));
|
|
3673
|
+
} catch (err) {
|
|
3674
|
+
throw new AllureReportError(`${printable(file)} \u2014 not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
3675
|
+
}
|
|
3676
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3677
|
+
throw new AllureReportError(`${printable(file)} \u2014 not an Allure result: expected a JSON object.`);
|
|
3678
|
+
}
|
|
3679
|
+
return parsed;
|
|
3680
|
+
}
|
|
3681
|
+
function listOf(v) {
|
|
3682
|
+
const out = [];
|
|
3683
|
+
if (!Array.isArray(v)) return out;
|
|
3684
|
+
for (const x of v) if (typeof x === "object" && x !== null && !Array.isArray(x)) out.push(x);
|
|
3685
|
+
return out;
|
|
3686
|
+
}
|
|
3687
|
+
function keyOf(r, name) {
|
|
3688
|
+
const full = textOf(r.fullName);
|
|
3689
|
+
if (name.endsWith("]") && full.includes("#")) return `${full.split("#")[0]}#${name}`;
|
|
3690
|
+
let base;
|
|
3691
|
+
if (full.includes(" \u203A ")) base = full.split(" \u203A ").join("#");
|
|
3692
|
+
else if (full.includes("#")) base = full;
|
|
3693
|
+
else if (labelOf(r, "package") !== "") base = `${labelOf(r, "package")}#${name}`;
|
|
3694
|
+
else base = full !== "" ? full : name;
|
|
3695
|
+
const params = listOf(r.parameters).map((p) => textOf(p.value));
|
|
3696
|
+
return params.length > 0 && !base.endsWith("]") ? `${base}[${params.join("-")}]` : base;
|
|
3697
|
+
}
|
|
3698
|
+
function labelGroup(r) {
|
|
3699
|
+
const out = [];
|
|
3700
|
+
for (const l of ["parentSuite", "suite", "subSuite"]) {
|
|
3701
|
+
const v = labelOf(r, l);
|
|
3702
|
+
if (v !== "" && out.at(-1) !== v) out.push(v);
|
|
3703
|
+
}
|
|
3704
|
+
return out;
|
|
3705
|
+
}
|
|
3706
|
+
function attachmentsOf(holder, out) {
|
|
3707
|
+
let deep = false;
|
|
3708
|
+
const todo = [{ h: holder, depth: 0 }];
|
|
3709
|
+
while (todo.length > 0) {
|
|
3710
|
+
const { h, depth } = todo.pop();
|
|
3711
|
+
for (const a of listOf(h.attachments)) out.push(a);
|
|
3712
|
+
const steps = listOf(h.steps);
|
|
3713
|
+
if (depth >= MAX_DEPTH) {
|
|
3714
|
+
deep ||= steps.length > 0;
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
for (let i = steps.length - 1; i >= 0; i--) todo.push({ h: steps[i], depth: depth + 1 });
|
|
3718
|
+
}
|
|
3719
|
+
return deep;
|
|
3720
|
+
}
|
|
3721
|
+
function readAllure(dir, missing = [], notes = [], maxBytes = MAX_JSON_BYTES) {
|
|
3722
|
+
if (!isFolder(dir)) throw new AllureReportError(`${printable(dir)} \u2014 not a folder.`);
|
|
3723
|
+
const resultNames = namesIn(dir, RESULT);
|
|
3724
|
+
if (resultNames.length === 0) {
|
|
3725
|
+
throw new AllureReportError(
|
|
3726
|
+
`${printable(dir)} \u2014 no *-result.json files in it, so there is no Allure report to read. Point at the folder an Allure adapter wrote (pytest --alluredir, allure-results), or at a JUnit or Playwright JSON file.`
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
const { lexical, real: folder } = reportFolders(path10.join(dir, "_"));
|
|
3730
|
+
const tests = resultNames.map((n) => load(path10.join(dir, n), maxBytes));
|
|
3731
|
+
const containers = namesIn(dir, CONTAINER).map((n) => load(path10.join(dir, n), maxBytes));
|
|
3732
|
+
const direct = /* @__PURE__ */ new Map();
|
|
3733
|
+
for (const c of containers) {
|
|
3734
|
+
for (const child of Array.isArray(c.children) ? c.children : []) {
|
|
3735
|
+
if (typeof child !== "string") continue;
|
|
3736
|
+
const list2 = direct.get(child);
|
|
3737
|
+
if (list2 === void 0) direct.set(child, [c]);
|
|
3738
|
+
else list2.push(c);
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
const around = (uuid) => {
|
|
3742
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3743
|
+
const todo = [uuid];
|
|
3744
|
+
while (todo.length > 0) {
|
|
3745
|
+
for (const c of direct.get(todo.pop()) ?? []) {
|
|
3746
|
+
if (seen.has(c)) continue;
|
|
3747
|
+
seen.add(c);
|
|
3748
|
+
if (typeof c.uuid === "string") todo.push(c.uuid);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return [...seen];
|
|
3752
|
+
};
|
|
3753
|
+
const ctx = { folder, lexical, missing, notes, around, direct };
|
|
3754
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3755
|
+
tests.forEach((r, i) => {
|
|
3756
|
+
const id = textOf(r.historyId);
|
|
3757
|
+
const g = id === "" ? `\0${i}` : id;
|
|
3758
|
+
const list2 = groups.get(g);
|
|
3759
|
+
if (list2 === void 0) groups.set(g, [r]);
|
|
3760
|
+
else list2.push(r);
|
|
3761
|
+
});
|
|
3762
|
+
const out = [];
|
|
3763
|
+
for (const attempts of groups.values()) {
|
|
3764
|
+
const ordered = attempts.map((r, i) => ({ r, i })).sort(
|
|
3765
|
+
(a, b) => stopOf(a.r) - stopOf(b.r) || (timeOf(a.r.start) ?? 0) - (timeOf(b.r.start) ?? 0) || compareText(textOf(a.r.uuid), textOf(b.r.uuid)) || a.i - b.i
|
|
3766
|
+
).map((x) => x.r);
|
|
3767
|
+
ordered.forEach((r, retry) => out.push(pendingOf(r, retry, ctx)));
|
|
3768
|
+
}
|
|
3769
|
+
return out;
|
|
3770
|
+
}
|
|
3771
|
+
function pendingOf(r, retry, ctx) {
|
|
3772
|
+
const name = textOf(r.name);
|
|
3773
|
+
const uuid = textOf(r.uuid);
|
|
3774
|
+
const key = keyOf(r, name);
|
|
3775
|
+
const ancestors = uuid === "" ? [] : ctx.around(uuid);
|
|
3776
|
+
const suite = (uuid === "" ? [] : ctx.direct.get(uuid) ?? []).filter((c) => textOf(c.name) !== "" && c.name !== name).sort((a, b) => (Array.isArray(b.children) ? b.children.length : 0) - (Array.isArray(a.children) ? a.children.length : 0))[0];
|
|
3777
|
+
const group = suite !== void 0 ? [textOf(suite.name)] : labelGroup(r);
|
|
3778
|
+
const title = [...group, name].join(" \u203A ");
|
|
3779
|
+
const safeTitle = printable(title);
|
|
3780
|
+
const rkey = resultKey(key, retry);
|
|
3781
|
+
const files = [];
|
|
3782
|
+
const announced = /* @__PURE__ */ new Set();
|
|
3783
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
3784
|
+
const announce = (att) => {
|
|
3785
|
+
const source = textOf(att.source);
|
|
3786
|
+
if (source === "" || announced.has(source) || ignored.has(source)) return;
|
|
3787
|
+
if (announced.size >= MARKERS_PER_TEST) {
|
|
3788
|
+
ignored.add(source);
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
announced.add(source);
|
|
3792
|
+
const found = resolveAttachment(ctx.folder, source, ctx.lexical);
|
|
3793
|
+
if (!("file" in found)) {
|
|
3794
|
+
ctx.missing.push({ title: safeTitle, resultKey: rkey, path: source });
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (files.some((f) => f.path === found.file)) return;
|
|
3798
|
+
const type = textOf(att.type).trim();
|
|
3799
|
+
const name2 = printable(textOf(att.name) !== "" ? textOf(att.name) : path10.basename(found.file));
|
|
3800
|
+
files.push({ name: name2, contentType: type !== "" ? type : typeOfFile(found.file), path: found.file });
|
|
3801
|
+
};
|
|
3802
|
+
const holders = [];
|
|
3803
|
+
for (const c of [...ancestors].reverse()) for (const h of listOf(c.befores)) holders.push(h);
|
|
3804
|
+
holders.push(r);
|
|
3805
|
+
for (const c of ancestors) for (const h of listOf(c.afters)) holders.push(h);
|
|
3806
|
+
const atts = [];
|
|
3807
|
+
let deep = false;
|
|
3808
|
+
for (const h of holders) deep = attachmentsOf(h, atts) || deep;
|
|
3809
|
+
atts.forEach(announce);
|
|
3810
|
+
if (ignored.size > 0) {
|
|
3811
|
+
ctx.notes.push(`plune: ${JSON.stringify(safeTitle)}: ${ignored.size} more markers ignored (at most ${MARKERS_PER_TEST} per test)`);
|
|
3812
|
+
}
|
|
3813
|
+
if (deep) ctx.notes.push(`plune: ${JSON.stringify(safeTitle)}: steps nested deeper than ${MAX_DEPTH} are ignored`);
|
|
3814
|
+
const status = textOf(r.status) !== "" ? textOf(r.status) : "unknown";
|
|
3815
|
+
const details = typeof r.statusDetails === "object" && r.statusDetails !== null ? r.statusDetails : {};
|
|
3816
|
+
const message = textOf(details.message);
|
|
3817
|
+
const trace = textOf(details.trace);
|
|
3818
|
+
const attempt = {
|
|
3819
|
+
status,
|
|
3820
|
+
errors: [{ text: [message, trace].filter((s) => s !== "").join("\n\n") }],
|
|
3821
|
+
steps: declared(r.steps),
|
|
3822
|
+
attachments: files.map(({ name: n, contentType, path: p }) => ({
|
|
3823
|
+
name: n,
|
|
3824
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
3825
|
+
...p !== void 0 ? { path: p } : {}
|
|
3826
|
+
})),
|
|
3827
|
+
testFile: ""
|
|
3828
|
+
};
|
|
3829
|
+
const errorContext = errorContextOf(attempt);
|
|
3830
|
+
const failure = FAILED.has(status) ? failureOf(attempt) : void 0;
|
|
3831
|
+
const pkg2 = labelOf(r, "package");
|
|
3832
|
+
const where = (key.includes("#") ? key.split("#")[0] : pkg2)?.replace(/^[A-Za-z][A-Za-z0-9_-]+:(?![\\/])/, "");
|
|
3833
|
+
const stated = TOKEN.exec(name)?.[1];
|
|
3834
|
+
const started = timeOf(r.start);
|
|
3835
|
+
const stopped = timeOf(r.stop);
|
|
3836
|
+
const durationMs = started !== void 0 && stopped !== void 0 ? Math.max(0, stopped - started) : 0;
|
|
3837
|
+
return {
|
|
3838
|
+
resultKey: rkey,
|
|
3839
|
+
...stated !== void 0 ? { testCaseId: stated } : {},
|
|
3840
|
+
keys: [{ kind: "path-title", value: key }],
|
|
3841
|
+
source: SOURCE,
|
|
3842
|
+
rawStatus: status,
|
|
3843
|
+
title,
|
|
3844
|
+
...where !== void 0 && where !== "" ? { specRef: where } : {},
|
|
3845
|
+
...started !== void 0 ? {
|
|
3846
|
+
execution: {
|
|
3847
|
+
startedAt: new Date(started).toISOString(),
|
|
3848
|
+
finishedAt: new Date(started + durationMs).toISOString(),
|
|
3849
|
+
durationMs,
|
|
3850
|
+
retry
|
|
3851
|
+
}
|
|
3852
|
+
} : {},
|
|
3853
|
+
...errorContext !== "" ? { errorContext } : {},
|
|
3854
|
+
...failure !== void 0 ? { failure } : {},
|
|
3855
|
+
...files.length > 0 ? { files } : {}
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
var fs9, path10, SOURCE, TOKEN, AllureReportError, RESULT, CONTAINER, MAX_JSON_BYTES, MAX_DEPTH, MAX_TIME, namesIn, timeOf, textOf, labelOf, FAILED, declared, stopOf, compareText;
|
|
3859
|
+
var init_allure = __esm({
|
|
3860
|
+
"src/importers/allure.ts"() {
|
|
3861
|
+
"use strict";
|
|
3862
|
+
init_cjs_shims();
|
|
3863
|
+
fs9 = __toESM(require("fs"), 1);
|
|
3864
|
+
path10 = __toESM(require("path"), 1);
|
|
3865
|
+
init_failure_detail();
|
|
3866
|
+
init_result_key();
|
|
3867
|
+
init_attachments();
|
|
3868
|
+
SOURCE = "allure";
|
|
3869
|
+
TOKEN = /@P([A-Za-z0-9_-]+)/;
|
|
3870
|
+
AllureReportError = class extends Error {
|
|
3871
|
+
constructor(message) {
|
|
3872
|
+
super(message);
|
|
3873
|
+
this.name = "AllureReportError";
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3876
|
+
RESULT = /-result\.json$/;
|
|
3877
|
+
CONTAINER = /-container\.json$/;
|
|
3878
|
+
MAX_JSON_BYTES = 64 * 1024 * 1024;
|
|
3879
|
+
MAX_DEPTH = 256;
|
|
3880
|
+
MAX_TIME = 864e13;
|
|
3881
|
+
namesIn = (dir, pattern) => fs9.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isFile() && pattern.test(d.name)).map((d) => d.name).sort();
|
|
3882
|
+
timeOf = (v) => typeof v === "number" && Math.abs(v) <= MAX_TIME ? v : void 0;
|
|
3883
|
+
textOf = (v) => typeof v === "string" ? v : "";
|
|
3884
|
+
labelOf = (r, name) => textOf(listOf(r.labels).find((l) => l.name === name)?.value);
|
|
3885
|
+
FAILED = /* @__PURE__ */ new Set(["failed", "broken"]);
|
|
3886
|
+
declared = (steps, depth = 0) => depth >= MAX_DEPTH ? [] : listOf(steps).map((s) => ({ title: textOf(s.name), failed: FAILED.has(textOf(s.status)), steps: declared(s.steps, depth + 1) }));
|
|
3887
|
+
stopOf = (r) => timeOf(r.stop) ?? timeOf(r.start) ?? 0;
|
|
3888
|
+
compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3889
|
+
}
|
|
3890
|
+
});
|
|
3891
|
+
|
|
3151
3892
|
// src/importers/xml.ts
|
|
3152
3893
|
function decode(raw) {
|
|
3153
3894
|
if (!raw.includes("&")) return raw;
|
|
@@ -3212,9 +3953,9 @@ function parseXml(source, file) {
|
|
|
3212
3953
|
if (end === -1) throw new XmlParseError(file, line, "a closing tag is never closed");
|
|
3213
3954
|
const name2 = source.slice(i + 2, end).trim();
|
|
3214
3955
|
const open = top();
|
|
3215
|
-
if (open === root) throw new XmlParseError(file, line, `</${name2}> closes nothing`);
|
|
3956
|
+
if (open === root) throw new XmlParseError(file, line, `</${printable(name2)}> closes nothing`);
|
|
3216
3957
|
if (open.name !== name2) {
|
|
3217
|
-
throw new XmlParseError(file, line, `</${name2}> closes <${open.name}>, opened on line ${open.line}`);
|
|
3958
|
+
throw new XmlParseError(file, line, `</${printable(name2)}> closes <${printable(open.name)}>, opened on line ${open.line}`);
|
|
3218
3959
|
}
|
|
3219
3960
|
stack.pop();
|
|
3220
3961
|
advance(end + 1);
|
|
@@ -3230,7 +3971,7 @@ function parseXml(source, file) {
|
|
|
3230
3971
|
let selfClosing = false;
|
|
3231
3972
|
for (; ; ) {
|
|
3232
3973
|
while (j < source.length && SPACE.test(source[j])) j += 1;
|
|
3233
|
-
if (j >= source.length) throw new XmlParseError(file, startLine, `<${name}> is never closed`);
|
|
3974
|
+
if (j >= source.length) throw new XmlParseError(file, startLine, `<${printable(name)}> is never closed`);
|
|
3234
3975
|
if (source[j] === "/" && source[j + 1] === ">") {
|
|
3235
3976
|
selfClosing = true;
|
|
3236
3977
|
j += 2;
|
|
@@ -3243,19 +3984,19 @@ function parseXml(source, file) {
|
|
|
3243
3984
|
const attrStart = j;
|
|
3244
3985
|
while (j < source.length && !ATTR_NAME_END.test(source[j])) j += 1;
|
|
3245
3986
|
const attr = source.slice(attrStart, j);
|
|
3246
|
-
if (attr === "") throw new XmlParseError(file, startLine, `<${name}> has an attribute with no name`);
|
|
3987
|
+
if (attr === "") throw new XmlParseError(file, startLine, `<${printable(name)}> has an attribute with no name`);
|
|
3247
3988
|
while (j < source.length && SPACE.test(source[j])) j += 1;
|
|
3248
3989
|
if (source[j] !== "=") {
|
|
3249
|
-
throw new XmlParseError(file, startLine, `<${name}> attribute "${attr}" has no value`);
|
|
3990
|
+
throw new XmlParseError(file, startLine, `<${printable(name)}> attribute "${printable(attr)}" has no value`);
|
|
3250
3991
|
}
|
|
3251
3992
|
j += 1;
|
|
3252
3993
|
while (j < source.length && SPACE.test(source[j])) j += 1;
|
|
3253
3994
|
const quote = source[j];
|
|
3254
3995
|
if (quote !== '"' && quote !== "'") {
|
|
3255
|
-
throw new XmlParseError(file, startLine, `<${name}> attribute "${attr}" is not quoted`);
|
|
3996
|
+
throw new XmlParseError(file, startLine, `<${printable(name)}> attribute "${printable(attr)}" is not quoted`);
|
|
3256
3997
|
}
|
|
3257
3998
|
const close = source.indexOf(quote, j + 1);
|
|
3258
|
-
if (close === -1) throw new XmlParseError(file, startLine, `<${name}> attribute "${attr}" is never closed`);
|
|
3999
|
+
if (close === -1) throw new XmlParseError(file, startLine, `<${printable(name)}> attribute "${printable(attr)}" is never closed`);
|
|
3259
4000
|
el.attrs[attr] = decode(source.slice(j + 1, close));
|
|
3260
4001
|
j = close + 1;
|
|
3261
4002
|
}
|
|
@@ -3265,7 +4006,7 @@ function parseXml(source, file) {
|
|
|
3265
4006
|
}
|
|
3266
4007
|
if (stack.length > 1) {
|
|
3267
4008
|
const open = top();
|
|
3268
|
-
throw new XmlParseError(file, open.line, `<${open.name}> is never closed`);
|
|
4009
|
+
throw new XmlParseError(file, open.line, `<${printable(open.name)}> is never closed`);
|
|
3269
4010
|
}
|
|
3270
4011
|
if (root.children.length === 0) throw new XmlParseError(file, 1, "no elements \u2014 is this an XML report?");
|
|
3271
4012
|
return root;
|
|
@@ -3275,6 +4016,7 @@ var init_xml = __esm({
|
|
|
3275
4016
|
"src/importers/xml.ts"() {
|
|
3276
4017
|
"use strict";
|
|
3277
4018
|
init_cjs_shims();
|
|
4019
|
+
init_failure_detail();
|
|
3278
4020
|
XmlParseError = class extends Error {
|
|
3279
4021
|
constructor(file, line, detail) {
|
|
3280
4022
|
super(`${file}:${line} \u2014 ${detail}`);
|
|
@@ -3321,7 +4063,7 @@ function playwrightOf(classname, name) {
|
|
|
3321
4063
|
if (!SPEC_FILE.test(classname)) return void 0;
|
|
3322
4064
|
return { file: classname.replaceAll("\\", "/"), titles: name.split(" \u203A ") };
|
|
3323
4065
|
}
|
|
3324
|
-
function
|
|
4066
|
+
function keyOf2(classname, name, playwright) {
|
|
3325
4067
|
if (playwright !== void 0) return { kind: "path-title", value: [playwright.file, ...playwright.titles].join("#") };
|
|
3326
4068
|
return { kind: "path-title", value: classname === "" ? name : `${classname}#${name}` };
|
|
3327
4069
|
}
|
|
@@ -3354,8 +4096,9 @@ function executionOf(test, suite) {
|
|
|
3354
4096
|
retry: 0
|
|
3355
4097
|
};
|
|
3356
4098
|
}
|
|
3357
|
-
function readJUnit(source, file) {
|
|
4099
|
+
function readJUnit(source, file, missing = [], notes = []) {
|
|
3358
4100
|
const doc = parseXml(source, file);
|
|
4101
|
+
const { lexical, real: folder } = reportFolders(file);
|
|
3359
4102
|
const found = [];
|
|
3360
4103
|
locate(doc, void 0, found);
|
|
3361
4104
|
return found.map(({ test, suite }) => {
|
|
@@ -3363,180 +4106,50 @@ function readJUnit(source, file) {
|
|
|
3363
4106
|
const classname = test.attrs["classname"] ?? test.attrs["class"] ?? "";
|
|
3364
4107
|
const { status, detail } = outcomeOf(test);
|
|
3365
4108
|
const playwright = playwrightOf(classname, name);
|
|
3366
|
-
const key =
|
|
3367
|
-
const stated =
|
|
4109
|
+
const key = keyOf2(classname, name, playwright);
|
|
4110
|
+
const stated = TOKEN2.exec(playwright?.titles.at(-1) ?? name)?.[1];
|
|
3368
4111
|
const specRef = specRefOf(test, suite, classname);
|
|
3369
4112
|
const errorContext = errorTextOf(detail);
|
|
3370
4113
|
const execution = executionOf(test, suite);
|
|
4114
|
+
const title = titleOf(classname, name);
|
|
4115
|
+
const rkey = resultKey(key.value, 0);
|
|
4116
|
+
const files = filesOfTest(test, folder, title, missing, { lexical, resultKey: rkey, notes });
|
|
3371
4117
|
return {
|
|
3372
|
-
resultKey:
|
|
4118
|
+
resultKey: rkey,
|
|
3373
4119
|
...stated !== void 0 ? { testCaseId: stated } : {},
|
|
3374
4120
|
keys: [key],
|
|
3375
|
-
source:
|
|
4121
|
+
source: SOURCE2,
|
|
3376
4122
|
rawStatus: status,
|
|
3377
|
-
title
|
|
4123
|
+
title,
|
|
3378
4124
|
...specRef !== void 0 ? { specRef } : {},
|
|
3379
4125
|
...execution !== void 0 ? { execution } : {},
|
|
3380
|
-
...errorContext !== "" ? { errorContext } : {}
|
|
4126
|
+
...errorContext !== "" ? { errorContext } : {},
|
|
4127
|
+
...files.length > 0 ? { files } : {}
|
|
3381
4128
|
};
|
|
3382
4129
|
});
|
|
3383
4130
|
}
|
|
3384
4131
|
function looksLikeJUnit(source) {
|
|
3385
4132
|
return /<\s*testsuites?\b/.test(source.slice(0, 4096));
|
|
3386
4133
|
}
|
|
3387
|
-
var
|
|
4134
|
+
var SOURCE2, TOKEN2, SPEC_FILE;
|
|
3388
4135
|
var init_junit = __esm({
|
|
3389
4136
|
"src/importers/junit.ts"() {
|
|
3390
4137
|
"use strict";
|
|
3391
4138
|
init_cjs_shims();
|
|
3392
4139
|
init_result_key();
|
|
4140
|
+
init_attachments();
|
|
3393
4141
|
init_xml();
|
|
3394
|
-
|
|
3395
|
-
|
|
4142
|
+
SOURCE2 = "junit";
|
|
4143
|
+
TOKEN2 = /@P([A-Za-z0-9_-]+)/;
|
|
3396
4144
|
SPEC_FILE = /\.(?:spec|test)\.[cm]?[jt]sx?$/;
|
|
3397
4145
|
}
|
|
3398
4146
|
});
|
|
3399
4147
|
|
|
3400
|
-
// src/reporter-core/failure-detail.ts
|
|
3401
|
-
function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3402
|
-
const error = chosen(attempt);
|
|
3403
|
-
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
3404
|
-
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
3405
|
-
const steps = chainOf(attempt.steps);
|
|
3406
|
-
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
3407
|
-
const artifacts = attempt.attachments.filter((a) => a.path !== void 0 && !a.name.startsWith("_") && a.contentType !== METADATA_TYPE).map(({ name, contentType }) => ({ name: fileNameOf(name), ...contentType ? { contentType } : {} })).filter((a) => a.name !== "");
|
|
3408
|
-
const ciUrl = webLink(attempt.buildHref);
|
|
3409
|
-
const failure = {
|
|
3410
|
-
...headline !== void 0 ? { headline } : {},
|
|
3411
|
-
...steps.length > 0 ? { steps } : {},
|
|
3412
|
-
...location !== void 0 ? { location } : {},
|
|
3413
|
-
...artifacts.length > 0 ? { artifacts } : {},
|
|
3414
|
-
...ciUrl !== void 0 ? { ciUrl } : {}
|
|
3415
|
-
};
|
|
3416
|
-
return Object.keys(failure).length === 0 ? void 0 : failure;
|
|
3417
|
-
}
|
|
3418
|
-
function repoRootOf(dir, stopAt) {
|
|
3419
|
-
for (let here = (0, import_node_path3.resolve)(dir); ; here = (0, import_node_path3.dirname)(here)) {
|
|
3420
|
-
if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(here, ".git"))) return here;
|
|
3421
|
-
if (here === stopAt || (0, import_node_path3.dirname)(here) === here) return void 0;
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3425
|
-
const text2 = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
3426
|
-
return cut(machineless(text2, attempt.repoRoot, home));
|
|
3427
|
-
}
|
|
3428
|
-
function filesOf(attempt) {
|
|
3429
|
-
return attempt.attachments.filter((a) => a.path !== void 0);
|
|
3430
|
-
}
|
|
3431
|
-
function machineless(raw, repoRoot, home) {
|
|
3432
|
-
let text2 = (0, import_node_util.stripVTControlCharacters)(raw).replaceAll("\x1B", "");
|
|
3433
|
-
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
3434
|
-
if (root !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
3435
|
-
const own = pathIn(home);
|
|
3436
|
-
if (own !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
3437
|
-
if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
3438
|
-
return text2;
|
|
3439
|
-
}
|
|
3440
|
-
function pathIn(path15) {
|
|
3441
|
-
const parts = path15.split(/[\\/]+/);
|
|
3442
|
-
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
3443
|
-
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
3444
|
-
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
3445
|
-
const escaped = /^\/(?!\/)/.test(path15) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
3446
|
-
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
3447
|
-
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
3448
|
-
}
|
|
3449
|
-
function cut(text2) {
|
|
3450
|
-
if (jsonBytes(text2) <= TEXT_LIMIT) return text2;
|
|
3451
|
-
const lines = text2.split("\n");
|
|
3452
|
-
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
3453
|
-
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
3454
|
-
let used = 0;
|
|
3455
|
-
let head = 0;
|
|
3456
|
-
let tail = lines.length;
|
|
3457
|
-
while (head < tail && used + size[head] <= budget / 2) used += size[head++];
|
|
3458
|
-
while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
|
|
3459
|
-
return [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n");
|
|
3460
|
-
}
|
|
3461
|
-
function chosen({ status, errors }) {
|
|
3462
|
-
if (status === "timedOut") {
|
|
3463
|
-
const timeout = errors.findIndex((e) => firstLine(e.text)?.startsWith("Test timeout of ") === true);
|
|
3464
|
-
if (timeout !== -1 && timeout + 1 < errors.length) return errors[timeout + 1];
|
|
3465
|
-
}
|
|
3466
|
-
return errors[0];
|
|
3467
|
-
}
|
|
3468
|
-
function chainOf(steps) {
|
|
3469
|
-
const chain = [];
|
|
3470
|
-
for (let failed = steps.find((s) => s.failed); failed !== void 0; failed = failed.steps.find((s) => s.failed)) {
|
|
3471
|
-
chain.push(failed.title);
|
|
3472
|
-
}
|
|
3473
|
-
return chain;
|
|
3474
|
-
}
|
|
3475
|
-
function placeOf(error, testFile) {
|
|
3476
|
-
const own = normal(testFile);
|
|
3477
|
-
let first;
|
|
3478
|
-
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
3479
|
-
if (line.length > FRAME_MAX) continue;
|
|
3480
|
-
const frame = FRAME.exec(line);
|
|
3481
|
-
if (frame === null) continue;
|
|
3482
|
-
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
3483
|
-
if (normal(place.file) === own) return place;
|
|
3484
|
-
if (first === void 0 && !normal(place.file).includes("/node_modules/")) first = place;
|
|
3485
|
-
}
|
|
3486
|
-
return error.location ?? first;
|
|
3487
|
-
}
|
|
3488
|
-
function fileOf(raw) {
|
|
3489
|
-
if (!raw.startsWith("file://")) return raw;
|
|
3490
|
-
let path15 = raw.slice("file://".length);
|
|
3491
|
-
try {
|
|
3492
|
-
path15 = decodeURIComponent(path15);
|
|
3493
|
-
} catch {
|
|
3494
|
-
}
|
|
3495
|
-
return /^\/[A-Za-z]:\//.test(path15) ? path15.slice(1) : path15;
|
|
3496
|
-
}
|
|
3497
|
-
function fromRoot(place, root) {
|
|
3498
|
-
if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
|
|
3499
|
-
const base = normal(root).replace(/\/+$/, "");
|
|
3500
|
-
const file = normal(place.file);
|
|
3501
|
-
if (!file.startsWith(`${base}/`)) return void 0;
|
|
3502
|
-
const relative2 = file.slice(base.length + 1);
|
|
3503
|
-
if (!REPO_RELATIVE.test(relative2)) return void 0;
|
|
3504
|
-
const column = place.column !== void 0 && Number.isInteger(place.column) && place.column >= 1 ? place.column : void 0;
|
|
3505
|
-
return column === void 0 ? { file: relative2, line: place.line } : { file: relative2, line: place.line, column };
|
|
3506
|
-
}
|
|
3507
|
-
function webLink(href) {
|
|
3508
|
-
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
3509
|
-
}
|
|
3510
|
-
var import_node_fs3, import_node_os2, import_node_path3, import_node_util, PATH_START, TEXT_LIMIT, HEADLINE_LIMIT, jsonBytes, ANY_HOME, fileNameOf, omitted, METADATA_TYPE, REPO_RELATIVE, FRAME, FRAME_MAX, firstLine, normal;
|
|
3511
|
-
var init_failure_detail = __esm({
|
|
3512
|
-
"src/reporter-core/failure-detail.ts"() {
|
|
3513
|
-
"use strict";
|
|
3514
|
-
init_cjs_shims();
|
|
3515
|
-
import_node_fs3 = require("fs");
|
|
3516
|
-
import_node_os2 = require("os");
|
|
3517
|
-
import_node_path3 = require("path");
|
|
3518
|
-
import_node_util = require("util");
|
|
3519
|
-
PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
3520
|
-
TEXT_LIMIT = 524288;
|
|
3521
|
-
HEADLINE_LIMIT = 8 * 1024;
|
|
3522
|
-
jsonBytes = (text2) => Buffer.byteLength(JSON.stringify(text2)) - 2;
|
|
3523
|
-
ANY_HOME = /(?<=^|[\s'"`(=,[])(?:(?:file|webpack|webpack-internal):(?:\\*\/){2,3})?(?:\\*\/home\\*\/|\\*\/Users\\*\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
3524
|
-
fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
3525
|
-
omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
3526
|
-
METADATA_TYPE = "application/plune.metadata+json";
|
|
3527
|
-
REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
3528
|
-
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
3529
|
-
FRAME_MAX = 8192;
|
|
3530
|
-
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
3531
|
-
normal = (path15) => import_node_path3.posix.normalize(path15.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
3532
|
-
}
|
|
3533
|
-
});
|
|
3534
|
-
|
|
3535
4148
|
// src/importers/playwright-json.ts
|
|
3536
4149
|
function statedIdOf(test, title) {
|
|
3537
4150
|
const annotation = test.annotations?.find((a) => a.type === "PluneId")?.description;
|
|
3538
4151
|
if (annotation !== void 0 && annotation !== "") return annotation;
|
|
3539
|
-
return
|
|
4152
|
+
return TOKEN3.exec(title)?.[1];
|
|
3540
4153
|
}
|
|
3541
4154
|
function attemptOf(result, file, ctx) {
|
|
3542
4155
|
const steps = (list2) => (list2 ?? []).map((s) => ({ title: s.title ?? "", failed: s.error !== void 0, steps: steps(s.steps) }));
|
|
@@ -3547,10 +4160,13 @@ function attemptOf(result, file, ctx) {
|
|
|
3547
4160
|
...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
|
|
3548
4161
|
})),
|
|
3549
4162
|
steps: steps(result.steps),
|
|
3550
|
-
attachments: (result.attachments ?? []).map(({ name, contentType, path:
|
|
4163
|
+
attachments: (result.attachments ?? []).map(({ name, contentType, path: path17, body }) => ({
|
|
3551
4164
|
name: name ?? "",
|
|
3552
4165
|
...contentType !== void 0 ? { contentType } : {},
|
|
3553
|
-
...
|
|
4166
|
+
...path17 !== void 0 ? { path: path17 } : {},
|
|
4167
|
+
// Decoded only where the platform would take it (plune-ai/plune#928): a report of screenshots kept as
|
|
4168
|
+
// bodies is megabytes of base64 that nothing here will read.
|
|
4169
|
+
...typeof body === "string" && isTextType(contentType) ? { body: Buffer.from(body, "base64") } : {}
|
|
3554
4170
|
})),
|
|
3555
4171
|
...ctx.buildHref !== void 0 ? { buildHref: ctx.buildHref } : {},
|
|
3556
4172
|
// Spec files are posix and relative to the root dir; the core reads either slash.
|
|
@@ -3580,7 +4196,7 @@ function pendingFrom(spec, test, result, file, titles, ctx) {
|
|
|
3580
4196
|
keys,
|
|
3581
4197
|
title: titles.join(" \u203A "),
|
|
3582
4198
|
specRef: spec.line !== void 0 ? `${file}:${spec.line}` : file,
|
|
3583
|
-
source:
|
|
4199
|
+
source: SOURCE3,
|
|
3584
4200
|
// Untouched, as everywhere: `timedOut` means what the project's map says it means, not what
|
|
3585
4201
|
// this file decided.
|
|
3586
4202
|
rawStatus: result.status ?? "unknown",
|
|
@@ -3615,7 +4231,7 @@ function readPlaywrightJson(source, file) {
|
|
|
3615
4231
|
try {
|
|
3616
4232
|
parsed = JSON.parse(source);
|
|
3617
4233
|
} catch (err) {
|
|
3618
|
-
throw new JsonReportError(file, `not JSON \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
4234
|
+
throw new JsonReportError(file, `not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
|
|
3619
4235
|
}
|
|
3620
4236
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.suites)) {
|
|
3621
4237
|
throw new JsonReportError(file, 'no "suites" array \u2014 is this a Playwright JSON report?');
|
|
@@ -3623,7 +4239,7 @@ function readPlaywrightJson(source, file) {
|
|
|
3623
4239
|
const { config, suites } = parsed;
|
|
3624
4240
|
const rootDir = typeof config?.rootDir === "string" ? config.rootDir : void 0;
|
|
3625
4241
|
const buildHref = config?.metadata?.ci?.buildHref;
|
|
3626
|
-
const repoRoot = rootDir !== void 0 && (0,
|
|
4242
|
+
const repoRoot = rootDir !== void 0 && (0, import_node_fs5.existsSync)(rootDir) ? repoRootOf(rootDir) : void 0;
|
|
3627
4243
|
const ctx = {
|
|
3628
4244
|
...rootDir !== void 0 ? { rootDir } : {},
|
|
3629
4245
|
...repoRoot !== void 0 ? { repoRoot } : {},
|
|
@@ -3642,20 +4258,20 @@ function readPlaywrightJson(source, file) {
|
|
|
3642
4258
|
function looksLikePlaywrightJson(source) {
|
|
3643
4259
|
return /^\s*\{/.test(source) && source.includes('"suites"');
|
|
3644
4260
|
}
|
|
3645
|
-
var
|
|
4261
|
+
var import_node_fs5, SOURCE3, TOKEN3, NOT_FAILED, JsonReportError;
|
|
3646
4262
|
var init_playwright_json = __esm({
|
|
3647
4263
|
"src/importers/playwright-json.ts"() {
|
|
3648
4264
|
"use strict";
|
|
3649
4265
|
init_cjs_shims();
|
|
3650
|
-
|
|
4266
|
+
import_node_fs5 = require("fs");
|
|
3651
4267
|
init_failure_detail();
|
|
3652
4268
|
init_result_key();
|
|
3653
|
-
|
|
3654
|
-
|
|
4269
|
+
SOURCE3 = "playwright";
|
|
4270
|
+
TOKEN3 = /@P([A-Za-z0-9_-]+)/;
|
|
3655
4271
|
NOT_FAILED = /* @__PURE__ */ new Set(["passed", "skipped"]);
|
|
3656
4272
|
JsonReportError = class extends Error {
|
|
3657
4273
|
constructor(file, detail) {
|
|
3658
|
-
super(`${file} \u2014 ${detail}`);
|
|
4274
|
+
super(`${printable(file)} \u2014 ${detail}`);
|
|
3659
4275
|
this.file = file;
|
|
3660
4276
|
this.name = "JsonReportError";
|
|
3661
4277
|
}
|
|
@@ -3667,11 +4283,14 @@ var init_playwright_json = __esm({
|
|
|
3667
4283
|
// src/importers/index.ts
|
|
3668
4284
|
var importers_exports = {};
|
|
3669
4285
|
__export(importers_exports, {
|
|
4286
|
+
AllureReportError: () => AllureReportError,
|
|
3670
4287
|
IMPORT_FORMATS: () => IMPORT_FORMATS,
|
|
3671
4288
|
JsonReportError: () => JsonReportError,
|
|
3672
4289
|
UnknownFormatError: () => UnknownFormatError,
|
|
3673
4290
|
XmlParseError: () => XmlParseError,
|
|
3674
4291
|
detectFormat: () => detectFormat,
|
|
4292
|
+
isFolder: () => isFolder,
|
|
4293
|
+
readFolder: () => readFolder,
|
|
3675
4294
|
readReport: () => readReport
|
|
3676
4295
|
});
|
|
3677
4296
|
function detectFormat(source, file) {
|
|
@@ -3682,21 +4301,34 @@ function detectFormat(source, file) {
|
|
|
3682
4301
|
'no <testsuite> and no Playwright "suites" \u2014 this is not a report we can read.'
|
|
3683
4302
|
);
|
|
3684
4303
|
}
|
|
4304
|
+
function readFolder(dir, format) {
|
|
4305
|
+
if (format !== void 0 && format !== "allure") {
|
|
4306
|
+
throw new UnknownFormatError(dir, `this is a folder, and only allure-results is read from one, not ${format}.`);
|
|
4307
|
+
}
|
|
4308
|
+
const missing = [];
|
|
4309
|
+
const notes = [];
|
|
4310
|
+
return { format: "allure", results: readAllure(dir, missing, notes), missing, notes };
|
|
4311
|
+
}
|
|
3685
4312
|
function readReport(source, file, format) {
|
|
3686
4313
|
const chosen2 = format ?? detectFormat(source, file);
|
|
3687
|
-
|
|
3688
|
-
|
|
4314
|
+
const missing = [];
|
|
4315
|
+
if (chosen2 === "allure") throw new UnknownFormatError(file, "allure-results is a folder, and this is a file.");
|
|
4316
|
+
const notes = [];
|
|
4317
|
+
if (chosen2 === "junit") return { format: chosen2, results: readJUnit(source, file, missing, notes), missing, notes };
|
|
4318
|
+
return { format: chosen2, ...readPlaywrightJson(source, file), missing, notes };
|
|
3689
4319
|
}
|
|
3690
4320
|
var IMPORT_FORMATS, UnknownFormatError;
|
|
3691
4321
|
var init_importers = __esm({
|
|
3692
4322
|
"src/importers/index.ts"() {
|
|
3693
4323
|
"use strict";
|
|
3694
4324
|
init_cjs_shims();
|
|
4325
|
+
init_allure();
|
|
3695
4326
|
init_junit();
|
|
3696
4327
|
init_playwright_json();
|
|
3697
4328
|
init_xml();
|
|
3698
4329
|
init_playwright_json();
|
|
3699
|
-
|
|
4330
|
+
init_allure();
|
|
4331
|
+
IMPORT_FORMATS = ["junit", "playwright-json", "allure"];
|
|
3700
4332
|
UnknownFormatError = class extends Error {
|
|
3701
4333
|
constructor(file, detail) {
|
|
3702
4334
|
super(`${file} \u2014 ${detail} Say which with --format ${IMPORT_FORMATS.join(" | ")}.`);
|
|
@@ -3714,16 +4346,25 @@ __export(run_import_exports, {
|
|
|
3714
4346
|
async function handleRunImport(options) {
|
|
3715
4347
|
const write = options.write ?? ((line) => void process.stdout.write(`${line}
|
|
3716
4348
|
`));
|
|
3717
|
-
let
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
}
|
|
3721
|
-
|
|
4349
|
+
let read;
|
|
4350
|
+
if (isFolder(options.file)) {
|
|
4351
|
+
read = readFolder(options.file, options.format);
|
|
4352
|
+
} else {
|
|
4353
|
+
let source;
|
|
4354
|
+
try {
|
|
4355
|
+
source = fs10.readFileSync(options.file, "utf8");
|
|
4356
|
+
} catch {
|
|
4357
|
+
throw new RunCommandError(`Cannot read ${options.file}.`);
|
|
4358
|
+
}
|
|
4359
|
+
read = readReport(source, options.file, options.format);
|
|
3722
4360
|
}
|
|
3723
|
-
const { format, results, ciUrl, ci } =
|
|
4361
|
+
const { format, results, missing, notes, ciUrl, ci } = read;
|
|
3724
4362
|
if (results.length === 0) {
|
|
3725
4363
|
throw new RunCommandError(`No test cases in ${options.file} \u2014 it parsed, but there is nothing in it.`);
|
|
3726
4364
|
}
|
|
4365
|
+
for (const m of missing) write(`plune: attachment not found \u2014 ${JSON.stringify(printable(m.title))}: ${printable(m.path)}`);
|
|
4366
|
+
for (const note of notes) write(note);
|
|
4367
|
+
const announced = (format === "junit" || format === "allure") && (missing.length > 0 || results.some((r) => (r.files?.length ?? 0) > 0));
|
|
3727
4368
|
const env = readEnv();
|
|
3728
4369
|
const apiUrl = resolveApiUrl(options.apiUrl ?? env.config.apiUrl);
|
|
3729
4370
|
const token = options.token ?? env.config.token ?? loadToken() ?? "";
|
|
@@ -3767,6 +4408,14 @@ async function handleRunImport(options) {
|
|
|
3767
4408
|
...stats.unofferedWhy !== void 0 ? { unofferedWhy: stats.unofferedWhy } : {},
|
|
3768
4409
|
deferred: stats.deferred,
|
|
3769
4410
|
screenshots: { ...stats.screenshots },
|
|
4411
|
+
textFiles: { ...stats.textFiles },
|
|
4412
|
+
missingAttachments: missing,
|
|
4413
|
+
filesUploaded: stats.screenshots.uploaded + stats.textFiles.uploaded + stats.otherFiles.uploaded,
|
|
4414
|
+
announced,
|
|
4415
|
+
filesNotSent: Math.max(
|
|
4416
|
+
0,
|
|
4417
|
+
results.reduce((n, r) => n + (r.files?.length ?? 0), 0) - [stats.screenshots, stats.textFiles, stats.otherFiles].reduce((n, c) => n + c.uploaded + c.skipped + c.failed, 0)
|
|
4418
|
+
),
|
|
3770
4419
|
unlocatable: results.filter((r) => r.specRef === void 0).length,
|
|
3771
4420
|
offering: options.create === true
|
|
3772
4421
|
};
|
|
@@ -3785,17 +4434,11 @@ function describe(result, apiUrl) {
|
|
|
3785
4434
|
// worked (#622).
|
|
3786
4435
|
// `not delivered` always, zero included (#790): a number that only appears when it is bad is one
|
|
3787
4436
|
// nobody learns to look for. The `[0-9]+ unmatched` the workflows read stays whole.
|
|
3788
|
-
`Read ${result.parsed} result(s) from a ${result.format} report: ${result.accepted} accepted, ${result.duplicate} already there, ${result.unresolved} unmatched, ${result.deferred} not delivered.`
|
|
4437
|
+
`Read ${result.parsed} result(s) from ${result.format === "allure" ? "an" : "a"} ${result.format} report: ${result.accepted} accepted, ${result.duplicate} already there, ${result.unresolved} unmatched, ${result.deferred} not delivered.`
|
|
3789
4438
|
];
|
|
3790
4439
|
if (result.conflict > 0 || result.rejected > 0) {
|
|
3791
4440
|
lines.push(`${result.conflict} conflicted and ${result.rejected} were refused.`);
|
|
3792
4441
|
}
|
|
3793
|
-
const shots = result.screenshots;
|
|
3794
|
-
if (shots.uploaded + shots.skipped + shots.failed > 0) {
|
|
3795
|
-
lines.push(
|
|
3796
|
-
`${shots.uploaded} screenshot(s) uploaded to their results` + (shots.skipped > 0 ? `, ${shots.skipped} skipped` : "") + (shots.failed > 0 ? `, ${shots.failed} could not be uploaded` : "") + "."
|
|
3797
|
-
);
|
|
3798
|
-
}
|
|
3799
4442
|
if (result.deferred > 0) {
|
|
3800
4443
|
lines.push(
|
|
3801
4444
|
`${result.deferred} could not be sent and are in the fallback file \u2014 "plune run report" retries them.`
|
|
@@ -3823,16 +4466,22 @@ function describe(result, apiUrl) {
|
|
|
3823
4466
|
`${result.unlocatable} test(s) carry no file or class in the report, so they cannot be offered for review \u2014 a reviewer would have nowhere to look.`
|
|
3824
4467
|
);
|
|
3825
4468
|
}
|
|
4469
|
+
if (result.announced) {
|
|
4470
|
+
lines.push(
|
|
4471
|
+
`Imported ${result.accepted} results \xB7 ${result.filesUploaded} files uploaded \xB7 ${result.missingAttachments.length} files not found` + (result.filesNotSent > 0 ? ` \xB7 ${result.filesNotSent} files not sent (their result was not stored)` : "")
|
|
4472
|
+
);
|
|
4473
|
+
}
|
|
3826
4474
|
if (result.runId !== null) lines.push(runUrl2(apiUrl, result.runId));
|
|
3827
4475
|
return lines;
|
|
3828
4476
|
}
|
|
3829
|
-
var
|
|
4477
|
+
var fs10, NEXT;
|
|
3830
4478
|
var init_run_import = __esm({
|
|
3831
4479
|
"src/cli/commands/run-import.ts"() {
|
|
3832
4480
|
"use strict";
|
|
3833
4481
|
init_cjs_shims();
|
|
3834
|
-
|
|
4482
|
+
fs10 = __toESM(require("fs"), 1);
|
|
3835
4483
|
init_session();
|
|
4484
|
+
init_failure_detail();
|
|
3836
4485
|
init_importers();
|
|
3837
4486
|
init_run_lifecycle();
|
|
3838
4487
|
init_api_url();
|
|
@@ -3859,10 +4508,10 @@ function isRunResult(v) {
|
|
|
3859
4508
|
}
|
|
3860
4509
|
function handleReport(opts = {}) {
|
|
3861
4510
|
const cwd = opts.cwd ?? process.cwd();
|
|
3862
|
-
const file =
|
|
4511
|
+
const file = path11.join(cwd, ".plune", "last-run.json");
|
|
3863
4512
|
let raw;
|
|
3864
4513
|
try {
|
|
3865
|
-
raw =
|
|
4514
|
+
raw = fs11.readFileSync(file, "utf8");
|
|
3866
4515
|
} catch {
|
|
3867
4516
|
throw new ReportNotFoundError('No saved run found. Run "plune run" first.');
|
|
3868
4517
|
}
|
|
@@ -3877,13 +4526,13 @@ function handleReport(opts = {}) {
|
|
|
3877
4526
|
}
|
|
3878
4527
|
return parsed;
|
|
3879
4528
|
}
|
|
3880
|
-
var
|
|
4529
|
+
var fs11, path11, ReportNotFoundError;
|
|
3881
4530
|
var init_report = __esm({
|
|
3882
4531
|
"src/cli/commands/report.ts"() {
|
|
3883
4532
|
"use strict";
|
|
3884
4533
|
init_cjs_shims();
|
|
3885
|
-
|
|
3886
|
-
|
|
4534
|
+
fs11 = __toESM(require("fs"), 1);
|
|
4535
|
+
path11 = __toESM(require("path"), 1);
|
|
3887
4536
|
ReportNotFoundError = class extends Error {
|
|
3888
4537
|
code = "NO_SAVED_RUN";
|
|
3889
4538
|
constructor(message) {
|
|
@@ -3957,7 +4606,7 @@ function isRunResult2(v) {
|
|
|
3957
4606
|
function loadRunResult(file, label) {
|
|
3958
4607
|
let raw;
|
|
3959
4608
|
try {
|
|
3960
|
-
raw =
|
|
4609
|
+
raw = fs12.readFileSync(file, "utf8");
|
|
3961
4610
|
} catch {
|
|
3962
4611
|
throw new DiffInputError(`Cannot read ${label} run file: ${file}`);
|
|
3963
4612
|
}
|
|
@@ -3977,12 +4626,12 @@ function handleDiff(opts) {
|
|
|
3977
4626
|
const current = loadRunResult(opts.currentPath, "current");
|
|
3978
4627
|
return diffRuns(baseline, current);
|
|
3979
4628
|
}
|
|
3980
|
-
var
|
|
4629
|
+
var fs12, DiffInputError;
|
|
3981
4630
|
var init_diff2 = __esm({
|
|
3982
4631
|
"src/cli/commands/diff.ts"() {
|
|
3983
4632
|
"use strict";
|
|
3984
4633
|
init_cjs_shims();
|
|
3985
|
-
|
|
4634
|
+
fs12 = __toESM(require("fs"), 1);
|
|
3986
4635
|
init_diff();
|
|
3987
4636
|
DiffInputError = class extends Error {
|
|
3988
4637
|
code = "DIFF_INPUT";
|
|
@@ -4107,8 +4756,8 @@ async function runInitWizard(cwd) {
|
|
|
4107
4756
|
process.exit(1);
|
|
4108
4757
|
return;
|
|
4109
4758
|
}
|
|
4110
|
-
const outputPath =
|
|
4111
|
-
if (
|
|
4759
|
+
const outputPath = path12.join(cwd, "plune.yaml");
|
|
4760
|
+
if (fs13.existsSync(outputPath)) {
|
|
4112
4761
|
const overwrite = await clack.confirm({
|
|
4113
4762
|
message: "plune.yaml already exists. Overwrite?"
|
|
4114
4763
|
});
|
|
@@ -4147,18 +4796,18 @@ async function runInitWizard(cwd) {
|
|
|
4147
4796
|
`;
|
|
4148
4797
|
const spin = clack.spinner();
|
|
4149
4798
|
spin.start("Writing plune.yaml\u2026");
|
|
4150
|
-
|
|
4799
|
+
fs13.writeFileSync(outputPath, header + "\n" + (0, import_yaml2.stringify)(config), "utf8");
|
|
4151
4800
|
spin.stop("plune.yaml created!");
|
|
4152
4801
|
clack.outro("plune.yaml is ready.");
|
|
4153
4802
|
}
|
|
4154
|
-
var clack,
|
|
4803
|
+
var clack, fs13, path12, import_yaml2;
|
|
4155
4804
|
var init_wizard = __esm({
|
|
4156
4805
|
"src/config/init/wizard.ts"() {
|
|
4157
4806
|
"use strict";
|
|
4158
4807
|
init_cjs_shims();
|
|
4159
4808
|
clack = __toESM(require("@clack/prompts"), 1);
|
|
4160
|
-
|
|
4161
|
-
|
|
4809
|
+
fs13 = __toESM(require("fs"), 1);
|
|
4810
|
+
path12 = __toESM(require("path"), 1);
|
|
4162
4811
|
import_yaml2 = require("yaml");
|
|
4163
4812
|
init_errors();
|
|
4164
4813
|
}
|
|
@@ -4166,21 +4815,21 @@ var init_wizard = __esm({
|
|
|
4166
4815
|
|
|
4167
4816
|
// src/cli/templates/index.ts
|
|
4168
4817
|
function writeTemplateFile(cwd, rel, content, force) {
|
|
4169
|
-
const target =
|
|
4170
|
-
if (!force &&
|
|
4818
|
+
const target = path13.join(cwd, rel);
|
|
4819
|
+
if (!force && fs14.existsSync(target)) {
|
|
4171
4820
|
return "skipped";
|
|
4172
4821
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4822
|
+
fs14.mkdirSync(path13.dirname(target), { recursive: true });
|
|
4823
|
+
fs14.writeFileSync(target, content, "utf8");
|
|
4175
4824
|
return "written";
|
|
4176
4825
|
}
|
|
4177
|
-
var
|
|
4826
|
+
var fs14, path13, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
|
|
4178
4827
|
var init_templates = __esm({
|
|
4179
4828
|
"src/cli/templates/index.ts"() {
|
|
4180
4829
|
"use strict";
|
|
4181
4830
|
init_cjs_shims();
|
|
4182
|
-
|
|
4183
|
-
|
|
4831
|
+
fs14 = __toESM(require("fs"), 1);
|
|
4832
|
+
path13 = __toESM(require("path"), 1);
|
|
4184
4833
|
ENV_EXAMPLE_TPL = `# Copy this file to .env and replace the placeholders with your real keys.
|
|
4185
4834
|
# .env is gitignored; .env.example is safe to commit.
|
|
4186
4835
|
# Plune reads whichever key matches your configured provider.
|
|
@@ -4358,68 +5007,136 @@ var init_logout = __esm({
|
|
|
4358
5007
|
}
|
|
4359
5008
|
});
|
|
4360
5009
|
|
|
5010
|
+
// src/types/test-case-schema.ts
|
|
5011
|
+
var import_zod2, TEST_CASE_SCHEMA_VERSION, externalKeySchema, manualStepSchema, assertionResultRecordSchema, manualCaseSchema, automatedCaseSchema, evalCaseSchema, testCaseBaseSchema, testCaseSchema;
|
|
5012
|
+
var init_test_case_schema = __esm({
|
|
5013
|
+
"src/types/test-case-schema.ts"() {
|
|
5014
|
+
"use strict";
|
|
5015
|
+
init_cjs_shims();
|
|
5016
|
+
import_zod2 = require("zod");
|
|
5017
|
+
TEST_CASE_SCHEMA_VERSION = 2;
|
|
5018
|
+
externalKeySchema = import_zod2.z.object({
|
|
5019
|
+
kind: import_zod2.z.enum(["playwright-id", "path-title", "allure-history", "cairn-stable", "eval-id", "qase", "testrail"]),
|
|
5020
|
+
value: import_zod2.z.string().min(1)
|
|
5021
|
+
});
|
|
5022
|
+
manualStepSchema = import_zod2.z.object({
|
|
5023
|
+
action: import_zod2.z.string().min(1),
|
|
5024
|
+
expected: import_zod2.z.string().min(1).optional()
|
|
5025
|
+
});
|
|
5026
|
+
assertionResultRecordSchema = import_zod2.z.object({
|
|
5027
|
+
type: import_zod2.z.string().min(1),
|
|
5028
|
+
passed: import_zod2.z.boolean(),
|
|
5029
|
+
score: import_zod2.z.number().optional(),
|
|
5030
|
+
reason: import_zod2.z.string().optional()
|
|
5031
|
+
});
|
|
5032
|
+
manualCaseSchema = import_zod2.z.object({
|
|
5033
|
+
type: import_zod2.z.literal("manual"),
|
|
5034
|
+
steps: import_zod2.z.array(manualStepSchema).min(1),
|
|
5035
|
+
expected: import_zod2.z.string().min(1).optional()
|
|
5036
|
+
});
|
|
5037
|
+
automatedCaseSchema = import_zod2.z.object({
|
|
5038
|
+
type: import_zod2.z.literal("automated"),
|
|
5039
|
+
specRef: import_zod2.z.string().min(1)
|
|
5040
|
+
});
|
|
5041
|
+
evalCaseSchema = import_zod2.z.object({
|
|
5042
|
+
type: import_zod2.z.literal("eval"),
|
|
5043
|
+
prompt: import_zod2.z.string().min(1),
|
|
5044
|
+
goldenSetId: import_zod2.z.string().min(1).optional(),
|
|
5045
|
+
assertions: import_zod2.z.array(assertionResultRecordSchema).min(1)
|
|
5046
|
+
});
|
|
5047
|
+
testCaseBaseSchema = import_zod2.z.object({
|
|
5048
|
+
schemaVersion: import_zod2.z.literal(TEST_CASE_SCHEMA_VERSION),
|
|
5049
|
+
id: import_zod2.z.string().min(1),
|
|
5050
|
+
externalKeys: import_zod2.z.array(externalKeySchema).optional(),
|
|
5051
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
5052
|
+
is_flaky: import_zod2.z.boolean().optional(),
|
|
5053
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
5054
|
+
is_muted: import_zod2.z.boolean().optional()
|
|
5055
|
+
});
|
|
5056
|
+
testCaseSchema = import_zod2.z.discriminatedUnion("type", [
|
|
5057
|
+
testCaseBaseSchema.merge(manualCaseSchema),
|
|
5058
|
+
testCaseBaseSchema.merge(automatedCaseSchema),
|
|
5059
|
+
testCaseBaseSchema.merge(evalCaseSchema)
|
|
5060
|
+
]);
|
|
5061
|
+
}
|
|
5062
|
+
});
|
|
5063
|
+
|
|
5064
|
+
// src/types/results.ts
|
|
5065
|
+
var ROW_INCONCLUSIVE;
|
|
5066
|
+
var init_results = __esm({
|
|
5067
|
+
"src/types/results.ts"() {
|
|
5068
|
+
"use strict";
|
|
5069
|
+
init_cjs_shims();
|
|
5070
|
+
init_test_case_schema();
|
|
5071
|
+
ROW_INCONCLUSIVE = ["blocked", "skipped"];
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
|
|
4361
5075
|
// src/types/run-result-schema.ts
|
|
4362
5076
|
function parseRunResult(value2) {
|
|
4363
5077
|
return runResultSchema.parse(value2);
|
|
4364
5078
|
}
|
|
4365
|
-
var
|
|
5079
|
+
var import_zod3, RUN_RESULT_SCHEMA_VERSION, binaryVerdictSchema, assertionResultRecordSchema2, usageSchema, runErrorSchema, rowResultSchema, summarySchema, evalResultSchema, runResultSchema;
|
|
4366
5080
|
var init_run_result_schema = __esm({
|
|
4367
5081
|
"src/types/run-result-schema.ts"() {
|
|
4368
5082
|
"use strict";
|
|
4369
5083
|
init_cjs_shims();
|
|
4370
|
-
|
|
5084
|
+
import_zod3 = require("zod");
|
|
5085
|
+
init_results();
|
|
4371
5086
|
RUN_RESULT_SCHEMA_VERSION = 1;
|
|
4372
|
-
binaryVerdictSchema =
|
|
4373
|
-
question:
|
|
4374
|
-
passed:
|
|
5087
|
+
binaryVerdictSchema = import_zod3.z.object({
|
|
5088
|
+
question: import_zod3.z.string().min(1),
|
|
5089
|
+
passed: import_zod3.z.boolean()
|
|
4375
5090
|
});
|
|
4376
|
-
|
|
4377
|
-
type:
|
|
4378
|
-
passed:
|
|
4379
|
-
score:
|
|
4380
|
-
reason:
|
|
4381
|
-
binaryVerdicts:
|
|
5091
|
+
assertionResultRecordSchema2 = import_zod3.z.object({
|
|
5092
|
+
type: import_zod3.z.string(),
|
|
5093
|
+
passed: import_zod3.z.boolean(),
|
|
5094
|
+
score: import_zod3.z.number().optional(),
|
|
5095
|
+
reason: import_zod3.z.string().optional(),
|
|
5096
|
+
binaryVerdicts: import_zod3.z.array(binaryVerdictSchema).optional()
|
|
4382
5097
|
});
|
|
4383
|
-
usageSchema =
|
|
4384
|
-
input_tokens:
|
|
4385
|
-
output_tokens:
|
|
4386
|
-
cost_usd:
|
|
5098
|
+
usageSchema = import_zod3.z.object({
|
|
5099
|
+
input_tokens: import_zod3.z.number(),
|
|
5100
|
+
output_tokens: import_zod3.z.number(),
|
|
5101
|
+
cost_usd: import_zod3.z.number()
|
|
4387
5102
|
});
|
|
4388
|
-
runErrorSchema =
|
|
4389
|
-
code:
|
|
4390
|
-
message:
|
|
5103
|
+
runErrorSchema = import_zod3.z.object({
|
|
5104
|
+
code: import_zod3.z.string(),
|
|
5105
|
+
message: import_zod3.z.string()
|
|
4391
5106
|
});
|
|
4392
|
-
rowResultSchema =
|
|
4393
|
-
vars:
|
|
4394
|
-
output:
|
|
4395
|
-
cached:
|
|
5107
|
+
rowResultSchema = import_zod3.z.object({
|
|
5108
|
+
vars: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()),
|
|
5109
|
+
output: import_zod3.z.string().nullable(),
|
|
5110
|
+
cached: import_zod3.z.boolean(),
|
|
4396
5111
|
usage: usageSchema.optional(),
|
|
4397
|
-
latency_ms:
|
|
5112
|
+
latency_ms: import_zod3.z.number().optional(),
|
|
4398
5113
|
error: runErrorSchema.optional(),
|
|
4399
|
-
assertions:
|
|
5114
|
+
assertions: import_zod3.z.array(assertionResultRecordSchema2),
|
|
5115
|
+
/** ADR 0045. Optional and additive — the same enum the platform validates on `POST /v1/runs`. */
|
|
5116
|
+
inconclusive: import_zod3.z.enum(ROW_INCONCLUSIVE).optional()
|
|
4400
5117
|
});
|
|
4401
|
-
summarySchema =
|
|
4402
|
-
total:
|
|
4403
|
-
passed:
|
|
4404
|
-
failed:
|
|
4405
|
-
errored:
|
|
4406
|
-
cost_usd:
|
|
4407
|
-
duration_ms:
|
|
5118
|
+
summarySchema = import_zod3.z.object({
|
|
5119
|
+
total: import_zod3.z.number(),
|
|
5120
|
+
passed: import_zod3.z.number(),
|
|
5121
|
+
failed: import_zod3.z.number(),
|
|
5122
|
+
errored: import_zod3.z.number(),
|
|
5123
|
+
cost_usd: import_zod3.z.number(),
|
|
5124
|
+
duration_ms: import_zod3.z.number()
|
|
4408
5125
|
});
|
|
4409
|
-
evalResultSchema =
|
|
4410
|
-
id:
|
|
4411
|
-
tags:
|
|
4412
|
-
rows:
|
|
4413
|
-
passed:
|
|
5126
|
+
evalResultSchema = import_zod3.z.object({
|
|
5127
|
+
id: import_zod3.z.string(),
|
|
5128
|
+
tags: import_zod3.z.array(import_zod3.z.string()),
|
|
5129
|
+
rows: import_zod3.z.array(rowResultSchema),
|
|
5130
|
+
passed: import_zod3.z.boolean()
|
|
4414
5131
|
});
|
|
4415
|
-
runResultSchema =
|
|
4416
|
-
schemaVersion:
|
|
4417
|
-
plune_version:
|
|
4418
|
-
started_at:
|
|
4419
|
-
finished_at:
|
|
4420
|
-
config_hash:
|
|
5132
|
+
runResultSchema = import_zod3.z.object({
|
|
5133
|
+
schemaVersion: import_zod3.z.literal(RUN_RESULT_SCHEMA_VERSION),
|
|
5134
|
+
plune_version: import_zod3.z.string(),
|
|
5135
|
+
started_at: import_zod3.z.string(),
|
|
5136
|
+
finished_at: import_zod3.z.string(),
|
|
5137
|
+
config_hash: import_zod3.z.string(),
|
|
4421
5138
|
summary: summarySchema,
|
|
4422
|
-
evals:
|
|
5139
|
+
evals: import_zod3.z.array(evalResultSchema)
|
|
4423
5140
|
});
|
|
4424
5141
|
}
|
|
4425
5142
|
});
|
|
@@ -4436,10 +5153,10 @@ __export(sync_exports, {
|
|
|
4436
5153
|
reportSyncFailure: () => reportSyncFailure
|
|
4437
5154
|
});
|
|
4438
5155
|
function readRun(cwd, file) {
|
|
4439
|
-
const target = file !== void 0 ?
|
|
5156
|
+
const target = file !== void 0 ? path14.resolve(cwd, file) : path14.join(cwd, ".plune", "last-run.json");
|
|
4440
5157
|
let raw;
|
|
4441
5158
|
try {
|
|
4442
|
-
raw =
|
|
5159
|
+
raw = fs15.readFileSync(target, "utf8");
|
|
4443
5160
|
} catch {
|
|
4444
5161
|
throw new SyncFileError(
|
|
4445
5162
|
file !== void 0 ? `Run file not found: ${target}` : 'No run to sync. Run "plune run" first (or pass --file <path>).'
|
|
@@ -4503,13 +5220,13 @@ function reportSyncFailure(err, write) {
|
|
|
4503
5220
|
}
|
|
4504
5221
|
return null;
|
|
4505
5222
|
}
|
|
4506
|
-
var
|
|
5223
|
+
var fs15, path14, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
|
|
4507
5224
|
var init_sync = __esm({
|
|
4508
5225
|
"src/cli/commands/sync.ts"() {
|
|
4509
5226
|
"use strict";
|
|
4510
5227
|
init_cjs_shims();
|
|
4511
|
-
|
|
4512
|
-
|
|
5228
|
+
fs15 = __toESM(require("fs"), 1);
|
|
5229
|
+
path14 = __toESM(require("path"), 1);
|
|
4513
5230
|
init_run_result_schema();
|
|
4514
5231
|
init_api_url();
|
|
4515
5232
|
init_credentials();
|
|
@@ -4554,14 +5271,14 @@ var init_sync = __esm({
|
|
|
4554
5271
|
|
|
4555
5272
|
// src/cli/commands/cairn-artifact.ts
|
|
4556
5273
|
async function findLatestCairnRun(runsDir = "runs") {
|
|
4557
|
-
const entries = await
|
|
5274
|
+
const entries = await fs16.readdir(runsDir, { withFileTypes: true }).catch(() => null);
|
|
4558
5275
|
if (entries === null) throw new NoCairnRunsError(runsDir);
|
|
4559
5276
|
let newest;
|
|
4560
5277
|
for (const entry of entries) {
|
|
4561
5278
|
if (!entry.isDirectory()) continue;
|
|
4562
|
-
const dir =
|
|
5279
|
+
const dir = path15.join(runsDir, entry.name);
|
|
4563
5280
|
try {
|
|
4564
|
-
const report = await
|
|
5281
|
+
const report = await fs16.lstat(path15.join(dir, "report.json"));
|
|
4565
5282
|
if (!report.isFile()) continue;
|
|
4566
5283
|
if (newest === void 0 || report.mtimeMs > newest.at) {
|
|
4567
5284
|
newest = { dir, at: report.mtimeMs };
|
|
@@ -4574,13 +5291,13 @@ async function findLatestCairnRun(runsDir = "runs") {
|
|
|
4574
5291
|
}
|
|
4575
5292
|
async function readJson(file) {
|
|
4576
5293
|
try {
|
|
4577
|
-
return JSON.parse(await
|
|
5294
|
+
return JSON.parse(await fs16.readFile(file, "utf8"));
|
|
4578
5295
|
} catch {
|
|
4579
5296
|
return void 0;
|
|
4580
5297
|
}
|
|
4581
5298
|
}
|
|
4582
5299
|
async function defaultReadEvidence(dir) {
|
|
4583
|
-
const raw = await readJson(
|
|
5300
|
+
const raw = await readJson(path15.join(dir, "api-evidence.json"));
|
|
4584
5301
|
if (!Array.isArray(raw)) return void 0;
|
|
4585
5302
|
return raw.map((r) => {
|
|
4586
5303
|
const rec = asRecord(r);
|
|
@@ -4622,7 +5339,7 @@ function casesFromApi(report, verdicts) {
|
|
|
4622
5339
|
});
|
|
4623
5340
|
}
|
|
4624
5341
|
async function readCairnRun(dir, deps = {}) {
|
|
4625
|
-
const raw = await readJson(
|
|
5342
|
+
const raw = await readJson(path15.join(dir, "report.json"));
|
|
4626
5343
|
if (raw === void 0) throw new CairnRunNotFoundError(dir);
|
|
4627
5344
|
const report = asRecord(raw);
|
|
4628
5345
|
if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
|
|
@@ -4652,20 +5369,20 @@ async function readCairnRun(dir, deps = {}) {
|
|
|
4652
5369
|
return {
|
|
4653
5370
|
schemaVersion: INGEST_SCHEMA_VERSION,
|
|
4654
5371
|
run: {
|
|
4655
|
-
runId: str(report["runId"],
|
|
5372
|
+
runId: str(report["runId"], path15.basename(dir)),
|
|
4656
5373
|
mode,
|
|
4657
5374
|
target: str(report["url"], "unknown")
|
|
4658
5375
|
},
|
|
4659
5376
|
cases
|
|
4660
5377
|
};
|
|
4661
5378
|
}
|
|
4662
|
-
var
|
|
5379
|
+
var fs16, path15, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
|
|
4663
5380
|
var init_cairn_artifact = __esm({
|
|
4664
5381
|
"src/cli/commands/cairn-artifact.ts"() {
|
|
4665
5382
|
"use strict";
|
|
4666
5383
|
init_cjs_shims();
|
|
4667
|
-
|
|
4668
|
-
|
|
5384
|
+
fs16 = __toESM(require("fs/promises"), 1);
|
|
5385
|
+
path15 = __toESM(require("path"), 1);
|
|
4669
5386
|
SUPPORTED_ARTIFACT_VERSION = 1;
|
|
4670
5387
|
INGEST_SCHEMA_VERSION = 1;
|
|
4671
5388
|
NoCairnRunsError = class extends Error {
|
|
@@ -4832,7 +5549,7 @@ __export(cases_exports, {
|
|
|
4832
5549
|
function gitDirty(file) {
|
|
4833
5550
|
try {
|
|
4834
5551
|
const out = (0, import_node_child_process2.execFileSync)("git", ["status", "--porcelain", "--", file], {
|
|
4835
|
-
cwd:
|
|
5552
|
+
cwd: path16.dirname(file),
|
|
4836
5553
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4837
5554
|
encoding: "utf8"
|
|
4838
5555
|
});
|
|
@@ -4858,9 +5575,9 @@ async function handlePull(deps = {}) {
|
|
|
4858
5575
|
const cwd = deps.cwd ?? process.cwd();
|
|
4859
5576
|
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
4860
5577
|
const token = tokenOf(deps);
|
|
4861
|
-
const file =
|
|
4862
|
-
if (
|
|
4863
|
-
throw new DirtyFileError(
|
|
5578
|
+
const file = path16.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
|
|
5579
|
+
if (fs17.existsSync(file) && !deps.force && (deps.isDirty ?? gitDirty)(file))
|
|
5580
|
+
throw new DirtyFileError(path16.relative(cwd, file) || file);
|
|
4864
5581
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
4865
5582
|
const endpoint = deps.suiteId !== void 0 ? `${apiUrl}/v1/suites/${encodeURIComponent(deps.suiteId)}/markdown` : `${apiUrl}/v1/test-cases/markdown`;
|
|
4866
5583
|
let res;
|
|
@@ -4875,18 +5592,18 @@ async function handlePull(deps = {}) {
|
|
|
4875
5592
|
throw new SyncHttpError(res.status, detail);
|
|
4876
5593
|
}
|
|
4877
5594
|
const text2 = await res.text();
|
|
4878
|
-
|
|
4879
|
-
|
|
5595
|
+
fs17.mkdirSync(path16.dirname(file), { recursive: true });
|
|
5596
|
+
fs17.writeFileSync(file, text2, "utf8");
|
|
4880
5597
|
return { file, cases: Number(res.headers.get("x-plune-cases") ?? "0") };
|
|
4881
5598
|
}
|
|
4882
5599
|
async function handlePush(deps = {}) {
|
|
4883
5600
|
const cwd = deps.cwd ?? process.cwd();
|
|
4884
5601
|
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
4885
5602
|
const token = tokenOf(deps);
|
|
4886
|
-
const file =
|
|
5603
|
+
const file = path16.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
|
|
4887
5604
|
let text2;
|
|
4888
5605
|
try {
|
|
4889
|
-
text2 =
|
|
5606
|
+
text2 = fs17.readFileSync(file, "utf8");
|
|
4890
5607
|
} catch {
|
|
4891
5608
|
throw new SyncFileError(
|
|
4892
5609
|
`No document to push: ${file}. Run "plune pull" first (or pass a file).`
|
|
@@ -4937,18 +5654,18 @@ function reportCasesFailure(err, write) {
|
|
|
4937
5654
|
}
|
|
4938
5655
|
return null;
|
|
4939
5656
|
}
|
|
4940
|
-
var import_node_child_process2,
|
|
5657
|
+
var import_node_child_process2, fs17, path16, DEFAULT_CASES_FILE, DirtyFileError;
|
|
4941
5658
|
var init_cases = __esm({
|
|
4942
5659
|
"src/cli/commands/cases.ts"() {
|
|
4943
5660
|
"use strict";
|
|
4944
5661
|
init_cjs_shims();
|
|
4945
5662
|
import_node_child_process2 = require("child_process");
|
|
4946
|
-
|
|
4947
|
-
|
|
5663
|
+
fs17 = __toESM(require("fs"), 1);
|
|
5664
|
+
path16 = __toESM(require("path"), 1);
|
|
4948
5665
|
init_api_url();
|
|
4949
5666
|
init_credentials();
|
|
4950
5667
|
init_sync();
|
|
4951
|
-
DEFAULT_CASES_FILE =
|
|
5668
|
+
DEFAULT_CASES_FILE = path16.join("plune", "cases.md");
|
|
4952
5669
|
DirtyFileError = class extends Error {
|
|
4953
5670
|
constructor(file) {
|
|
4954
5671
|
super(`${file} has uncommitted changes; commit them or pass --force to overwrite.`);
|
|
@@ -5029,11 +5746,11 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
5029
5746
|
init_cjs_shims();
|
|
5030
5747
|
init_api_url();
|
|
5031
5748
|
var import_commander = require("commander");
|
|
5032
|
-
var
|
|
5749
|
+
var import_node_fs6 = require("fs");
|
|
5033
5750
|
var import_node_path4 = require("path");
|
|
5034
5751
|
var import_node_url = require("url");
|
|
5035
5752
|
var __dirname = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5036
|
-
var pkg = JSON.parse((0,
|
|
5753
|
+
var pkg = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path4.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
5037
5754
|
function toFormat(s) {
|
|
5038
5755
|
return s === "json" || s === "markdown" ? s : "console";
|
|
5039
5756
|
}
|
|
@@ -5042,7 +5759,7 @@ function shouldColor(outputPath) {
|
|
|
5042
5759
|
}
|
|
5043
5760
|
function writeReport(text2, outputPath) {
|
|
5044
5761
|
if (outputPath !== void 0) {
|
|
5045
|
-
(0,
|
|
5762
|
+
(0, import_node_fs6.writeFileSync)(outputPath, text2);
|
|
5046
5763
|
} else {
|
|
5047
5764
|
process.stdout.write(text2 + "\n");
|
|
5048
5765
|
}
|
|
@@ -5233,11 +5950,15 @@ function createProgram() {
|
|
|
5233
5950
|
await failRunCommand(err, verboseOf(command));
|
|
5234
5951
|
}
|
|
5235
5952
|
});
|
|
5236
|
-
runCommand.command("import").argument("<file>", "A JUnit XML or Playwright JSON report").description("Turn a report another runner wrote into a run in Plune \u2014 no provider key needed").option("--format <fmt>", "junit | playwright-json; detected from the file 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).
|
|
5953
|
+
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(
|
|
5954
|
+
"--fail-on-missing-attachments",
|
|
5955
|
+
"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",
|
|
5956
|
+
false
|
|
5957
|
+
).action(
|
|
5237
5958
|
async (file, options, command) => {
|
|
5238
5959
|
const [
|
|
5239
5960
|
{ handleRunImport: handleRunImport2 },
|
|
5240
|
-
{ IMPORT_FORMATS: IMPORT_FORMATS2, UnknownFormatError: UnknownFormatError2, XmlParseError: XmlParseError2, JsonReportError: JsonReportError2 }
|
|
5961
|
+
{ IMPORT_FORMATS: IMPORT_FORMATS2, UnknownFormatError: UnknownFormatError2, XmlParseError: XmlParseError2, JsonReportError: JsonReportError2, AllureReportError: AllureReportError2 }
|
|
5241
5962
|
] = await Promise.all([
|
|
5242
5963
|
Promise.resolve().then(() => (init_run_import(), run_import_exports)),
|
|
5243
5964
|
Promise.resolve().then(() => (init_importers(), importers_exports))
|
|
@@ -5253,14 +5974,15 @@ function createProgram() {
|
|
|
5253
5974
|
return;
|
|
5254
5975
|
}
|
|
5255
5976
|
try {
|
|
5256
|
-
await handleRunImport2({
|
|
5977
|
+
const imported = await handleRunImport2({
|
|
5257
5978
|
file,
|
|
5258
5979
|
...format !== void 0 ? { format } : {},
|
|
5259
5980
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
5260
5981
|
...options.create ? { create: true } : {}
|
|
5261
5982
|
});
|
|
5983
|
+
if (options.failOnMissingAttachments && imported.missingAttachments.length > 0) process.exitCode = 1;
|
|
5262
5984
|
} catch (err) {
|
|
5263
|
-
if (err instanceof UnknownFormatError2 || err instanceof XmlParseError2 || err instanceof JsonReportError2) {
|
|
5985
|
+
if (err instanceof UnknownFormatError2 || err instanceof XmlParseError2 || err instanceof JsonReportError2 || err instanceof AllureReportError2) {
|
|
5264
5986
|
process.stderr.write(err.message + "\n");
|
|
5265
5987
|
process.exit(2);
|
|
5266
5988
|
return;
|
|
@@ -5339,10 +6061,10 @@ function createProgram() {
|
|
|
5339
6061
|
const { handleLogin: handleLogin2, reportLoginFailure: reportLoginFailure2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
5340
6062
|
try {
|
|
5341
6063
|
const token = options.token ?? await readTokenFromStdin();
|
|
5342
|
-
const { path:
|
|
6064
|
+
const { path: path17, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
|
|
5343
6065
|
process.stdout.write(
|
|
5344
|
-
verified ? `Logged in. Token checked against the API and saved to ${
|
|
5345
|
-
` : `Token saved to ${
|
|
6066
|
+
verified ? `Logged in. Token checked against the API and saved to ${path17}
|
|
6067
|
+
` : `Token saved to ${path17} WITHOUT being checked (--skip-verify).
|
|
5346
6068
|
`
|
|
5347
6069
|
);
|
|
5348
6070
|
} catch (err) {
|
|
@@ -5359,9 +6081,9 @@ function createProgram() {
|
|
|
5359
6081
|
const verbose = command.optsWithGlobals().verbose === true;
|
|
5360
6082
|
const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
|
|
5361
6083
|
try {
|
|
5362
|
-
const { removed, path:
|
|
6084
|
+
const { removed, path: path17 } = handleLogout2();
|
|
5363
6085
|
process.stdout.write(
|
|
5364
|
-
removed ? `Logged out. Removed ${
|
|
6086
|
+
removed ? `Logged out. Removed ${path17}
|
|
5365
6087
|
` : "Not logged in \u2014 nothing to remove.\n"
|
|
5366
6088
|
);
|
|
5367
6089
|
} catch (err) {
|
|
@@ -5490,7 +6212,7 @@ function isMainModule() {
|
|
|
5490
6212
|
const entry = process.argv[1];
|
|
5491
6213
|
if (entry === void 0) return false;
|
|
5492
6214
|
try {
|
|
5493
|
-
return (0,
|
|
6215
|
+
return (0, import_node_fs6.realpathSync)(entry) === (0, import_node_fs6.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5494
6216
|
} catch {
|
|
5495
6217
|
return false;
|
|
5496
6218
|
}
|