@plune-ai/cli 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -1
- package/dist/cli.cjs +387 -57
- package/dist/reporter-core.cjs +362 -49
- package/dist/reporter-core.d.cts +16 -1
- package/dist/reporter-core.d.ts +16 -1
- package/dist/reporter-core.js +358 -45
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -2304,6 +2304,13 @@ var init_credentials = __esm({
|
|
|
2304
2304
|
});
|
|
2305
2305
|
|
|
2306
2306
|
// src/reporter-core/client.ts
|
|
2307
|
+
function parseRetryAfter(raw, now = Date.now()) {
|
|
2308
|
+
const text2 = raw?.trim();
|
|
2309
|
+
if (text2 === void 0 || text2 === "") return null;
|
|
2310
|
+
const seconds = /^\d+(\.\d+)?$/.test(text2) ? Number(text2) : (Date.parse(text2) - now) / 1e3;
|
|
2311
|
+
if (!Number.isFinite(seconds)) return null;
|
|
2312
|
+
return Math.min(Math.max(seconds, 0) * 1e3, RETRY_AFTER_CAP_MS);
|
|
2313
|
+
}
|
|
2307
2314
|
function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
2308
2315
|
return new Promise((resolve9, reject) => {
|
|
2309
2316
|
const target = new URL(url);
|
|
@@ -2371,12 +2378,6 @@ async function detailOf(res) {
|
|
|
2371
2378
|
return { detail: "" };
|
|
2372
2379
|
}
|
|
2373
2380
|
}
|
|
2374
|
-
function retryAfterMs2(res) {
|
|
2375
|
-
const raw = res.headers.get("retry-after");
|
|
2376
|
-
if (raw === null) return null;
|
|
2377
|
-
const seconds = Number(raw.trim());
|
|
2378
|
-
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : null;
|
|
2379
|
-
}
|
|
2380
2381
|
function classify(status) {
|
|
2381
2382
|
if (status === 401 || status === 403) return "auth";
|
|
2382
2383
|
if (status === 409) return "conflict";
|
|
@@ -2387,15 +2388,34 @@ function classify(status) {
|
|
|
2387
2388
|
function createClient(opts) {
|
|
2388
2389
|
const doFetch = opts.fetchImpl ?? nodeTransport;
|
|
2389
2390
|
const wait = opts.wait ?? sleep;
|
|
2391
|
+
const uploadRetries = opts.uploadRetries === void 0 ? void 0 : Math.max(0, Math.floor(opts.uploadRetries));
|
|
2392
|
+
const random = opts.random ?? Math.random;
|
|
2393
|
+
const now = opts.now ?? Date.now;
|
|
2394
|
+
let pausedUntil = 0;
|
|
2395
|
+
const jittered = (ms) => Math.round(ms * (1 + random() * 0.5));
|
|
2396
|
+
async function backOff(attempt, said) {
|
|
2397
|
+
opts.onRetry?.();
|
|
2398
|
+
const ms = jittered(said ?? Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS));
|
|
2399
|
+
const until = now() + ms;
|
|
2400
|
+
pausedUntil = Math.max(pausedUntil, until);
|
|
2401
|
+
await wait(ms);
|
|
2402
|
+
return until;
|
|
2403
|
+
}
|
|
2404
|
+
async function respectPause(mine) {
|
|
2405
|
+
const left = pausedUntil - now();
|
|
2406
|
+
if (pausedUntil > mine && left > 0) await wait(left + Math.round(random() * 250));
|
|
2407
|
+
}
|
|
2390
2408
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
2391
2409
|
const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
|
|
2392
|
-
async function send(method, path17, payload) {
|
|
2410
|
+
async function send(method, path17, payload, retries = DEFAULT_RETRIES) {
|
|
2393
2411
|
let last = {
|
|
2394
2412
|
kind: "unavailable",
|
|
2395
2413
|
status: null,
|
|
2396
2414
|
detail: "no attempt was made"
|
|
2397
2415
|
};
|
|
2398
|
-
|
|
2416
|
+
let mine = 0;
|
|
2417
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
2418
|
+
await respectPause(mine);
|
|
2399
2419
|
let res;
|
|
2400
2420
|
try {
|
|
2401
2421
|
res = await doFetch(`${base}${path17}`, {
|
|
@@ -2408,7 +2428,8 @@ function createClient(opts) {
|
|
|
2408
2428
|
});
|
|
2409
2429
|
} catch (err) {
|
|
2410
2430
|
last = { kind: "unavailable", status: null, detail: scrub(String(err)) };
|
|
2411
|
-
if (attempt
|
|
2431
|
+
if (attempt >= Math.min(retries, DEFAULT_RETRIES)) break;
|
|
2432
|
+
mine = await backOff(attempt, null);
|
|
2412
2433
|
continue;
|
|
2413
2434
|
}
|
|
2414
2435
|
if (res.ok) {
|
|
@@ -2423,25 +2444,28 @@ function createClient(opts) {
|
|
|
2423
2444
|
const kind = classify(res.status);
|
|
2424
2445
|
last = { kind, status: res.status, detail: scrub(said.detail), ...said.code !== void 0 ? { code: said.code } : {} };
|
|
2425
2446
|
if (kind !== "unavailable") break;
|
|
2426
|
-
|
|
2447
|
+
const said429 = parseRetryAfter(res.headers.get("retry-after"), now());
|
|
2448
|
+
if (res.status === 429 && said429 === null) {
|
|
2427
2449
|
last.kind = "invalid";
|
|
2428
2450
|
last.detail = "quota exceeded (no Retry-After)";
|
|
2429
2451
|
if (said.detail) last.detail += ` \u2014 ${scrub(said.detail)}`;
|
|
2430
2452
|
break;
|
|
2431
2453
|
}
|
|
2432
|
-
if (attempt
|
|
2433
|
-
|
|
2434
|
-
}
|
|
2454
|
+
if (attempt >= (said429 === null ? Math.min(retries, DEFAULT_RETRIES) : retries)) break;
|
|
2455
|
+
mine = await backOff(attempt, said429);
|
|
2435
2456
|
}
|
|
2436
2457
|
return { ok: false, ...last };
|
|
2437
2458
|
}
|
|
2438
2459
|
return {
|
|
2439
|
-
post: (path17, body) => send("POST", path17, { body: JSON.stringify(body), type: "application/json" }),
|
|
2440
|
-
upload: (path17, bytes, contentType) => send("POST", path17, { body: bytes, type: contentType }),
|
|
2460
|
+
post: (path17, body, retries) => send("POST", path17, { body: JSON.stringify(body), type: "application/json" }, retries),
|
|
2461
|
+
upload: (path17, bytes, contentType) => send("POST", path17, { body: bytes, type: contentType }, uploadRetries ?? DEFAULT_RETRIES),
|
|
2441
2462
|
del: (path17) => send("DELETE", path17),
|
|
2442
2463
|
async put(url, headers, source) {
|
|
2464
|
+
const putRetries = uploadRetries ?? 3;
|
|
2443
2465
|
let last = { ok: false, status: null, code: "network" };
|
|
2444
|
-
|
|
2466
|
+
let mine = 0;
|
|
2467
|
+
for (let attempt = 0; attempt <= putRetries; attempt += 1) {
|
|
2468
|
+
await respectPause(mine);
|
|
2445
2469
|
try {
|
|
2446
2470
|
let status;
|
|
2447
2471
|
let retryAfter;
|
|
@@ -2456,21 +2480,21 @@ function createClient(opts) {
|
|
|
2456
2480
|
if (status >= 200 && status <= 299) return { ok: true };
|
|
2457
2481
|
last = { ok: false, status, code: `bucket_${status}` };
|
|
2458
2482
|
if (status < 500 && status !== 429) return last;
|
|
2459
|
-
const
|
|
2460
|
-
if (attempt
|
|
2461
|
-
|
|
2462
|
-
}
|
|
2483
|
+
const said = parseRetryAfter(retryAfter, now());
|
|
2484
|
+
if (attempt >= (said === null ? Math.min(putRetries, 3) : putRetries)) return last;
|
|
2485
|
+
mine = await backOff(attempt, said);
|
|
2463
2486
|
continue;
|
|
2464
2487
|
} catch (err) {
|
|
2465
2488
|
last = { ok: false, status: null, code: codeOfError(err) };
|
|
2466
2489
|
}
|
|
2467
|
-
if (attempt
|
|
2490
|
+
if (attempt >= Math.min(putRetries, 3)) return last;
|
|
2491
|
+
mine = await backOff(attempt, null);
|
|
2468
2492
|
}
|
|
2469
2493
|
return last;
|
|
2470
2494
|
}
|
|
2471
2495
|
};
|
|
2472
2496
|
}
|
|
2473
|
-
var import_node_fs2, import_node_stream, import_node_http, import_node_https,
|
|
2497
|
+
var import_node_fs2, import_node_stream, import_node_http, import_node_https, BACKOFF_BASE_MS, BACKOFF_CAP_MS, DEFAULT_RETRIES, RETRY_AFTER_CAP_MS, IDLE_MS, withoutLength, codeOfError, sleep;
|
|
2474
2498
|
var init_client = __esm({
|
|
2475
2499
|
"src/reporter-core/client.ts"() {
|
|
2476
2500
|
"use strict";
|
|
@@ -2479,9 +2503,11 @@ var init_client = __esm({
|
|
|
2479
2503
|
import_node_stream = require("stream");
|
|
2480
2504
|
import_node_http = require("http");
|
|
2481
2505
|
import_node_https = require("https");
|
|
2482
|
-
|
|
2506
|
+
BACKOFF_BASE_MS = 300;
|
|
2507
|
+
BACKOFF_CAP_MS = 1e4;
|
|
2508
|
+
DEFAULT_RETRIES = 2;
|
|
2509
|
+
RETRY_AFTER_CAP_MS = 6e4;
|
|
2483
2510
|
IDLE_MS = 3e5;
|
|
2484
|
-
PUT_DELAYS_MS = [500, 1500, 4e3];
|
|
2485
2511
|
withoutLength = (headers) => Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== "content-length"));
|
|
2486
2512
|
codeOfError = (err) => {
|
|
2487
2513
|
const code = err?.code;
|
|
@@ -2506,6 +2532,14 @@ function count(env, name) {
|
|
|
2506
2532
|
const n = Number(raw);
|
|
2507
2533
|
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
2508
2534
|
}
|
|
2535
|
+
function bounded(env, name, range, warnings) {
|
|
2536
|
+
const raw = value(env, name);
|
|
2537
|
+
if (raw === void 0) return void 0;
|
|
2538
|
+
const n = Number(raw);
|
|
2539
|
+
if (/^\d+$/.test(raw) && n >= range.min && n <= range.max) return n;
|
|
2540
|
+
warnings.push(`${name}=${JSON.stringify(raw.slice(0, 40))} is not a whole number from ${range.min} to ${range.max} \u2014 using ${range.default}.`);
|
|
2541
|
+
return void 0;
|
|
2542
|
+
}
|
|
2509
2543
|
function list(env, name) {
|
|
2510
2544
|
const raw = value(env, name);
|
|
2511
2545
|
if (raw === void 0) return void 0;
|
|
@@ -2518,6 +2552,9 @@ function readEnv(env = process.env) {
|
|
|
2518
2552
|
const apiUrl = value(env, "PLUNE_API_URL");
|
|
2519
2553
|
const fallbackPath = value(env, "PLUNE_FALLBACK");
|
|
2520
2554
|
const batchSize = count(env, "PLUNE_BATCH_SIZE");
|
|
2555
|
+
const warnings = [];
|
|
2556
|
+
const uploadConcurrency = bounded(env, "PLUNE_UPLOAD_CONCURRENCY", UPLOAD_CONCURRENCY, warnings);
|
|
2557
|
+
const uploadRetries = bounded(env, "PLUNE_UPLOAD_RETRIES", UPLOAD_RETRIES, warnings);
|
|
2521
2558
|
const title = value(env, "PLUNE_RUN_TITLE");
|
|
2522
2559
|
const environment = value(env, "PLUNE_ENV");
|
|
2523
2560
|
const labels = list(env, "PLUNE_LABELS");
|
|
@@ -2530,6 +2567,8 @@ function readEnv(env = process.env) {
|
|
|
2530
2567
|
...apiUrl !== void 0 ? { apiUrl } : {},
|
|
2531
2568
|
...fallbackPath !== void 0 ? { fallbackPath } : {},
|
|
2532
2569
|
...batchSize !== void 0 ? { batchSize } : {},
|
|
2570
|
+
...uploadConcurrency !== void 0 ? { uploadConcurrency } : {},
|
|
2571
|
+
...uploadRetries !== void 0 ? { uploadRetries } : {},
|
|
2533
2572
|
...title !== void 0 ? { title } : {},
|
|
2534
2573
|
...environment !== void 0 ? { environment } : {},
|
|
2535
2574
|
...labels !== void 0 ? { labels } : {},
|
|
@@ -2542,7 +2581,8 @@ function readEnv(env = process.env) {
|
|
|
2542
2581
|
// reporting into this run; `PROCEED` says the job will close it on its own schedule. Either
|
|
2543
2582
|
// way this process must not be the one to call it finished.
|
|
2544
2583
|
keepOpen: flag(env, "PLUNE_SHARED_RUN") || flag(env, "PLUNE_PROCEED"),
|
|
2545
|
-
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
|
|
2584
|
+
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0),
|
|
2585
|
+
warnings
|
|
2546
2586
|
};
|
|
2547
2587
|
}
|
|
2548
2588
|
function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
|
|
@@ -2568,13 +2608,15 @@ function ciCommit(reported, env = process.env) {
|
|
|
2568
2608
|
...branch !== void 0 ? { branch } : {}
|
|
2569
2609
|
};
|
|
2570
2610
|
}
|
|
2571
|
-
var path6, UNSUPPORTED_VARS, PROVIDERS;
|
|
2611
|
+
var path6, UNSUPPORTED_VARS, UPLOAD_CONCURRENCY, UPLOAD_RETRIES, PROVIDERS;
|
|
2572
2612
|
var init_env2 = __esm({
|
|
2573
2613
|
"src/reporter-core/env.ts"() {
|
|
2574
2614
|
"use strict";
|
|
2575
2615
|
init_cjs_shims();
|
|
2576
2616
|
path6 = __toESM(require("path"), 1);
|
|
2577
2617
|
UNSUPPORTED_VARS = ["PLUNE_GROUP"];
|
|
2618
|
+
UPLOAD_CONCURRENCY = { min: 1, max: 32, default: 4 };
|
|
2619
|
+
UPLOAD_RETRIES = { min: 0, max: 20, default: 6 };
|
|
2578
2620
|
PROVIDERS = [
|
|
2579
2621
|
{ is: "GITHUB_ACTIONS", sha: "GITHUB_SHA", branch: ["GITHUB_HEAD_REF", "GITHUB_REF_NAME"] },
|
|
2580
2622
|
{ is: "GITLAB_CI", sha: "CI_COMMIT_SHA", branch: ["CI_COMMIT_REF_NAME"] },
|
|
@@ -2747,6 +2789,34 @@ var init_failure_detail = __esm({
|
|
|
2747
2789
|
}
|
|
2748
2790
|
});
|
|
2749
2791
|
|
|
2792
|
+
// src/reporter-core/files-batch.ts
|
|
2793
|
+
async function sha256File(path17) {
|
|
2794
|
+
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
2795
|
+
for await (const part of (0, import_node_fs4.createReadStream)(path17)) hash.update(part);
|
|
2796
|
+
return hash.digest("hex");
|
|
2797
|
+
}
|
|
2798
|
+
var import_node_crypto2, import_node_fs4, FILES_BATCH_MAX, DIRECT_BATCH_BYTES, BATCH_PIPELINES, sha256Bytes, BUCKET_CODES;
|
|
2799
|
+
var init_files_batch = __esm({
|
|
2800
|
+
"src/reporter-core/files-batch.ts"() {
|
|
2801
|
+
"use strict";
|
|
2802
|
+
init_cjs_shims();
|
|
2803
|
+
import_node_crypto2 = require("crypto");
|
|
2804
|
+
import_node_fs4 = require("fs");
|
|
2805
|
+
FILES_BATCH_MAX = 100;
|
|
2806
|
+
DIRECT_BATCH_BYTES = 6 * 1024 * 1024;
|
|
2807
|
+
BATCH_PIPELINES = 2;
|
|
2808
|
+
sha256Bytes = (bytes) => (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
|
|
2809
|
+
BUCKET_CODES = /* @__PURE__ */ new Set([
|
|
2810
|
+
"bucket_unreachable",
|
|
2811
|
+
"not_connected",
|
|
2812
|
+
"platform_not_configured",
|
|
2813
|
+
"upload_expired",
|
|
2814
|
+
"file_mismatch",
|
|
2815
|
+
"file_missing"
|
|
2816
|
+
]);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
|
|
2750
2820
|
// src/reporter-core/session.ts
|
|
2751
2821
|
function nameOf(file) {
|
|
2752
2822
|
const own = file.name.split(/[\\/]/).pop()?.trim() || (file.path === void 0 ? "" : (0, import_node_path3.basename)(file.path));
|
|
@@ -2798,7 +2868,7 @@ function tally(counts, noun) {
|
|
|
2798
2868
|
}
|
|
2799
2869
|
function boundKey(key) {
|
|
2800
2870
|
if (key.value.length <= KEY_MAX) return key;
|
|
2801
|
-
const digest = (0,
|
|
2871
|
+
const digest = (0, import_node_crypto3.createHash)("sha256").update(key.value).digest("hex").slice(0, 16);
|
|
2802
2872
|
let head = key.value.slice(0, KEY_MAX - digest.length - 1);
|
|
2803
2873
|
if (/[\uD800-\uDBFF]$/.test(head)) head = head.slice(0, -1);
|
|
2804
2874
|
return { ...key, value: `${head}#${digest}` };
|
|
@@ -2850,13 +2920,22 @@ async function startRun(passed, declared2) {
|
|
|
2850
2920
|
`));
|
|
2851
2921
|
const fallbackPath = cfg.fallbackPath ?? DEFAULT_FALLBACK_PATH;
|
|
2852
2922
|
const externalKey = cfg.externalKey ?? null;
|
|
2853
|
-
const sessionMarker = (0,
|
|
2923
|
+
const sessionMarker = (0, import_node_crypto3.randomUUID)();
|
|
2854
2924
|
const batchSize = Math.min(cfg.batchSize ?? BATCH_DEFAULT, BATCH_MAX);
|
|
2855
2925
|
const offerDiscovered = cfg.offerDiscovered ?? false;
|
|
2856
2926
|
const token = cfg.token ?? loadToken() ?? "";
|
|
2927
|
+
const uploadsAtOnce = Math.min(Math.max(Math.floor(cfg.uploadConcurrency ?? UPLOAD_CONCURRENCY.default), UPLOAD_CONCURRENCY.min), UPLOAD_CONCURRENCY.max);
|
|
2928
|
+
const uploadRetries = Math.min(Math.max(Math.floor(cfg.uploadRetries ?? UPLOAD_RETRIES.default), UPLOAD_RETRIES.min), UPLOAD_RETRIES.max);
|
|
2929
|
+
let retried = 0;
|
|
2930
|
+
let uploadMs = 0;
|
|
2931
|
+
let requests = 0;
|
|
2932
|
+
let existed = 0;
|
|
2933
|
+
let batchMissing = false;
|
|
2857
2934
|
const client = createClient({
|
|
2858
2935
|
apiUrl: resolveApiUrl(cfg.apiUrl),
|
|
2859
2936
|
token,
|
|
2937
|
+
uploadRetries,
|
|
2938
|
+
onRetry: () => void (retried += 1),
|
|
2860
2939
|
...cfg.fetchImpl !== void 0 ? { fetchImpl: cfg.fetchImpl } : {}
|
|
2861
2940
|
});
|
|
2862
2941
|
const stats = {
|
|
@@ -2907,6 +2986,7 @@ async function startRun(passed, declared2) {
|
|
|
2907
2986
|
if (offline) {
|
|
2908
2987
|
log('plune: no API token \u2014 run "plune login" first. Results will be written to the fallback file.');
|
|
2909
2988
|
}
|
|
2989
|
+
for (const warning of env.warnings) log(`plune: ${warning}`);
|
|
2910
2990
|
if (env.ignored.length > 0) {
|
|
2911
2991
|
log(`plune: ignoring ${env.ignored.join(", ")} \u2014 a run has nowhere to store them yet.`);
|
|
2912
2992
|
}
|
|
@@ -3034,9 +3114,10 @@ async function startRun(passed, declared2) {
|
|
|
3034
3114
|
else log(`plune: ${KINDS[kind].label} "${name}" not uploaded \u2014 ${SKIPPED[why]}.`);
|
|
3035
3115
|
}
|
|
3036
3116
|
function ask(job, size) {
|
|
3117
|
+
requests += 1;
|
|
3037
3118
|
return client.post(`/v1/results/${encodeURIComponent(job.id)}/files/uploads`, {
|
|
3038
3119
|
files: [{ name: job.name, contentType: job.type, size }]
|
|
3039
|
-
});
|
|
3120
|
+
}, uploadRetries);
|
|
3040
3121
|
}
|
|
3041
3122
|
function failed(job, why) {
|
|
3042
3123
|
countsOf(job.kind).failed += 1;
|
|
@@ -3068,13 +3149,14 @@ async function startRun(passed, declared2) {
|
|
|
3068
3149
|
if (job.body !== void 0) bytes = job.body;
|
|
3069
3150
|
else {
|
|
3070
3151
|
try {
|
|
3071
|
-
bytes = await
|
|
3152
|
+
bytes = await import_node_fs5.promises.readFile(job.path);
|
|
3072
3153
|
} catch {
|
|
3073
3154
|
return skip(kind, job.name, "missing");
|
|
3074
3155
|
}
|
|
3075
3156
|
}
|
|
3076
3157
|
const refusal = kind === "text" ? textRefusal(bytes) : void 0;
|
|
3077
3158
|
if (refusal !== void 0) return skip(kind, job.name, refusal);
|
|
3159
|
+
requests += 1;
|
|
3078
3160
|
const out = await client.upload(
|
|
3079
3161
|
`/v1/results/${encodeURIComponent(job.id)}/files?name=${encodeURIComponent(job.name)}`,
|
|
3080
3162
|
bytes,
|
|
@@ -3097,11 +3179,12 @@ async function startRun(passed, declared2) {
|
|
|
3097
3179
|
if (fresh === void 0) return failedToBucket(job, "bad_answer");
|
|
3098
3180
|
link = fresh;
|
|
3099
3181
|
}
|
|
3100
|
-
|
|
3182
|
+
requests += 1;
|
|
3183
|
+
const done2 = await client.post(`/v1/files/${encodeURIComponent(link.id)}/complete`, {}, uploadRetries);
|
|
3101
3184
|
if (!done2.ok) return failedToBucket(job, codeOf2(done2));
|
|
3102
3185
|
countsOf(job.kind).uploaded += 1;
|
|
3103
3186
|
}
|
|
3104
|
-
let free =
|
|
3187
|
+
let free = uploadsAtOnce;
|
|
3105
3188
|
const waiting = [];
|
|
3106
3189
|
async function gated(work) {
|
|
3107
3190
|
if (free === 0) await new Promise((resolve9) => waiting.push(resolve9));
|
|
@@ -3121,7 +3204,7 @@ async function startRun(passed, declared2) {
|
|
|
3121
3204
|
else if (job.path === void 0) return void skip(kind, job.name, "missing");
|
|
3122
3205
|
else {
|
|
3123
3206
|
try {
|
|
3124
|
-
size = (await
|
|
3207
|
+
size = (await import_node_fs5.promises.stat(job.path)).size;
|
|
3125
3208
|
} catch {
|
|
3126
3209
|
return void skip(kind, job.name, "missing");
|
|
3127
3210
|
}
|
|
@@ -3157,6 +3240,245 @@ async function startRun(passed, declared2) {
|
|
|
3157
3240
|
}
|
|
3158
3241
|
return () => uploadToBucket(job, size, link);
|
|
3159
3242
|
}
|
|
3243
|
+
async function uploadEach(jobs) {
|
|
3244
|
+
const byResult = /* @__PURE__ */ new Map();
|
|
3245
|
+
for (const job of jobs) byResult.set(job.id, [...byResult.get(job.id) ?? [], job]);
|
|
3246
|
+
await Promise.all(
|
|
3247
|
+
[...byResult.values()].map(async (group) => {
|
|
3248
|
+
const work = [];
|
|
3249
|
+
for (const job of group) {
|
|
3250
|
+
const act = await plan(job);
|
|
3251
|
+
if (act !== void 0) work.push(gated(act));
|
|
3252
|
+
}
|
|
3253
|
+
await Promise.all(work);
|
|
3254
|
+
})
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
async function measure(job) {
|
|
3258
|
+
let size;
|
|
3259
|
+
if (job.body !== void 0) size = job.body.length;
|
|
3260
|
+
else if (job.path === void 0) return void skip(job.kind, job.name, "missing");
|
|
3261
|
+
else {
|
|
3262
|
+
try {
|
|
3263
|
+
size = (await import_node_fs5.promises.stat(job.path)).size;
|
|
3264
|
+
} catch {
|
|
3265
|
+
return void skip(job.kind, job.name, "missing");
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
if (size === 0) return void skip(job.kind, job.name, "empty");
|
|
3269
|
+
return size;
|
|
3270
|
+
}
|
|
3271
|
+
const routeMissing = (out) => !out.ok && out.status === 404 && out.detail === "" && out.code === void 0;
|
|
3272
|
+
function takeRoom(job) {
|
|
3273
|
+
if (job.kind === "file") {
|
|
3274
|
+
skip(job.kind, job.name, "noStorage");
|
|
3275
|
+
return false;
|
|
3276
|
+
}
|
|
3277
|
+
const roomKey = `${job.id}/${job.kind}`;
|
|
3278
|
+
const left = rooms.get(roomKey) ?? KINDS[job.kind].room;
|
|
3279
|
+
if (left === 0) {
|
|
3280
|
+
skip(job.kind, job.name, KINDS[job.kind].limit);
|
|
3281
|
+
return false;
|
|
3282
|
+
}
|
|
3283
|
+
rooms.set(roomKey, left - 1);
|
|
3284
|
+
return true;
|
|
3285
|
+
}
|
|
3286
|
+
function refusedItem(job, size, item) {
|
|
3287
|
+
const code = item.code ?? "refused";
|
|
3288
|
+
if (code === "duplicate") existed += 1;
|
|
3289
|
+
else if (code === "no_storage") skip(job.kind, job.name, refusedHere(job, size) ?? "noStorage");
|
|
3290
|
+
else if (code === "too_many_files") skip(job.kind, job.name, KINDS[job.kind].limit);
|
|
3291
|
+
else if (BUCKET_CODES.has(code)) failedToBucket(job, code);
|
|
3292
|
+
else failed(job, code);
|
|
3293
|
+
}
|
|
3294
|
+
const itemPause = (round) => new Promise((r) => setTimeout(r, Math.min(250 * 2 ** round, 4e3)));
|
|
3295
|
+
async function batchOf(jobs) {
|
|
3296
|
+
if (batchMissing) return uploadEach(jobs);
|
|
3297
|
+
const sized = [];
|
|
3298
|
+
for (const job of jobs) {
|
|
3299
|
+
const size = await measure(job);
|
|
3300
|
+
if (size === void 0) continue;
|
|
3301
|
+
try {
|
|
3302
|
+
sized.push({ job, size, sha256: job.body !== void 0 ? sha256Bytes(job.body) : await sha256File(job.path) });
|
|
3303
|
+
} catch {
|
|
3304
|
+
skip(job.kind, job.name, "missing");
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (sized.length === 0) return;
|
|
3308
|
+
const announced = await gated(() => {
|
|
3309
|
+
requests += 1;
|
|
3310
|
+
return client.post(
|
|
3311
|
+
"/v1/files/uploads-batch",
|
|
3312
|
+
{ items: sized.map((s) => ({ resultId: s.job.id, name: s.job.name, contentType: s.job.type, size: s.size, sha256: s.sha256 })) },
|
|
3313
|
+
uploadRetries
|
|
3314
|
+
);
|
|
3315
|
+
});
|
|
3316
|
+
if (routeMissing(announced)) {
|
|
3317
|
+
batchMissing = true;
|
|
3318
|
+
return uploadEach(sized.map((s) => s.job));
|
|
3319
|
+
}
|
|
3320
|
+
const verdicts = announced.ok ? announced.body?.items : void 0;
|
|
3321
|
+
if (!announced.ok || !Array.isArray(verdicts) || verdicts.length !== sized.length) {
|
|
3322
|
+
for (const s of sized) {
|
|
3323
|
+
if (announced.ok) failed(s.job, "bad_answer");
|
|
3324
|
+
else if (announced.code !== void 0) failedToBucket(s.job, announced.code);
|
|
3325
|
+
else failed(s.job, announced.detail || announced.kind);
|
|
3326
|
+
}
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
const puts = [];
|
|
3330
|
+
const directs = [];
|
|
3331
|
+
sized.forEach((s, i) => {
|
|
3332
|
+
const v = verdicts[i] ?? {};
|
|
3333
|
+
if (v.status === "exists") existed += 1;
|
|
3334
|
+
else if (v.status === "direct") {
|
|
3335
|
+
if (takeRoom(s.job)) directs.push(s);
|
|
3336
|
+
} else if (v.status === "upload") {
|
|
3337
|
+
const link = linkOf(v);
|
|
3338
|
+
if (link === void 0) return failed(s.job, "bad_answer");
|
|
3339
|
+
if (!bucketSeen) {
|
|
3340
|
+
bucketSeen = true;
|
|
3341
|
+
log(BUCKET_FOUND);
|
|
3342
|
+
}
|
|
3343
|
+
puts.push({ s, link });
|
|
3344
|
+
} else if (v.status === "refused") refusedItem(s.job, s.size, v);
|
|
3345
|
+
else failed(s.job, "bad_answer");
|
|
3346
|
+
});
|
|
3347
|
+
const toComplete = [];
|
|
3348
|
+
await Promise.all(
|
|
3349
|
+
puts.map(
|
|
3350
|
+
({ s, link: first }) => gated(async () => {
|
|
3351
|
+
const source = s.job.body !== void 0 ? { body: s.job.body } : { path: s.job.path, size: s.size };
|
|
3352
|
+
let link = first;
|
|
3353
|
+
for (let renewed = false; ; ) {
|
|
3354
|
+
const put = await client.put(link.url, link.headers, source);
|
|
3355
|
+
if (put.ok) break;
|
|
3356
|
+
if (put.status !== 403 || renewed) return failedToBucket(s.job, put.code);
|
|
3357
|
+
renewed = true;
|
|
3358
|
+
requests += 1;
|
|
3359
|
+
const again = await client.post(
|
|
3360
|
+
"/v1/files/uploads-batch",
|
|
3361
|
+
{ items: [{ resultId: s.job.id, name: s.job.name, contentType: s.job.type, size: s.size, sha256: s.sha256 }] },
|
|
3362
|
+
uploadRetries
|
|
3363
|
+
);
|
|
3364
|
+
const fresh = again.ok ? linkOf(again.body?.items?.[0]) : void 0;
|
|
3365
|
+
if (fresh === void 0) return failedToBucket(s.job, again.ok ? "bad_answer" : codeOf2(again));
|
|
3366
|
+
link = fresh;
|
|
3367
|
+
}
|
|
3368
|
+
toComplete.push({ job: s.job, id: link.id });
|
|
3369
|
+
})
|
|
3370
|
+
)
|
|
3371
|
+
);
|
|
3372
|
+
await complete(toComplete);
|
|
3373
|
+
await sendDirect(directs);
|
|
3374
|
+
}
|
|
3375
|
+
async function complete(items) {
|
|
3376
|
+
for (let round = 0; items.length > 0; round += 1) {
|
|
3377
|
+
const out = await gated(() => {
|
|
3378
|
+
requests += 1;
|
|
3379
|
+
return client.post("/v1/files/complete-batch", { ids: items.map((i) => i.id) }, uploadRetries);
|
|
3380
|
+
});
|
|
3381
|
+
if (routeMissing(out)) {
|
|
3382
|
+
await Promise.all(
|
|
3383
|
+
items.map(
|
|
3384
|
+
(i) => gated(async () => {
|
|
3385
|
+
requests += 1;
|
|
3386
|
+
const one = await client.post(`/v1/files/${encodeURIComponent(i.id)}/complete`, {}, uploadRetries);
|
|
3387
|
+
if (one.ok) countsOf(i.job.kind).uploaded += 1;
|
|
3388
|
+
else failedToBucket(i.job, codeOf2(one));
|
|
3389
|
+
})
|
|
3390
|
+
)
|
|
3391
|
+
);
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const verdicts = out.ok ? out.body?.items : void 0;
|
|
3395
|
+
if (!out.ok || !Array.isArray(verdicts) || verdicts.length !== items.length) {
|
|
3396
|
+
for (const i of items) failedToBucket(i.job, out.ok ? "bad_answer" : codeOf2(out));
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
const again = [];
|
|
3400
|
+
items.forEach((i, at) => {
|
|
3401
|
+
const v = verdicts[at] ?? {};
|
|
3402
|
+
if (v.status === "ready") countsOf(i.job.kind).uploaded += 1;
|
|
3403
|
+
else if (v.retryable === true && round < Math.min(uploadRetries, 3)) again.push(i);
|
|
3404
|
+
else refusedItem(i.job, 0, v.status === "refused" ? v : { code: "bad_answer" });
|
|
3405
|
+
});
|
|
3406
|
+
items = again;
|
|
3407
|
+
if (items.length > 0) {
|
|
3408
|
+
retried += items.length;
|
|
3409
|
+
await itemPause(round);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
async function sendDirect(items) {
|
|
3414
|
+
const groups = [];
|
|
3415
|
+
let bytes = 0;
|
|
3416
|
+
for (const item of items) {
|
|
3417
|
+
const group = groups[groups.length - 1];
|
|
3418
|
+
if (group === void 0 || group.length >= FILES_BATCH_MAX || bytes + item.size > DIRECT_BATCH_BYTES) {
|
|
3419
|
+
groups.push([item]);
|
|
3420
|
+
bytes = item.size;
|
|
3421
|
+
} else {
|
|
3422
|
+
group.push(item);
|
|
3423
|
+
bytes += item.size;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
for (const group of groups) {
|
|
3427
|
+
const body = [];
|
|
3428
|
+
const sent = [];
|
|
3429
|
+
for (const { job, size } of group) {
|
|
3430
|
+
const { kind } = job;
|
|
3431
|
+
if (size > KINDS[kind].bytes) {
|
|
3432
|
+
skip(kind, job.name, KINDS[kind].size);
|
|
3433
|
+
continue;
|
|
3434
|
+
}
|
|
3435
|
+
let data;
|
|
3436
|
+
try {
|
|
3437
|
+
data = job.body ?? await import_node_fs5.promises.readFile(job.path);
|
|
3438
|
+
} catch {
|
|
3439
|
+
skip(kind, job.name, "missing");
|
|
3440
|
+
continue;
|
|
3441
|
+
}
|
|
3442
|
+
const refusal = kind === "text" ? textRefusal(data) : void 0;
|
|
3443
|
+
if (refusal !== void 0) {
|
|
3444
|
+
skip(kind, job.name, refusal);
|
|
3445
|
+
continue;
|
|
3446
|
+
}
|
|
3447
|
+
body.push({ resultId: job.id, name: job.name, contentType: job.type, data: data.toString("base64") });
|
|
3448
|
+
sent.push({ job, size });
|
|
3449
|
+
}
|
|
3450
|
+
if (body.length === 0) continue;
|
|
3451
|
+
const out = await gated(() => {
|
|
3452
|
+
requests += 1;
|
|
3453
|
+
return client.post("/v1/files/direct-batch", { items: body }, uploadRetries);
|
|
3454
|
+
});
|
|
3455
|
+
if (routeMissing(out)) {
|
|
3456
|
+
batchMissing = true;
|
|
3457
|
+
for (const { job, size } of sent) await gated(() => uploadDirect(job, size));
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
const verdicts = out.ok ? out.body?.items : void 0;
|
|
3461
|
+
if (!out.ok || !Array.isArray(verdicts) || verdicts.length !== sent.length) {
|
|
3462
|
+
for (const { job } of sent) failed(job, out.ok ? "bad_answer" : out.detail || out.kind);
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
sent.forEach(({ job }, i) => {
|
|
3466
|
+
const v = verdicts[i] ?? {};
|
|
3467
|
+
if (v.status === "stored") countsOf(job.kind).uploaded += 1;
|
|
3468
|
+
else if (v.status === "exists") existed += 1;
|
|
3469
|
+
else refusedItem(job, 0, v.status === "refused" ? v : { code: "bad_answer" });
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
async function uploadBatched(jobs) {
|
|
3474
|
+
const batches = chunk(jobs, FILES_BATCH_MAX);
|
|
3475
|
+
let next = 0;
|
|
3476
|
+
await Promise.all(
|
|
3477
|
+
Array.from({ length: Math.min(BATCH_PIPELINES, batches.length) }, async () => {
|
|
3478
|
+
for (let at = next++; at < batches.length; at = next++) await batchOf(batches[at]);
|
|
3479
|
+
})
|
|
3480
|
+
);
|
|
3481
|
+
}
|
|
3160
3482
|
async function upload(items, filesByKey) {
|
|
3161
3483
|
if (filesByKey.size === 0 || !Array.isArray(items)) return;
|
|
3162
3484
|
const jobs = [];
|
|
@@ -3181,18 +3503,10 @@ async function startRun(passed, declared2) {
|
|
|
3181
3503
|
}
|
|
3182
3504
|
}
|
|
3183
3505
|
}
|
|
3184
|
-
const
|
|
3185
|
-
|
|
3186
|
-
await
|
|
3187
|
-
|
|
3188
|
-
const work = [];
|
|
3189
|
-
for (const job of group) {
|
|
3190
|
-
const act = await plan(job);
|
|
3191
|
-
if (act !== void 0) work.push(gated(act));
|
|
3192
|
-
}
|
|
3193
|
-
await Promise.all(work);
|
|
3194
|
-
})
|
|
3195
|
-
);
|
|
3506
|
+
const started = Date.now();
|
|
3507
|
+
if (batchMissing) await uploadEach(jobs);
|
|
3508
|
+
else await uploadBatched(jobs);
|
|
3509
|
+
uploadMs += Date.now() - started;
|
|
3196
3510
|
}
|
|
3197
3511
|
async function flush() {
|
|
3198
3512
|
if (buffer.length === 0) return;
|
|
@@ -3285,6 +3599,13 @@ async function startRun(passed, declared2) {
|
|
|
3285
3599
|
}
|
|
3286
3600
|
if (stats.deferred > 0) parts.push(`${stats.deferred} not delivered \u2014 written to ${fallbackPath}`);
|
|
3287
3601
|
if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
|
|
3602
|
+
const files = [stats.screenshots, stats.textFiles, stats.otherFiles];
|
|
3603
|
+
const total = (what) => files.reduce((n, counts) => n + counts[what], 0);
|
|
3604
|
+
if (total("uploaded") + total("skipped") + total("failed") + existed > 0) {
|
|
3605
|
+
log(
|
|
3606
|
+
`plune: files \u2014 ${total("uploaded")} uploaded, ${total("skipped")} skipped, ${existed} already there (skipped), ${retried} retried, ${total("failed")} dropped, in ${(uploadMs / 1e3).toFixed(1)} s (${uploadsAtOnce} at once, ${requests} request(s) to Plune)`
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3288
3609
|
log(`plune: ${parts.join(" \xB7 ")}`);
|
|
3289
3610
|
if (stats.deferred > 0 && process.env["GITHUB_ACTIONS"] === "true") {
|
|
3290
3611
|
log(`::warning::${stats.deferred} result(s) were not delivered to Plune \u2014 see the reporting step's log.`);
|
|
@@ -3339,13 +3660,13 @@ async function startRun(passed, declared2) {
|
|
|
3339
3660
|
}
|
|
3340
3661
|
};
|
|
3341
3662
|
}
|
|
3342
|
-
var
|
|
3663
|
+
var import_node_crypto3, import_node_fs5, 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, NAME_MAX, SKIPPED, BUCKET_FOUND, KINDS, codeOf2, BATCH_BYTES, ENVELOPE_BYTES;
|
|
3343
3664
|
var init_session = __esm({
|
|
3344
3665
|
"src/reporter-core/session.ts"() {
|
|
3345
3666
|
"use strict";
|
|
3346
3667
|
init_cjs_shims();
|
|
3347
|
-
|
|
3348
|
-
|
|
3668
|
+
import_node_crypto3 = require("crypto");
|
|
3669
|
+
import_node_fs5 = require("fs");
|
|
3349
3670
|
import_node_path3 = require("path");
|
|
3350
3671
|
import_node_util2 = require("util");
|
|
3351
3672
|
init_api_url();
|
|
@@ -3353,11 +3674,12 @@ var init_session = __esm({
|
|
|
3353
3674
|
init_client();
|
|
3354
3675
|
init_env2();
|
|
3355
3676
|
init_failure_detail();
|
|
3677
|
+
init_files_batch();
|
|
3356
3678
|
init_fallback();
|
|
3357
3679
|
RESOLVE_CHUNK = 500;
|
|
3358
3680
|
BATCH_MAX = 500;
|
|
3359
3681
|
DISCOVER_MAX = 500;
|
|
3360
|
-
BATCH_DEFAULT =
|
|
3682
|
+
BATCH_DEFAULT = 500;
|
|
3361
3683
|
EXPECTED_MAX = 1e4;
|
|
3362
3684
|
KEY_MAX = 1024;
|
|
3363
3685
|
TITLE_MAX = 300;
|
|
@@ -3375,7 +3697,6 @@ var init_session = __esm({
|
|
|
3375
3697
|
".webp": "image/webp"
|
|
3376
3698
|
};
|
|
3377
3699
|
METADATA_TYPE2 = "application/plune.metadata+json";
|
|
3378
|
-
UPLOADS_AT_ONCE = 4;
|
|
3379
3700
|
NAME_MAX = 200;
|
|
3380
3701
|
SKIPPED = {
|
|
3381
3702
|
hidden: "its name starts with an underscore, which hides an attachment in Playwright's own reports too",
|
|
@@ -4821,7 +5142,7 @@ function readPlaywrightJson(source, file) {
|
|
|
4821
5142
|
const { config, suites } = parsed;
|
|
4822
5143
|
const rootDir = typeof config?.rootDir === "string" ? config.rootDir : void 0;
|
|
4823
5144
|
const buildHref = config?.metadata?.ci?.buildHref;
|
|
4824
|
-
const repoRoot = rootDir !== void 0 && (0,
|
|
5145
|
+
const repoRoot = rootDir !== void 0 && (0, import_node_fs6.existsSync)(rootDir) ? repoRootOf(rootDir) : void 0;
|
|
4825
5146
|
const ctx = {
|
|
4826
5147
|
...rootDir !== void 0 ? { rootDir } : {},
|
|
4827
5148
|
...repoRoot !== void 0 ? { repoRoot } : {},
|
|
@@ -4840,12 +5161,12 @@ function readPlaywrightJson(source, file) {
|
|
|
4840
5161
|
function looksLikePlaywrightJson(source) {
|
|
4841
5162
|
return /^\s*\{/.test(source) && source.includes('"suites"');
|
|
4842
5163
|
}
|
|
4843
|
-
var
|
|
5164
|
+
var import_node_fs6, SOURCE3, TOKEN3, NOT_FAILED, JsonReportError;
|
|
4844
5165
|
var init_playwright_json = __esm({
|
|
4845
5166
|
"src/importers/playwright-json.ts"() {
|
|
4846
5167
|
"use strict";
|
|
4847
5168
|
init_cjs_shims();
|
|
4848
|
-
|
|
5169
|
+
import_node_fs6 = require("fs");
|
|
4849
5170
|
init_failure_detail();
|
|
4850
5171
|
init_result_key();
|
|
4851
5172
|
SOURCE3 = "playwright";
|
|
@@ -4958,6 +5279,7 @@ async function handleRunImport(options) {
|
|
|
4958
5279
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
4959
5280
|
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
4960
5281
|
...options.create === true ? { offerDiscovered: true } : {},
|
|
5282
|
+
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
4961
5283
|
kind: "automated",
|
|
4962
5284
|
// The CI run the report names, never this machine's: whoever imports may be another job (#790).
|
|
4963
5285
|
// The commit and branch likewise, where the report names a commit; a report that names none —
|
|
@@ -5872,11 +6194,11 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
5872
6194
|
init_cjs_shims();
|
|
5873
6195
|
init_api_url();
|
|
5874
6196
|
var import_commander = require("commander");
|
|
5875
|
-
var
|
|
6197
|
+
var import_node_fs7 = require("fs");
|
|
5876
6198
|
var import_node_path4 = require("path");
|
|
5877
6199
|
var import_node_url = require("url");
|
|
5878
6200
|
var __dirname = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5879
|
-
var pkg = JSON.parse((0,
|
|
6201
|
+
var pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path4.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
5880
6202
|
function toFormat(s) {
|
|
5881
6203
|
return s === "json" || s === "markdown" ? s : "console";
|
|
5882
6204
|
}
|
|
@@ -5885,7 +6207,7 @@ function shouldColor(outputPath) {
|
|
|
5885
6207
|
}
|
|
5886
6208
|
function writeReport(text2, outputPath) {
|
|
5887
6209
|
if (outputPath !== void 0) {
|
|
5888
|
-
(0,
|
|
6210
|
+
(0, import_node_fs7.writeFileSync)(outputPath, text2);
|
|
5889
6211
|
} else {
|
|
5890
6212
|
process.stdout.write(text2 + "\n");
|
|
5891
6213
|
}
|
|
@@ -6110,6 +6432,13 @@ function createProgram() {
|
|
|
6110
6432
|
"--fail-on-missing-attachments",
|
|
6111
6433
|
"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",
|
|
6112
6434
|
false
|
|
6435
|
+
).option(
|
|
6436
|
+
"--upload-concurrency <n>",
|
|
6437
|
+
"File uploads in flight at once, 1 to 32 (default 4; PLUNE_UPLOAD_CONCURRENCY sets it too). Files are announced and confirmed up to 100 to a request; a 429 is retried PLUNE_UPLOAD_RETRIES times (default 6)",
|
|
6438
|
+
(v) => {
|
|
6439
|
+
if (!/^\d+$/.test(v) || Number(v) < 1 || Number(v) > 32) throw new import_commander.InvalidArgumentError("a whole number from 1 to 32.");
|
|
6440
|
+
return Number(v);
|
|
6441
|
+
}
|
|
6113
6442
|
).option("--fail-on-failures", FAIL_ON_FAILURES, false).action(
|
|
6114
6443
|
async (file, options, command) => {
|
|
6115
6444
|
const [
|
|
@@ -6135,6 +6464,7 @@ function createProgram() {
|
|
|
6135
6464
|
...format !== void 0 ? { format } : {},
|
|
6136
6465
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
6137
6466
|
...options.create ? { create: true } : {},
|
|
6467
|
+
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
6138
6468
|
...options.failOnFailures ? { failOnFailures: true } : {}
|
|
6139
6469
|
});
|
|
6140
6470
|
if (options.failOnMissingAttachments && imported.missingAttachments.length > 0) process.exitCode = 1;
|
|
@@ -6376,7 +6706,7 @@ function isMainModule() {
|
|
|
6376
6706
|
const entry = process.argv[1];
|
|
6377
6707
|
if (entry === void 0) return false;
|
|
6378
6708
|
try {
|
|
6379
|
-
return (0,
|
|
6709
|
+
return (0, import_node_fs7.realpathSync)(entry) === (0, import_node_fs7.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
6380
6710
|
} catch {
|
|
6381
6711
|
return false;
|
|
6382
6712
|
}
|