@plune-ai/cli 0.29.0 → 0.30.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 +1 -1
- package/dist/cli.cjs +99 -24
- package/dist/index.cjs +89 -21
- package/dist/index.js +89 -21
- package/dist/reporter-core.cjs +9 -2
- package/dist/reporter-core.js +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ the last resort, used only when `PLUNE_TOKEN` is set nowhere else. Nothing write
|
|
|
121
121
|
| `plune init` | Scaffold `plune.yaml`, a sample dataset, and `.env.example`. Flags: `--yes` (non-interactive), `--force`. |
|
|
122
122
|
| `plune login` | Save a [Plune platform](https://plune.ai/platform) API token so `sync` and `ingest` can reach it. The token is **checked against the API before it is saved**, so a wrong one fails here rather than two commands later. Get one at `https://beta.plune.ai` → Account → API tokens. Flags: `--token <token>` (omit to paste it or pipe it via stdin), `--skip-verify` (save without checking, for offline setup). In CI, skip this step: every platform command reads `PLUNE_TOKEN` from the environment first, and the saved login only when it is not set. |
|
|
123
123
|
| `plune logout` | Remove the saved token. |
|
|
124
|
-
| `plune sync` | Upload the latest local run to the platform. Flags: `--file <path>` to send a specific run JSON, `--fail-on-failures` to read the run's summary back once after the upload and exit with [the gate](#a-release-gate---fail-on-failures)'s code. Each upload carries an `Idempotency-Key` (SHA-256 of the body), so sending the same run again returns the same run rather than creating a second one. |
|
|
124
|
+
| `plune sync` | Upload the latest local run to the platform. Flags: `--file <path>` to send a specific run JSON, `--fail-on-failures` to read the run's summary back once after the upload and exit with [the gate](#a-release-gate---fail-on-failures)'s code. Each upload carries an `Idempotency-Key` (SHA-256 of the body), so sending the same run again returns the same run rather than creating a second one. Against a platform with `POST /v1/jobs` the upload is also reported as a job (kind `cli-sync`) that ends failed with the error text if the sync fails; an older platform without the route is not told, and a job report never fails the sync. |
|
|
125
125
|
| `plune run import <file>` | Turn a **JUnit XML** or **Playwright JSON** report, or an **Allure `allure-results` folder**, into a run in Plune. Needs no provider key — nothing is generated. Flags: `--format junit\|playwright-json\|allure` (detected from the file when omitted; a folder is Allure), `--name <text>` to call the run something other than the [folder-based default](#what-the-run-is-called), `--key <externalKey>` to land several reports in one run (with `PLUNE_SHARED_RUN=1` — see below), `--create` to offer unmatched tests to the review queue, `--fail-on-missing-attachments` to exit 1 when a file a JUnit report names with `[[ATTACHMENT\|path]]`, or an Allure result names as a `source`, is not there (the results are sent either way), `--upload-concurrency <n>` for files uploaded at once ([1 to 32, default 16](#many-screenshots-upload-concurrency-and-retries)), `--fail-on-failures` to exit with [the gate](#a-release-gate---fail-on-failures)'s code on the summary the platform froze when this import finished the run. |
|
|
126
126
|
| `plune run start` | Open a platform run — or join the one already carrying `--key` — and print its id. Flags: `--key <externalKey>` (generated when absent), `--name <text>` to call the run something other than the [default](#what-the-run-is-called), `--json` for one machine-readable line. |
|
|
127
127
|
| `plune run finish <id>` | Close a platform run. This is what the reporter tells you to do for a run it had to leave open. Flags: `--terminate` to record it as cut short, `--reason <text>`, `--fail-on-failures` to exit with [the gate](#a-release-gate---fail-on-failures)'s code on the summary the platform froze. |
|
package/dist/cli.cjs
CHANGED
|
@@ -3230,7 +3230,7 @@ async function startRun(passed, declared2) {
|
|
|
3230
3230
|
...cfg.environment !== void 0 ? { environment: cfg.environment } : {},
|
|
3231
3231
|
...cfg.labels !== void 0 ? { labels: cfg.labels } : {}
|
|
3232
3232
|
};
|
|
3233
|
-
const
|
|
3233
|
+
const request = {
|
|
3234
3234
|
schemaVersion: 2,
|
|
3235
3235
|
kind: cfg.kind ?? "automated",
|
|
3236
3236
|
...externalKey !== null ? { externalKey } : {},
|
|
@@ -3241,7 +3241,14 @@ async function startRun(passed, declared2) {
|
|
|
3241
3241
|
// a person will miss, and a deployment older than the field must not be nagged on every run.
|
|
3242
3242
|
title: defaultRunTitle(),
|
|
3243
3243
|
...described
|
|
3244
|
-
}
|
|
3244
|
+
};
|
|
3245
|
+
let start = await client.post("/v1/runs", request);
|
|
3246
|
+
if (!start.ok && start.status === 413 && configuration !== void 0) {
|
|
3247
|
+
log(
|
|
3248
|
+
`plune: the platform refused the run's list of expected tests as too large (${start.detail || start.kind}) \u2014 starting without it: the results still land, but this run counts no tests that did not run, and claims no full run.`
|
|
3249
|
+
);
|
|
3250
|
+
start = await client.post("/v1/runs", { ...request, configuration: void 0 });
|
|
3251
|
+
}
|
|
3245
3252
|
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
3246
3253
|
runId = start.body.run.id;
|
|
3247
3254
|
joined = start.status === 200;
|
|
@@ -4367,30 +4374,46 @@ async function handleSync(deps = {}) {
|
|
|
4367
4374
|
const endpoint = `${apiUrl}/v1/runs`;
|
|
4368
4375
|
const payload = JSON.stringify(run);
|
|
4369
4376
|
const idempotencyKey = (0, import_node_crypto4.createHash)("sha256").update(payload).digest("hex");
|
|
4370
|
-
|
|
4377
|
+
const bearer = token;
|
|
4378
|
+
const job = new JobReporter(doFetch, apiUrl, bearer);
|
|
4379
|
+
await job.open(path9.basename(deps.file !== void 0 ? path9.resolve(cwd, deps.file) : cwd));
|
|
4371
4380
|
try {
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
},
|
|
4379
|
-
body: payload
|
|
4380
|
-
});
|
|
4381
|
-
} catch {
|
|
4382
|
-
throw new SyncNetworkError(apiUrl);
|
|
4381
|
+
const result = await upload();
|
|
4382
|
+
await job.finish({ ok: true });
|
|
4383
|
+
return result;
|
|
4384
|
+
} catch (err) {
|
|
4385
|
+
await job.finish({ ok: false, reason: err instanceof Error ? err.message : "Sync failed." });
|
|
4386
|
+
throw err;
|
|
4383
4387
|
}
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
+
async function upload() {
|
|
4389
|
+
let res;
|
|
4390
|
+
try {
|
|
4391
|
+
res = await doFetch(endpoint, {
|
|
4392
|
+
method: "POST",
|
|
4393
|
+
headers: {
|
|
4394
|
+
"content-type": "application/json",
|
|
4395
|
+
authorization: `Bearer ${bearer}`,
|
|
4396
|
+
"idempotency-key": idempotencyKey
|
|
4397
|
+
},
|
|
4398
|
+
body: payload
|
|
4399
|
+
});
|
|
4400
|
+
} catch {
|
|
4401
|
+
throw new SyncNetworkError(apiUrl);
|
|
4402
|
+
}
|
|
4403
|
+
if (!res.ok) {
|
|
4404
|
+
const detail = await safeErrorDetail(res);
|
|
4405
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
4406
|
+
throw new SyncHttpError(res.status, detail);
|
|
4407
|
+
}
|
|
4408
|
+
const body = await res.json().catch(() => ({}));
|
|
4409
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
4410
|
+
const result = { id, url: `${endpoint}/${id}` };
|
|
4411
|
+
await job.report(deps.failOnFailures === true ? 80 : 95, id || void 0);
|
|
4412
|
+
if (deps.failOnFailures === true) {
|
|
4413
|
+
result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, bearer));
|
|
4414
|
+
}
|
|
4415
|
+
return result;
|
|
4388
4416
|
}
|
|
4389
|
-
const body = await res.json().catch(() => ({}));
|
|
4390
|
-
const id = typeof body.id === "string" ? body.id : "";
|
|
4391
|
-
const result = { id, url: `${endpoint}/${id}` };
|
|
4392
|
-
if (deps.failOnFailures === true) result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, token));
|
|
4393
|
-
return result;
|
|
4394
4417
|
}
|
|
4395
4418
|
async function readVerdict(doFetch, url, token) {
|
|
4396
4419
|
try {
|
|
@@ -4412,7 +4435,7 @@ function reportSyncFailure(err, write) {
|
|
|
4412
4435
|
}
|
|
4413
4436
|
return null;
|
|
4414
4437
|
}
|
|
4415
|
-
var import_node_crypto4, fs8, path9, NotLoggedInError, TokenRejectedError, SyncFileError, SyncNetworkError, SyncHttpError;
|
|
4438
|
+
var import_node_crypto4, fs8, path9, NotLoggedInError, TokenRejectedError, SyncFileError, SyncNetworkError, SyncHttpError, JOB_KIND, JOB_TIMEOUT_MS, JobReporter;
|
|
4416
4439
|
var init_sync = __esm({
|
|
4417
4440
|
"src/cli/commands/sync.ts"() {
|
|
4418
4441
|
"use strict";
|
|
@@ -4460,6 +4483,58 @@ var init_sync = __esm({
|
|
|
4460
4483
|
}
|
|
4461
4484
|
status;
|
|
4462
4485
|
};
|
|
4486
|
+
JOB_KIND = "cli-sync";
|
|
4487
|
+
JOB_TIMEOUT_MS = 5e3;
|
|
4488
|
+
JobReporter = class {
|
|
4489
|
+
constructor(doFetch, base, token) {
|
|
4490
|
+
this.doFetch = doFetch;
|
|
4491
|
+
this.base = base;
|
|
4492
|
+
this.token = token;
|
|
4493
|
+
}
|
|
4494
|
+
doFetch;
|
|
4495
|
+
base;
|
|
4496
|
+
token;
|
|
4497
|
+
id;
|
|
4498
|
+
async send(method, url, body) {
|
|
4499
|
+
try {
|
|
4500
|
+
const res = await this.doFetch(url, {
|
|
4501
|
+
method,
|
|
4502
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${this.token}` },
|
|
4503
|
+
body: JSON.stringify(body),
|
|
4504
|
+
signal: AbortSignal.timeout(JOB_TIMEOUT_MS)
|
|
4505
|
+
});
|
|
4506
|
+
const json = await res.json().catch(() => void 0);
|
|
4507
|
+
return res.ok ? json ?? {} : void 0;
|
|
4508
|
+
} catch {
|
|
4509
|
+
return void 0;
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
async open(subjectName) {
|
|
4513
|
+
const body = await this.send("POST", `${this.base}/v1/jobs`, {
|
|
4514
|
+
kind: JOB_KIND,
|
|
4515
|
+
subjectName,
|
|
4516
|
+
progress: 0
|
|
4517
|
+
});
|
|
4518
|
+
if (typeof body?.id === "string") this.id = body.id;
|
|
4519
|
+
}
|
|
4520
|
+
async report(progress, cursor) {
|
|
4521
|
+
if (this.id === void 0) return;
|
|
4522
|
+
await this.send("PATCH", `${this.base}/v1/jobs/${encodeURIComponent(this.id)}`, {
|
|
4523
|
+
progress,
|
|
4524
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
async finish(outcome) {
|
|
4528
|
+
if (this.id === void 0) return;
|
|
4529
|
+
const id = this.id;
|
|
4530
|
+
this.id = void 0;
|
|
4531
|
+
await this.send(
|
|
4532
|
+
"PATCH",
|
|
4533
|
+
`${this.base}/v1/jobs/${encodeURIComponent(id)}`,
|
|
4534
|
+
outcome.ok ? { status: "succeeded", progress: 100 } : { status: "failed", reason: outcome.reason }
|
|
4535
|
+
);
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4463
4538
|
}
|
|
4464
4539
|
});
|
|
4465
4540
|
|
package/dist/index.cjs
CHANGED
|
@@ -1892,6 +1892,58 @@ var SyncHttpError = class extends Error {
|
|
|
1892
1892
|
}
|
|
1893
1893
|
status;
|
|
1894
1894
|
};
|
|
1895
|
+
var JOB_KIND = "cli-sync";
|
|
1896
|
+
var JOB_TIMEOUT_MS = 5e3;
|
|
1897
|
+
var JobReporter = class {
|
|
1898
|
+
constructor(doFetch, base, token) {
|
|
1899
|
+
this.doFetch = doFetch;
|
|
1900
|
+
this.base = base;
|
|
1901
|
+
this.token = token;
|
|
1902
|
+
}
|
|
1903
|
+
doFetch;
|
|
1904
|
+
base;
|
|
1905
|
+
token;
|
|
1906
|
+
id;
|
|
1907
|
+
async send(method, url, body) {
|
|
1908
|
+
try {
|
|
1909
|
+
const res = await this.doFetch(url, {
|
|
1910
|
+
method,
|
|
1911
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${this.token}` },
|
|
1912
|
+
body: JSON.stringify(body),
|
|
1913
|
+
signal: AbortSignal.timeout(JOB_TIMEOUT_MS)
|
|
1914
|
+
});
|
|
1915
|
+
const json = await res.json().catch(() => void 0);
|
|
1916
|
+
return res.ok ? json ?? {} : void 0;
|
|
1917
|
+
} catch {
|
|
1918
|
+
return void 0;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
async open(subjectName) {
|
|
1922
|
+
const body = await this.send("POST", `${this.base}/v1/jobs`, {
|
|
1923
|
+
kind: JOB_KIND,
|
|
1924
|
+
subjectName,
|
|
1925
|
+
progress: 0
|
|
1926
|
+
});
|
|
1927
|
+
if (typeof body?.id === "string") this.id = body.id;
|
|
1928
|
+
}
|
|
1929
|
+
async report(progress, cursor) {
|
|
1930
|
+
if (this.id === void 0) return;
|
|
1931
|
+
await this.send("PATCH", `${this.base}/v1/jobs/${encodeURIComponent(this.id)}`, {
|
|
1932
|
+
progress,
|
|
1933
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
async finish(outcome) {
|
|
1937
|
+
if (this.id === void 0) return;
|
|
1938
|
+
const id = this.id;
|
|
1939
|
+
this.id = void 0;
|
|
1940
|
+
await this.send(
|
|
1941
|
+
"PATCH",
|
|
1942
|
+
`${this.base}/v1/jobs/${encodeURIComponent(id)}`,
|
|
1943
|
+
outcome.ok ? { status: "succeeded", progress: 100 } : { status: "failed", reason: outcome.reason }
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1895
1947
|
function readRun(cwd, file) {
|
|
1896
1948
|
const target = file !== void 0 ? path5.resolve(cwd, file) : path5.join(cwd, ".plune", "last-run.json");
|
|
1897
1949
|
let raw;
|
|
@@ -1932,30 +1984,46 @@ async function handleSync(deps = {}) {
|
|
|
1932
1984
|
const endpoint = `${apiUrl}/v1/runs`;
|
|
1933
1985
|
const payload = JSON.stringify(run);
|
|
1934
1986
|
const idempotencyKey = (0, import_node_crypto2.createHash)("sha256").update(payload).digest("hex");
|
|
1935
|
-
|
|
1987
|
+
const bearer = token;
|
|
1988
|
+
const job = new JobReporter(doFetch, apiUrl, bearer);
|
|
1989
|
+
await job.open(path5.basename(deps.file !== void 0 ? path5.resolve(cwd, deps.file) : cwd));
|
|
1936
1990
|
try {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
},
|
|
1944
|
-
body: payload
|
|
1945
|
-
});
|
|
1946
|
-
} catch {
|
|
1947
|
-
throw new SyncNetworkError(apiUrl);
|
|
1991
|
+
const result = await upload();
|
|
1992
|
+
await job.finish({ ok: true });
|
|
1993
|
+
return result;
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
await job.finish({ ok: false, reason: err instanceof Error ? err.message : "Sync failed." });
|
|
1996
|
+
throw err;
|
|
1948
1997
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1998
|
+
async function upload() {
|
|
1999
|
+
let res;
|
|
2000
|
+
try {
|
|
2001
|
+
res = await doFetch(endpoint, {
|
|
2002
|
+
method: "POST",
|
|
2003
|
+
headers: {
|
|
2004
|
+
"content-type": "application/json",
|
|
2005
|
+
authorization: `Bearer ${bearer}`,
|
|
2006
|
+
"idempotency-key": idempotencyKey
|
|
2007
|
+
},
|
|
2008
|
+
body: payload
|
|
2009
|
+
});
|
|
2010
|
+
} catch {
|
|
2011
|
+
throw new SyncNetworkError(apiUrl);
|
|
2012
|
+
}
|
|
2013
|
+
if (!res.ok) {
|
|
2014
|
+
const detail = await safeErrorDetail(res);
|
|
2015
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
2016
|
+
throw new SyncHttpError(res.status, detail);
|
|
2017
|
+
}
|
|
2018
|
+
const body = await res.json().catch(() => ({}));
|
|
2019
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
2020
|
+
const result = { id, url: `${endpoint}/${id}` };
|
|
2021
|
+
await job.report(deps.failOnFailures === true ? 80 : 95, id || void 0);
|
|
2022
|
+
if (deps.failOnFailures === true) {
|
|
2023
|
+
result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, bearer));
|
|
2024
|
+
}
|
|
2025
|
+
return result;
|
|
1953
2026
|
}
|
|
1954
|
-
const body = await res.json().catch(() => ({}));
|
|
1955
|
-
const id = typeof body.id === "string" ? body.id : "";
|
|
1956
|
-
const result = { id, url: `${endpoint}/${id}` };
|
|
1957
|
-
if (deps.failOnFailures === true) result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, token));
|
|
1958
|
-
return result;
|
|
1959
2027
|
}
|
|
1960
2028
|
async function readVerdict(doFetch, url, token) {
|
|
1961
2029
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1832,6 +1832,58 @@ var SyncHttpError = class extends Error {
|
|
|
1832
1832
|
}
|
|
1833
1833
|
status;
|
|
1834
1834
|
};
|
|
1835
|
+
var JOB_KIND = "cli-sync";
|
|
1836
|
+
var JOB_TIMEOUT_MS = 5e3;
|
|
1837
|
+
var JobReporter = class {
|
|
1838
|
+
constructor(doFetch, base, token) {
|
|
1839
|
+
this.doFetch = doFetch;
|
|
1840
|
+
this.base = base;
|
|
1841
|
+
this.token = token;
|
|
1842
|
+
}
|
|
1843
|
+
doFetch;
|
|
1844
|
+
base;
|
|
1845
|
+
token;
|
|
1846
|
+
id;
|
|
1847
|
+
async send(method, url, body) {
|
|
1848
|
+
try {
|
|
1849
|
+
const res = await this.doFetch(url, {
|
|
1850
|
+
method,
|
|
1851
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${this.token}` },
|
|
1852
|
+
body: JSON.stringify(body),
|
|
1853
|
+
signal: AbortSignal.timeout(JOB_TIMEOUT_MS)
|
|
1854
|
+
});
|
|
1855
|
+
const json = await res.json().catch(() => void 0);
|
|
1856
|
+
return res.ok ? json ?? {} : void 0;
|
|
1857
|
+
} catch {
|
|
1858
|
+
return void 0;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async open(subjectName) {
|
|
1862
|
+
const body = await this.send("POST", `${this.base}/v1/jobs`, {
|
|
1863
|
+
kind: JOB_KIND,
|
|
1864
|
+
subjectName,
|
|
1865
|
+
progress: 0
|
|
1866
|
+
});
|
|
1867
|
+
if (typeof body?.id === "string") this.id = body.id;
|
|
1868
|
+
}
|
|
1869
|
+
async report(progress, cursor) {
|
|
1870
|
+
if (this.id === void 0) return;
|
|
1871
|
+
await this.send("PATCH", `${this.base}/v1/jobs/${encodeURIComponent(this.id)}`, {
|
|
1872
|
+
progress,
|
|
1873
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
async finish(outcome) {
|
|
1877
|
+
if (this.id === void 0) return;
|
|
1878
|
+
const id = this.id;
|
|
1879
|
+
this.id = void 0;
|
|
1880
|
+
await this.send(
|
|
1881
|
+
"PATCH",
|
|
1882
|
+
`${this.base}/v1/jobs/${encodeURIComponent(id)}`,
|
|
1883
|
+
outcome.ok ? { status: "succeeded", progress: 100 } : { status: "failed", reason: outcome.reason }
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1835
1887
|
function readRun(cwd, file) {
|
|
1836
1888
|
const target = file !== void 0 ? path5.resolve(cwd, file) : path5.join(cwd, ".plune", "last-run.json");
|
|
1837
1889
|
let raw;
|
|
@@ -1872,30 +1924,46 @@ async function handleSync(deps = {}) {
|
|
|
1872
1924
|
const endpoint = `${apiUrl}/v1/runs`;
|
|
1873
1925
|
const payload = JSON.stringify(run);
|
|
1874
1926
|
const idempotencyKey = createHash2("sha256").update(payload).digest("hex");
|
|
1875
|
-
|
|
1927
|
+
const bearer = token;
|
|
1928
|
+
const job = new JobReporter(doFetch, apiUrl, bearer);
|
|
1929
|
+
await job.open(path5.basename(deps.file !== void 0 ? path5.resolve(cwd, deps.file) : cwd));
|
|
1876
1930
|
try {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
},
|
|
1884
|
-
body: payload
|
|
1885
|
-
});
|
|
1886
|
-
} catch {
|
|
1887
|
-
throw new SyncNetworkError(apiUrl);
|
|
1931
|
+
const result = await upload();
|
|
1932
|
+
await job.finish({ ok: true });
|
|
1933
|
+
return result;
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
await job.finish({ ok: false, reason: err instanceof Error ? err.message : "Sync failed." });
|
|
1936
|
+
throw err;
|
|
1888
1937
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1938
|
+
async function upload() {
|
|
1939
|
+
let res;
|
|
1940
|
+
try {
|
|
1941
|
+
res = await doFetch(endpoint, {
|
|
1942
|
+
method: "POST",
|
|
1943
|
+
headers: {
|
|
1944
|
+
"content-type": "application/json",
|
|
1945
|
+
authorization: `Bearer ${bearer}`,
|
|
1946
|
+
"idempotency-key": idempotencyKey
|
|
1947
|
+
},
|
|
1948
|
+
body: payload
|
|
1949
|
+
});
|
|
1950
|
+
} catch {
|
|
1951
|
+
throw new SyncNetworkError(apiUrl);
|
|
1952
|
+
}
|
|
1953
|
+
if (!res.ok) {
|
|
1954
|
+
const detail = await safeErrorDetail(res);
|
|
1955
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
1956
|
+
throw new SyncHttpError(res.status, detail);
|
|
1957
|
+
}
|
|
1958
|
+
const body = await res.json().catch(() => ({}));
|
|
1959
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
1960
|
+
const result = { id, url: `${endpoint}/${id}` };
|
|
1961
|
+
await job.report(deps.failOnFailures === true ? 80 : 95, id || void 0);
|
|
1962
|
+
if (deps.failOnFailures === true) {
|
|
1963
|
+
result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, bearer));
|
|
1964
|
+
}
|
|
1965
|
+
return result;
|
|
1893
1966
|
}
|
|
1894
|
-
const body = await res.json().catch(() => ({}));
|
|
1895
|
-
const id = typeof body.id === "string" ? body.id : "";
|
|
1896
|
-
const result = { id, url: `${endpoint}/${id}` };
|
|
1897
|
-
if (deps.failOnFailures === true) result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, token));
|
|
1898
|
-
return result;
|
|
1899
1967
|
}
|
|
1900
1968
|
async function readVerdict(doFetch, url, token) {
|
|
1901
1969
|
try {
|
package/dist/reporter-core.cjs
CHANGED
|
@@ -875,7 +875,7 @@ async function startRun(passed, declared) {
|
|
|
875
875
|
...cfg.environment !== void 0 ? { environment: cfg.environment } : {},
|
|
876
876
|
...cfg.labels !== void 0 ? { labels: cfg.labels } : {}
|
|
877
877
|
};
|
|
878
|
-
const
|
|
878
|
+
const request = {
|
|
879
879
|
schemaVersion: 2,
|
|
880
880
|
kind: cfg.kind ?? "automated",
|
|
881
881
|
...externalKey !== null ? { externalKey } : {},
|
|
@@ -886,7 +886,14 @@ async function startRun(passed, declared) {
|
|
|
886
886
|
// a person will miss, and a deployment older than the field must not be nagged on every run.
|
|
887
887
|
title: defaultRunTitle(),
|
|
888
888
|
...described
|
|
889
|
-
}
|
|
889
|
+
};
|
|
890
|
+
let start = await client.post("/v1/runs", request);
|
|
891
|
+
if (!start.ok && start.status === 413 && configuration !== void 0) {
|
|
892
|
+
log(
|
|
893
|
+
`plune: the platform refused the run's list of expected tests as too large (${start.detail || start.kind}) \u2014 starting without it: the results still land, but this run counts no tests that did not run, and claims no full run.`
|
|
894
|
+
);
|
|
895
|
+
start = await client.post("/v1/runs", { ...request, configuration: void 0 });
|
|
896
|
+
}
|
|
890
897
|
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
891
898
|
runId = start.body.run.id;
|
|
892
899
|
joined = start.status === 200;
|
package/dist/reporter-core.js
CHANGED
|
@@ -825,7 +825,7 @@ async function startRun(passed, declared) {
|
|
|
825
825
|
...cfg.environment !== void 0 ? { environment: cfg.environment } : {},
|
|
826
826
|
...cfg.labels !== void 0 ? { labels: cfg.labels } : {}
|
|
827
827
|
};
|
|
828
|
-
const
|
|
828
|
+
const request = {
|
|
829
829
|
schemaVersion: 2,
|
|
830
830
|
kind: cfg.kind ?? "automated",
|
|
831
831
|
...externalKey !== null ? { externalKey } : {},
|
|
@@ -836,7 +836,14 @@ async function startRun(passed, declared) {
|
|
|
836
836
|
// a person will miss, and a deployment older than the field must not be nagged on every run.
|
|
837
837
|
title: defaultRunTitle(),
|
|
838
838
|
...described
|
|
839
|
-
}
|
|
839
|
+
};
|
|
840
|
+
let start = await client.post("/v1/runs", request);
|
|
841
|
+
if (!start.ok && start.status === 413 && configuration !== void 0) {
|
|
842
|
+
log(
|
|
843
|
+
`plune: the platform refused the run's list of expected tests as too large (${start.detail || start.kind}) \u2014 starting without it: the results still land, but this run counts no tests that did not run, and claims no full run.`
|
|
844
|
+
);
|
|
845
|
+
start = await client.post("/v1/runs", { ...request, configuration: void 0 });
|
|
846
|
+
}
|
|
840
847
|
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
841
848
|
runId = start.body.run.id;
|
|
842
849
|
joined = start.status === 200;
|
package/package.json
CHANGED