@plune-ai/cli 0.26.0 → 0.27.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 +11 -0
- package/dist/cli.cjs +71 -26
- package/dist/reporter-core.cjs +3 -0
- package/dist/reporter-core.d.cts +2 -0
- package/dist/reporter-core.d.ts +2 -0
- package/dist/reporter-core.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -392,6 +392,17 @@ The shard that is the Nth also prints `plune: run closed` just above that line,
|
|
|
392
392
|
and leaves the run open, as `PLUNE_SHARED_RUN` does: close it with `plune run finish`.
|
|
393
393
|
- With `--fail-on-failures`, the shard that closes the run gates on its summary; the others have no verdict yet.
|
|
394
394
|
|
|
395
|
+
### A time limit for the import
|
|
396
|
+
|
|
397
|
+
The whole `plune run import` stops after 600 seconds by default, so a platform that stops answering cannot hold a CI job until its own timeout kills it. Raise it for a very large report, or turn it off with `0`:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
PLUNE_IMPORT_TIMEOUT=1200 plune run import ./playwright.json # seconds, 0 to 86400; 0 is no limit
|
|
401
|
+
plune run import ./playwright.json --timeout 1200 # the same, as a flag (it wins)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
At the limit the command sends `terminate` for the run it opened (not for a run other jobs share), says in one line that the platform may have received part of the results, and exits 1. Import the report again afterwards.
|
|
405
|
+
|
|
395
406
|
### Many screenshots: upload concurrency and retries
|
|
396
407
|
|
|
397
408
|
Files go to Plune up to 100 to a request: `uploads-batch` announces them (with the SHA-256 of each, read as a stream,
|
package/dist/cli.cjs
CHANGED
|
@@ -2579,6 +2579,7 @@ function readEnv(env = process.env) {
|
|
|
2579
2579
|
const warnings = [];
|
|
2580
2580
|
const uploadConcurrency = bounded(env, "PLUNE_UPLOAD_CONCURRENCY", UPLOAD_CONCURRENCY, warnings);
|
|
2581
2581
|
const uploadRetries = bounded(env, "PLUNE_UPLOAD_RETRIES", UPLOAD_RETRIES, warnings);
|
|
2582
|
+
const importTimeout = bounded(env, "PLUNE_IMPORT_TIMEOUT", IMPORT_TIMEOUT, warnings);
|
|
2582
2583
|
const title = value(env, "PLUNE_RUN_TITLE");
|
|
2583
2584
|
const environment = value(env, "PLUNE_ENV");
|
|
2584
2585
|
const labels = list(env, "PLUNE_LABELS");
|
|
@@ -2610,6 +2611,7 @@ function readEnv(env = process.env) {
|
|
|
2610
2611
|
keepOpen: flag(env, "PLUNE_SHARED_RUN") || flag(env, "PLUNE_PROCEED") || shard !== void 0 || shardError !== void 0,
|
|
2611
2612
|
...shard !== void 0 ? { shard } : {},
|
|
2612
2613
|
...shardError !== void 0 ? { shardError } : {},
|
|
2614
|
+
...importTimeout !== void 0 ? { importTimeout } : {},
|
|
2613
2615
|
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0),
|
|
2614
2616
|
warnings
|
|
2615
2617
|
};
|
|
@@ -2637,7 +2639,7 @@ function ciCommit(reported, env = process.env) {
|
|
|
2637
2639
|
...branch !== void 0 ? { branch } : {}
|
|
2638
2640
|
};
|
|
2639
2641
|
}
|
|
2640
|
-
var path6, UNSUPPORTED_VARS, SHARDS_MAX, UPLOAD_CONCURRENCY, UPLOAD_RETRIES, PROVIDERS;
|
|
2642
|
+
var path6, UNSUPPORTED_VARS, SHARDS_MAX, UPLOAD_CONCURRENCY, UPLOAD_RETRIES, IMPORT_TIMEOUT, PROVIDERS;
|
|
2641
2643
|
var init_env2 = __esm({
|
|
2642
2644
|
"src/reporter-core/env.ts"() {
|
|
2643
2645
|
"use strict";
|
|
@@ -2647,6 +2649,7 @@ var init_env2 = __esm({
|
|
|
2647
2649
|
SHARDS_MAX = 1e3;
|
|
2648
2650
|
UPLOAD_CONCURRENCY = { min: 1, max: 32, default: 16 };
|
|
2649
2651
|
UPLOAD_RETRIES = { min: 0, max: 20, default: 6 };
|
|
2652
|
+
IMPORT_TIMEOUT = { min: 0, max: 86400, default: 600 };
|
|
2650
2653
|
PROVIDERS = [
|
|
2651
2654
|
{ is: "GITHUB_ACTIONS", sha: "GITHUB_SHA", branch: ["GITHUB_HEAD_REF", "GITHUB_REF_NAME"] },
|
|
2652
2655
|
{ is: "GITLAB_CI", sha: "CI_COMMIT_SHA", branch: ["CI_COMMIT_REF_NAME"] },
|
|
@@ -5365,8 +5368,24 @@ var init_importers = __esm({
|
|
|
5365
5368
|
// src/cli/commands/run-import.ts
|
|
5366
5369
|
var run_import_exports = {};
|
|
5367
5370
|
__export(run_import_exports, {
|
|
5371
|
+
ImportTimeoutError: () => ImportTimeoutError,
|
|
5368
5372
|
handleRunImport: () => handleRunImport
|
|
5369
5373
|
});
|
|
5374
|
+
async function timeoutMessage(limit, runId, ours, options) {
|
|
5375
|
+
const head = `plune: run import did not finish in ${limit} s (--timeout / PLUNE_IMPORT_TIMEOUT; 0 turns the limit off); the platform may have received part of the results`;
|
|
5376
|
+
if (runId === null) return `${head}; the run id is not known, so nothing was terminated - look for an open run with this key in Plune and terminate it there, then import again.`;
|
|
5377
|
+
if (!ours) return `${head}; run ${runId} is shared with other jobs and was left open - close it with "plune run finish ${runId}" once they are done, then import this report again.`;
|
|
5378
|
+
const wait = options.terminateWaitMs ?? 15e3;
|
|
5379
|
+
let timer;
|
|
5380
|
+
const gaveUp = new Promise((resolve10) => timer = setTimeout(() => resolve10("gaveup"), wait));
|
|
5381
|
+
const quiet = () => void 0;
|
|
5382
|
+
const closed = handleRunFinish(runId, { ...options, write: quiet, terminate: true, reason: `import timed out after ${limit} s` }).then(
|
|
5383
|
+
() => true,
|
|
5384
|
+
() => false
|
|
5385
|
+
);
|
|
5386
|
+
const done = await Promise.race([closed, gaveUp]).finally(() => clearTimeout(timer)) === true;
|
|
5387
|
+
return done ? `${head}; run ${runId} was terminated - import the report again.` : `${head}; run ${runId} could not be terminated from here - run "plune run finish ${runId} --terminate" (the platform also closes a silent run after 30 minutes), then import again.`;
|
|
5388
|
+
}
|
|
5370
5389
|
async function handleRunImport(options) {
|
|
5371
5390
|
const write = options.write ?? ((line) => void process.stdout.write(`${line}
|
|
5372
5391
|
`));
|
|
@@ -5393,31 +5412,47 @@ async function handleRunImport(options) {
|
|
|
5393
5412
|
const apiUrl = resolveApiUrl(options.apiUrl ?? env.config.apiUrl);
|
|
5394
5413
|
const token = options.token ?? env.config.token ?? loadToken() ?? "";
|
|
5395
5414
|
if (token === "") throw new NoTokenError();
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5415
|
+
let session;
|
|
5416
|
+
const send = async () => {
|
|
5417
|
+
session = await startRun(
|
|
5418
|
+
{
|
|
5419
|
+
apiUrl,
|
|
5420
|
+
token,
|
|
5421
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
5422
|
+
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
5423
|
+
...options.create === true ? { offerDiscovered: true } : {},
|
|
5424
|
+
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
5425
|
+
kind: "automated",
|
|
5426
|
+
// The CI run the report names, never this machine's: whoever imports may be another job (#790).
|
|
5427
|
+
// The commit and branch likewise, where the report names a commit; a report that names none —
|
|
5428
|
+
// JUnit's never does — takes this job's own, which is where the tests ran when import follows
|
|
5429
|
+
// them in one job (plune-ai/plune#927).
|
|
5430
|
+
meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {}, ...ciCommit(ci) },
|
|
5431
|
+
log: write
|
|
5432
|
+
},
|
|
5433
|
+
// The file is the complete list of what ran, so the platform can resolve every test in one
|
|
5434
|
+
// request and derive what never ran from the same list (AC-09/AC-10) — the lazy path would be
|
|
5435
|
+
// a lookup per result.
|
|
5436
|
+
results.map((r) => r.keys)
|
|
5437
|
+
);
|
|
5438
|
+
for (const result2 of results) await session.add(result2);
|
|
5439
|
+
return env.keepOpen ? session.leaveOpen({ holdShardLines: true }) : session.finish();
|
|
5440
|
+
};
|
|
5441
|
+
const limit = options.timeoutSec ?? env.importTimeout ?? IMPORT_TIMEOUT.default;
|
|
5442
|
+
const sent = send();
|
|
5418
5443
|
let frozen;
|
|
5419
|
-
if (
|
|
5420
|
-
else
|
|
5444
|
+
if (limit === 0) frozen = await sent;
|
|
5445
|
+
else {
|
|
5446
|
+
sent.catch(() => void 0);
|
|
5447
|
+
let timer;
|
|
5448
|
+
const late = new Promise((resolve10) => timer = setTimeout(() => resolve10("late"), limit * 1e3));
|
|
5449
|
+
const first = await Promise.race([sent, late]).finally(() => clearTimeout(timer));
|
|
5450
|
+
if (first === "late") {
|
|
5451
|
+
throw new ImportTimeoutError(await timeoutMessage(limit, session?.runId ?? null, !env.keepOpen, { ...options, apiUrl, token }));
|
|
5452
|
+
}
|
|
5453
|
+
frozen = first;
|
|
5454
|
+
}
|
|
5455
|
+
if (session === void 0) throw new RunCommandError("The import did not start.");
|
|
5421
5456
|
const stats = session.stats;
|
|
5422
5457
|
const result = {
|
|
5423
5458
|
runId: session.runId,
|
|
@@ -5512,7 +5547,7 @@ function describe(result, apiUrl) {
|
|
|
5512
5547
|
if (result.runId !== null) lines.push(runUrl2(apiUrl, result.runId));
|
|
5513
5548
|
return lines;
|
|
5514
5549
|
}
|
|
5515
|
-
var fs12, NEXT;
|
|
5550
|
+
var fs12, ImportTimeoutError, NEXT;
|
|
5516
5551
|
var init_run_import = __esm({
|
|
5517
5552
|
"src/cli/commands/run-import.ts"() {
|
|
5518
5553
|
"use strict";
|
|
@@ -5526,6 +5561,8 @@ var init_run_import = __esm({
|
|
|
5526
5561
|
init_credentials();
|
|
5527
5562
|
init_gate();
|
|
5528
5563
|
init_env2();
|
|
5564
|
+
ImportTimeoutError = class extends RunCommandError {
|
|
5565
|
+
};
|
|
5529
5566
|
NEXT = {
|
|
5530
5567
|
queue: "the review queue is full. Approve or reject what is waiting, then import this report again to offer the rest.",
|
|
5531
5568
|
cases: "the project is at its test case limit. Delete cases you no longer need, or ask an operator to raise the limit, then import this report again to offer the rest.",
|
|
@@ -6862,6 +6899,13 @@ function createProgram() {
|
|
|
6862
6899
|
if (!/^\d+$/.test(v) || Number(v) < 1 || Number(v) > 32) throw new import_commander.InvalidArgumentError("a whole number from 1 to 32.");
|
|
6863
6900
|
return Number(v);
|
|
6864
6901
|
}
|
|
6902
|
+
).option(
|
|
6903
|
+
"--timeout <seconds>",
|
|
6904
|
+
"Give up on the whole import after this many seconds, 0 for no limit (default 600; PLUNE_IMPORT_TIMEOUT sets it too). On a timeout the run this import opened is terminated, and the exit code is 1",
|
|
6905
|
+
(v) => {
|
|
6906
|
+
if (!/^\d+$/.test(v) || Number(v) > 86400) throw new import_commander.InvalidArgumentError("a whole number of seconds from 0 to 86400.");
|
|
6907
|
+
return Number(v);
|
|
6908
|
+
}
|
|
6865
6909
|
).option("--fail-on-failures", FAIL_ON_FAILURES, false).action(
|
|
6866
6910
|
async (file, options, command) => {
|
|
6867
6911
|
const [
|
|
@@ -6888,6 +6932,7 @@ function createProgram() {
|
|
|
6888
6932
|
...options.key !== void 0 ? { key: options.key } : {},
|
|
6889
6933
|
...options.create ? { create: true } : {},
|
|
6890
6934
|
...options.uploadConcurrency !== void 0 ? { uploadConcurrency: options.uploadConcurrency } : {},
|
|
6935
|
+
...options.timeout !== void 0 ? { timeoutSec: options.timeout } : {},
|
|
6891
6936
|
...options.failOnFailures ? { failOnFailures: true } : {}
|
|
6892
6937
|
});
|
|
6893
6938
|
if (options.failOnMissingAttachments && imported.missingAttachments.length > 0) process.exitCode = 1;
|
package/dist/reporter-core.cjs
CHANGED
|
@@ -316,6 +316,7 @@ function shardOf(env) {
|
|
|
316
316
|
}
|
|
317
317
|
var UPLOAD_CONCURRENCY = { min: 1, max: 32, default: 16 };
|
|
318
318
|
var UPLOAD_RETRIES = { min: 0, max: 20, default: 6 };
|
|
319
|
+
var IMPORT_TIMEOUT = { min: 0, max: 86400, default: 600 };
|
|
319
320
|
function value(env, name) {
|
|
320
321
|
const raw = env[name]?.trim();
|
|
321
322
|
return raw === void 0 || raw === "" ? void 0 : raw;
|
|
@@ -353,6 +354,7 @@ function readEnv(env = process.env) {
|
|
|
353
354
|
const warnings = [];
|
|
354
355
|
const uploadConcurrency = bounded(env, "PLUNE_UPLOAD_CONCURRENCY", UPLOAD_CONCURRENCY, warnings);
|
|
355
356
|
const uploadRetries = bounded(env, "PLUNE_UPLOAD_RETRIES", UPLOAD_RETRIES, warnings);
|
|
357
|
+
const importTimeout = bounded(env, "PLUNE_IMPORT_TIMEOUT", IMPORT_TIMEOUT, warnings);
|
|
356
358
|
const title = value(env, "PLUNE_RUN_TITLE");
|
|
357
359
|
const environment = value(env, "PLUNE_ENV");
|
|
358
360
|
const labels = list(env, "PLUNE_LABELS");
|
|
@@ -384,6 +386,7 @@ function readEnv(env = process.env) {
|
|
|
384
386
|
keepOpen: flag(env, "PLUNE_SHARED_RUN") || flag(env, "PLUNE_PROCEED") || shard !== void 0 || shardError !== void 0,
|
|
385
387
|
...shard !== void 0 ? { shard } : {},
|
|
386
388
|
...shardError !== void 0 ? { shardError } : {},
|
|
389
|
+
...importTimeout !== void 0 ? { importTimeout } : {},
|
|
387
390
|
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0),
|
|
388
391
|
warnings
|
|
389
392
|
};
|
package/dist/reporter-core.d.cts
CHANGED
|
@@ -445,6 +445,8 @@ interface EnvSettings {
|
|
|
445
445
|
shard?: ShardOf;
|
|
446
446
|
/** `PLUNE_SHARD` was set to something that is not `k/N`. A configuration error: the caller refuses to start. */
|
|
447
447
|
shardError?: string;
|
|
448
|
+
/** `PLUNE_IMPORT_TIMEOUT` in seconds, when set and usable. */
|
|
449
|
+
importTimeout?: number;
|
|
448
450
|
/** Variables that were set and have nowhere to land. The caller says so once. */
|
|
449
451
|
ignored: string[];
|
|
450
452
|
/** Variables that were set to something unusable and fell back to their default. The caller says so once. */
|
package/dist/reporter-core.d.ts
CHANGED
|
@@ -445,6 +445,8 @@ interface EnvSettings {
|
|
|
445
445
|
shard?: ShardOf;
|
|
446
446
|
/** `PLUNE_SHARD` was set to something that is not `k/N`. A configuration error: the caller refuses to start. */
|
|
447
447
|
shardError?: string;
|
|
448
|
+
/** `PLUNE_IMPORT_TIMEOUT` in seconds, when set and usable. */
|
|
449
|
+
importTimeout?: number;
|
|
448
450
|
/** Variables that were set and have nowhere to land. The caller says so once. */
|
|
449
451
|
ignored: string[];
|
|
450
452
|
/** Variables that were set to something unusable and fell back to their default. The caller says so once. */
|
package/dist/reporter-core.js
CHANGED
|
@@ -267,6 +267,7 @@ function shardOf(env) {
|
|
|
267
267
|
}
|
|
268
268
|
var UPLOAD_CONCURRENCY = { min: 1, max: 32, default: 16 };
|
|
269
269
|
var UPLOAD_RETRIES = { min: 0, max: 20, default: 6 };
|
|
270
|
+
var IMPORT_TIMEOUT = { min: 0, max: 86400, default: 600 };
|
|
270
271
|
function value(env, name) {
|
|
271
272
|
const raw = env[name]?.trim();
|
|
272
273
|
return raw === void 0 || raw === "" ? void 0 : raw;
|
|
@@ -304,6 +305,7 @@ function readEnv(env = process.env) {
|
|
|
304
305
|
const warnings = [];
|
|
305
306
|
const uploadConcurrency = bounded(env, "PLUNE_UPLOAD_CONCURRENCY", UPLOAD_CONCURRENCY, warnings);
|
|
306
307
|
const uploadRetries = bounded(env, "PLUNE_UPLOAD_RETRIES", UPLOAD_RETRIES, warnings);
|
|
308
|
+
const importTimeout = bounded(env, "PLUNE_IMPORT_TIMEOUT", IMPORT_TIMEOUT, warnings);
|
|
307
309
|
const title = value(env, "PLUNE_RUN_TITLE");
|
|
308
310
|
const environment = value(env, "PLUNE_ENV");
|
|
309
311
|
const labels = list(env, "PLUNE_LABELS");
|
|
@@ -335,6 +337,7 @@ function readEnv(env = process.env) {
|
|
|
335
337
|
keepOpen: flag(env, "PLUNE_SHARED_RUN") || flag(env, "PLUNE_PROCEED") || shard !== void 0 || shardError !== void 0,
|
|
336
338
|
...shard !== void 0 ? { shard } : {},
|
|
337
339
|
...shardError !== void 0 ? { shardError } : {},
|
|
340
|
+
...importTimeout !== void 0 ? { importTimeout } : {},
|
|
338
341
|
ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0),
|
|
339
342
|
warnings
|
|
340
343
|
};
|
package/package.json
CHANGED