@plune-ai/cli 0.13.0 → 0.14.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/dist/cli.cjs +290 -60
- package/dist/reporter-core.cjs +222 -25
- package/dist/reporter-core.d.cts +102 -11
- package/dist/reporter-core.d.ts +102 -11
- package/dist/reporter-core.js +217 -24
- package/package.json +1 -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((resolve8) => setTimeout(resolve8, ms));
|
|
516
516
|
}
|
|
517
517
|
});
|
|
518
518
|
|
|
@@ -2186,6 +2186,35 @@ var init_credentials = __esm({
|
|
|
2186
2186
|
});
|
|
2187
2187
|
|
|
2188
2188
|
// src/reporter-core/client.ts
|
|
2189
|
+
function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
2190
|
+
return new Promise((resolve8, reject) => {
|
|
2191
|
+
const target = new URL(url);
|
|
2192
|
+
const send = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2193
|
+
const req = send(target, { method: init.method, headers: init.headers }, (res) => {
|
|
2194
|
+
const chunks = [];
|
|
2195
|
+
res.on("data", (chunk2) => chunks.push(chunk2));
|
|
2196
|
+
res.on("error", reject);
|
|
2197
|
+
res.on("end", () => {
|
|
2198
|
+
const status = res.statusCode ?? 0;
|
|
2199
|
+
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
2200
|
+
resolve8({
|
|
2201
|
+
ok: status >= 200 && status <= 299,
|
|
2202
|
+
status,
|
|
2203
|
+
headers: {
|
|
2204
|
+
get: (name) => {
|
|
2205
|
+
const value2 = res.headers[name.toLowerCase()];
|
|
2206
|
+
return value2 === void 0 ? null : Array.isArray(value2) ? value2.join(", ") : value2;
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
json: async () => JSON.parse(text2)
|
|
2210
|
+
});
|
|
2211
|
+
});
|
|
2212
|
+
});
|
|
2213
|
+
req.setTimeout(idleMs, () => req.destroy(new Error(`no answer from ${target.origin} in ${idleMs} ms`)));
|
|
2214
|
+
req.on("error", reject);
|
|
2215
|
+
req.end(init.body);
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2189
2218
|
async function detailOf(res) {
|
|
2190
2219
|
try {
|
|
2191
2220
|
const body = await res.json();
|
|
@@ -2208,7 +2237,7 @@ function classify3(status) {
|
|
|
2208
2237
|
return "other";
|
|
2209
2238
|
}
|
|
2210
2239
|
function createClient(opts) {
|
|
2211
|
-
const doFetch = opts.fetchImpl ??
|
|
2240
|
+
const doFetch = opts.fetchImpl ?? nodeTransport;
|
|
2212
2241
|
const wait = opts.wait ?? sleep;
|
|
2213
2242
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
2214
2243
|
const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
|
|
@@ -2236,7 +2265,11 @@ function createClient(opts) {
|
|
|
2236
2265
|
}
|
|
2237
2266
|
if (res.ok) {
|
|
2238
2267
|
if (res.status === 204) return { ok: true, status: res.status, body: void 0 };
|
|
2239
|
-
|
|
2268
|
+
try {
|
|
2269
|
+
return { ok: true, status: res.status, body: await res.json() };
|
|
2270
|
+
} catch {
|
|
2271
|
+
return { ok: false, kind: "other", status: res.status, detail: "the answer was not JSON" };
|
|
2272
|
+
}
|
|
2240
2273
|
}
|
|
2241
2274
|
const detail = scrub(await detailOf(res));
|
|
2242
2275
|
const kind = classify3(res.status);
|
|
@@ -2253,12 +2286,15 @@ function createClient(opts) {
|
|
|
2253
2286
|
del: (path14) => send("DELETE", path14)
|
|
2254
2287
|
};
|
|
2255
2288
|
}
|
|
2256
|
-
var RETRY_DELAYS_MS, sleep;
|
|
2289
|
+
var import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, sleep;
|
|
2257
2290
|
var init_client = __esm({
|
|
2258
2291
|
"src/reporter-core/client.ts"() {
|
|
2259
2292
|
"use strict";
|
|
2260
2293
|
init_cjs_shims();
|
|
2294
|
+
import_node_http = require("http");
|
|
2295
|
+
import_node_https = require("https");
|
|
2261
2296
|
RETRY_DELAYS_MS = [300, 900];
|
|
2297
|
+
IDLE_MS = 3e5;
|
|
2262
2298
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2263
2299
|
}
|
|
2264
2300
|
});
|
|
@@ -2367,6 +2403,23 @@ function boundKey(key) {
|
|
|
2367
2403
|
function boundTitle(title) {
|
|
2368
2404
|
return title.length <= TITLE_MAX ? title : `${title.slice(0, TITLE_MAX - 1)}\u2026`;
|
|
2369
2405
|
}
|
|
2406
|
+
function packed(results) {
|
|
2407
|
+
const out = [];
|
|
2408
|
+
let part = [];
|
|
2409
|
+
let bytes = ENVELOPE_BYTES;
|
|
2410
|
+
for (const result of results) {
|
|
2411
|
+
const size = Buffer.byteLength(JSON.stringify(result));
|
|
2412
|
+
if (part.length > 0 && bytes + 1 + size > BATCH_BYTES) {
|
|
2413
|
+
out.push(part);
|
|
2414
|
+
part = [];
|
|
2415
|
+
bytes = ENVELOPE_BYTES;
|
|
2416
|
+
}
|
|
2417
|
+
bytes += (part.length > 0 ? 1 : 0) + size;
|
|
2418
|
+
part.push(result);
|
|
2419
|
+
}
|
|
2420
|
+
if (part.length > 0) out.push(part);
|
|
2421
|
+
return out;
|
|
2422
|
+
}
|
|
2370
2423
|
function chunk(items, size) {
|
|
2371
2424
|
const out = [];
|
|
2372
2425
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
@@ -2568,27 +2621,35 @@ async function startRun(passed, declared) {
|
|
|
2568
2621
|
progress();
|
|
2569
2622
|
return;
|
|
2570
2623
|
}
|
|
2571
|
-
const
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2624
|
+
for (const part of packed(submissions)) {
|
|
2625
|
+
if (offline) {
|
|
2626
|
+
defer(part);
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
const out = await client.post(`/v1/runs/${runId}/results`, {
|
|
2630
|
+
results: part
|
|
2631
|
+
});
|
|
2632
|
+
const counts = out.ok ? out.body?.counts : void 0;
|
|
2633
|
+
if (counts !== void 0) {
|
|
2634
|
+
stats.accepted += counts.accepted ?? 0;
|
|
2635
|
+
stats.duplicate += counts.duplicate ?? 0;
|
|
2636
|
+
stats.conflict += counts.conflict ?? 0;
|
|
2637
|
+
stats.rejected += counts.rejected ?? 0;
|
|
2638
|
+
progress();
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
if (out.ok) {
|
|
2642
|
+
log("plune: could not send results (the answer carried no counts).");
|
|
2643
|
+
} else if (out.kind === "auth") {
|
|
2644
|
+
log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
|
|
2645
|
+
offline = true;
|
|
2646
|
+
} else if (out.kind === "conflict") {
|
|
2647
|
+
log(`plune: this run is closed \u2014 ${out.detail}`);
|
|
2648
|
+
} else {
|
|
2649
|
+
log(`plune: could not send results (${out.detail || out.kind}).`);
|
|
2650
|
+
}
|
|
2651
|
+
defer(part);
|
|
2590
2652
|
}
|
|
2591
|
-
defer(submissions);
|
|
2592
2653
|
}
|
|
2593
2654
|
function progress() {
|
|
2594
2655
|
const total = expected?.length ?? 0;
|
|
@@ -2604,9 +2665,12 @@ async function startRun(passed, declared) {
|
|
|
2604
2665
|
if (stats.unresolved > 0) parts.push(`${stats.unresolved} with no matching test case`);
|
|
2605
2666
|
if (stats.offered > 0) parts.push(`${stats.offered} offered for review`);
|
|
2606
2667
|
if (stats.created > 0) parts.push(`${stats.created} added as cases (trusted source)`);
|
|
2607
|
-
if (stats.deferred > 0) parts.push(`${stats.deferred} written to ${fallbackPath}`);
|
|
2668
|
+
if (stats.deferred > 0) parts.push(`${stats.deferred} not delivered \u2014 written to ${fallbackPath}`);
|
|
2608
2669
|
if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
|
|
2609
2670
|
log(`plune: ${parts.join(" \xB7 ")}`);
|
|
2671
|
+
if (stats.deferred > 0 && process.env["GITHUB_ACTIONS"] === "true") {
|
|
2672
|
+
log(`::warning::${stats.deferred} result(s) were not delivered to Plune \u2014 see the reporting step's log.`);
|
|
2673
|
+
}
|
|
2610
2674
|
}
|
|
2611
2675
|
return {
|
|
2612
2676
|
get runId() {
|
|
@@ -2652,7 +2716,7 @@ async function startRun(passed, declared) {
|
|
|
2652
2716
|
}
|
|
2653
2717
|
};
|
|
2654
2718
|
}
|
|
2655
|
-
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX;
|
|
2719
|
+
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX, BATCH_BYTES, ENVELOPE_BYTES;
|
|
2656
2720
|
var init_session = __esm({
|
|
2657
2721
|
"src/reporter-core/session.ts"() {
|
|
2658
2722
|
"use strict";
|
|
@@ -2670,6 +2734,8 @@ var init_session = __esm({
|
|
|
2670
2734
|
EXPECTED_MAX = 1e4;
|
|
2671
2735
|
KEY_MAX = 1024;
|
|
2672
2736
|
TITLE_MAX = 300;
|
|
2737
|
+
BATCH_BYTES = 8 * 1024 * 1024;
|
|
2738
|
+
ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
|
|
2673
2739
|
}
|
|
2674
2740
|
});
|
|
2675
2741
|
|
|
@@ -2765,14 +2831,14 @@ async function handleRunExec(options) {
|
|
|
2765
2831
|
if (command === void 0) throw new RunCommandError("Nothing to run \u2014 pass a command after `--`.");
|
|
2766
2832
|
const run = await handleRunStart(options);
|
|
2767
2833
|
const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
|
|
2768
|
-
const code = await new Promise((
|
|
2834
|
+
const code = await new Promise((resolve8) => {
|
|
2769
2835
|
const child = spawnImpl(command, args, {
|
|
2770
2836
|
stdio: "inherit",
|
|
2771
2837
|
shell: process.platform === "win32",
|
|
2772
2838
|
env: { ...process.env, PLUNE_RUN: run.externalKey, PLUNE_PROCEED: "1" }
|
|
2773
2839
|
});
|
|
2774
|
-
child.on("close", (exitCode) =>
|
|
2775
|
-
child.on("error", () =>
|
|
2840
|
+
child.on("close", (exitCode) => resolve8(exitCode ?? 1));
|
|
2841
|
+
child.on("error", () => resolve8(1));
|
|
2776
2842
|
});
|
|
2777
2843
|
await handleRunFinish(run.id, options);
|
|
2778
2844
|
return code;
|
|
@@ -3104,21 +3170,170 @@ var init_junit = __esm({
|
|
|
3104
3170
|
}
|
|
3105
3171
|
});
|
|
3106
3172
|
|
|
3173
|
+
// src/reporter-core/failure-detail.ts
|
|
3174
|
+
function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3175
|
+
const error = chosen(attempt);
|
|
3176
|
+
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
3177
|
+
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
3178
|
+
const steps = chainOf(attempt.steps);
|
|
3179
|
+
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
3180
|
+
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 !== "");
|
|
3181
|
+
const ciUrl = webLink(attempt.buildHref);
|
|
3182
|
+
const failure = {
|
|
3183
|
+
...headline !== void 0 ? { headline } : {},
|
|
3184
|
+
...steps.length > 0 ? { steps } : {},
|
|
3185
|
+
...location !== void 0 ? { location } : {},
|
|
3186
|
+
...artifacts.length > 0 ? { artifacts } : {},
|
|
3187
|
+
...ciUrl !== void 0 ? { ciUrl } : {}
|
|
3188
|
+
};
|
|
3189
|
+
return Object.keys(failure).length === 0 ? void 0 : failure;
|
|
3190
|
+
}
|
|
3191
|
+
function repoRootOf(dir, stopAt) {
|
|
3192
|
+
for (let here = (0, import_node_path2.resolve)(dir); ; here = (0, import_node_path2.dirname)(here)) {
|
|
3193
|
+
if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(here, ".git"))) return here;
|
|
3194
|
+
if (here === stopAt || (0, import_node_path2.dirname)(here) === here) return void 0;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3198
|
+
const text2 = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
3199
|
+
return cut(machineless(text2, attempt.repoRoot, home));
|
|
3200
|
+
}
|
|
3201
|
+
function machineless(raw, repoRoot, home) {
|
|
3202
|
+
let text2 = (0, import_node_util.stripVTControlCharacters)(raw).replaceAll("\x1B", "");
|
|
3203
|
+
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
3204
|
+
if (root !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
3205
|
+
const own = pathIn(home);
|
|
3206
|
+
if (own !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
3207
|
+
if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
3208
|
+
return text2;
|
|
3209
|
+
}
|
|
3210
|
+
function pathIn(path14) {
|
|
3211
|
+
const parts = path14.split(/[\\/]+/);
|
|
3212
|
+
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
3213
|
+
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
3214
|
+
const escaped = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
3215
|
+
return new RegExp(`(?:file:\\/\\/\\/?)?${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
3216
|
+
}
|
|
3217
|
+
function cut(text2) {
|
|
3218
|
+
if (jsonBytes(text2) <= TEXT_LIMIT) return text2;
|
|
3219
|
+
const lines = text2.split("\n");
|
|
3220
|
+
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
3221
|
+
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
3222
|
+
let used = 0;
|
|
3223
|
+
let head = 0;
|
|
3224
|
+
let tail = lines.length;
|
|
3225
|
+
while (head < tail && used + size[head] <= budget / 2) used += size[head++];
|
|
3226
|
+
while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
|
|
3227
|
+
return [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n");
|
|
3228
|
+
}
|
|
3229
|
+
function chosen({ status, errors }) {
|
|
3230
|
+
if (status === "timedOut") {
|
|
3231
|
+
const timeout = errors.findIndex((e) => firstLine(e.text)?.startsWith("Test timeout of ") === true);
|
|
3232
|
+
if (timeout !== -1 && timeout + 1 < errors.length) return errors[timeout + 1];
|
|
3233
|
+
}
|
|
3234
|
+
return errors[0];
|
|
3235
|
+
}
|
|
3236
|
+
function chainOf(steps) {
|
|
3237
|
+
const chain = [];
|
|
3238
|
+
for (let failed = steps.find((s) => s.failed); failed !== void 0; failed = failed.steps.find((s) => s.failed)) {
|
|
3239
|
+
chain.push(failed.title);
|
|
3240
|
+
}
|
|
3241
|
+
return chain;
|
|
3242
|
+
}
|
|
3243
|
+
function placeOf(error, testFile) {
|
|
3244
|
+
const own = normal(testFile);
|
|
3245
|
+
let first;
|
|
3246
|
+
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
3247
|
+
if (line.length > FRAME_MAX) continue;
|
|
3248
|
+
const frame = FRAME.exec(line);
|
|
3249
|
+
if (frame === null) continue;
|
|
3250
|
+
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
3251
|
+
if (normal(place.file) === own) return place;
|
|
3252
|
+
if (first === void 0 && !normal(place.file).includes("/node_modules/")) first = place;
|
|
3253
|
+
}
|
|
3254
|
+
return error.location ?? first;
|
|
3255
|
+
}
|
|
3256
|
+
function fileOf(raw) {
|
|
3257
|
+
if (!raw.startsWith("file://")) return raw;
|
|
3258
|
+
let path14 = raw.slice("file://".length);
|
|
3259
|
+
try {
|
|
3260
|
+
path14 = decodeURIComponent(path14);
|
|
3261
|
+
} catch {
|
|
3262
|
+
}
|
|
3263
|
+
return /^\/[A-Za-z]:\//.test(path14) ? path14.slice(1) : path14;
|
|
3264
|
+
}
|
|
3265
|
+
function fromRoot(place, root) {
|
|
3266
|
+
if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
|
|
3267
|
+
const base = normal(root).replace(/\/+$/, "");
|
|
3268
|
+
const file = normal(place.file);
|
|
3269
|
+
if (!file.startsWith(`${base}/`)) return void 0;
|
|
3270
|
+
const relative2 = file.slice(base.length + 1);
|
|
3271
|
+
if (!REPO_RELATIVE.test(relative2)) return void 0;
|
|
3272
|
+
const column = place.column !== void 0 && Number.isInteger(place.column) && place.column >= 1 ? place.column : void 0;
|
|
3273
|
+
return column === void 0 ? { file: relative2, line: place.line } : { file: relative2, line: place.line, column };
|
|
3274
|
+
}
|
|
3275
|
+
function webLink(href) {
|
|
3276
|
+
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
3277
|
+
}
|
|
3278
|
+
var import_node_fs2, import_node_os2, import_node_path2, import_node_util, PATH_START, TEXT_LIMIT, HEADLINE_LIMIT, jsonBytes, ANY_HOME, fileNameOf, omitted, METADATA_TYPE, REPO_RELATIVE, FRAME, FRAME_MAX, firstLine, normal;
|
|
3279
|
+
var init_failure_detail = __esm({
|
|
3280
|
+
"src/reporter-core/failure-detail.ts"() {
|
|
3281
|
+
"use strict";
|
|
3282
|
+
init_cjs_shims();
|
|
3283
|
+
import_node_fs2 = require("fs");
|
|
3284
|
+
import_node_os2 = require("os");
|
|
3285
|
+
import_node_path2 = require("path");
|
|
3286
|
+
import_node_util = require("util");
|
|
3287
|
+
PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
3288
|
+
TEXT_LIMIT = 524288;
|
|
3289
|
+
HEADLINE_LIMIT = 8 * 1024;
|
|
3290
|
+
jsonBytes = (text2) => Buffer.byteLength(JSON.stringify(text2)) - 2;
|
|
3291
|
+
ANY_HOME = /(?<=^|[\s'"`(=,[])(?:file:\/\/\/?)?(?:\/home\/|\/Users\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
3292
|
+
fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
3293
|
+
omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
3294
|
+
METADATA_TYPE = "application/plune.metadata+json";
|
|
3295
|
+
REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
3296
|
+
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
3297
|
+
FRAME_MAX = 8192;
|
|
3298
|
+
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
3299
|
+
normal = (path14) => import_node_path2.posix.normalize(path14.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
|
|
3107
3303
|
// src/importers/playwright-json.ts
|
|
3108
3304
|
function statedIdOf(test, title) {
|
|
3109
3305
|
const annotation = test.annotations?.find((a) => a.type === "PluneId")?.description;
|
|
3110
3306
|
if (annotation !== void 0 && annotation !== "") return annotation;
|
|
3111
3307
|
return TOKEN.exec(title)?.[1];
|
|
3112
3308
|
}
|
|
3113
|
-
function
|
|
3114
|
-
|
|
3309
|
+
function attemptOf(result, file, ctx) {
|
|
3310
|
+
const steps = (list2) => (list2 ?? []).map((s) => ({ title: s.title ?? "", failed: s.error !== void 0, steps: steps(s.steps) }));
|
|
3311
|
+
return {
|
|
3312
|
+
status: result.status ?? "unknown",
|
|
3313
|
+
errors: (result.errors ?? []).map(({ message, value: value2, location: at }) => ({
|
|
3314
|
+
text: message ?? value2 ?? "",
|
|
3315
|
+
...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
|
|
3316
|
+
})),
|
|
3317
|
+
steps: steps(result.steps),
|
|
3318
|
+
attachments: (result.attachments ?? []).map(({ name, contentType, path: path14 }) => ({
|
|
3319
|
+
name: name ?? "",
|
|
3320
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
3321
|
+
...path14 !== void 0 ? { path: path14 } : {}
|
|
3322
|
+
})),
|
|
3323
|
+
...ctx.buildHref !== void 0 ? { buildHref: ctx.buildHref } : {},
|
|
3324
|
+
// Spec files are posix and relative to the root dir; the core reads either slash.
|
|
3325
|
+
testFile: ctx.rootDir === void 0 ? file : `${ctx.rootDir.replace(/[\\/]+$/, "")}/${file}`,
|
|
3326
|
+
...ctx.repoRoot !== void 0 ? { repoRoot: ctx.repoRoot } : {}
|
|
3327
|
+
};
|
|
3115
3328
|
}
|
|
3116
|
-
function pendingFrom(spec, test, result, file, titles) {
|
|
3329
|
+
function pendingFrom(spec, test, result, file, titles, ctx) {
|
|
3117
3330
|
const pathTitle = [file, ...titles].join("#");
|
|
3118
3331
|
const seed = test.id ?? pathTitle;
|
|
3119
3332
|
const stated = statedIdOf(test, spec.title ?? "");
|
|
3120
3333
|
const expected = test.expectedStatus;
|
|
3121
|
-
const
|
|
3334
|
+
const attempt = attemptOf(result, file, ctx);
|
|
3335
|
+
const errorContext = errorContextOf(attempt);
|
|
3336
|
+
const failure = NOT_FAILED.has(attempt.status) ? void 0 : failureOf(attempt);
|
|
3122
3337
|
const keys = [
|
|
3123
3338
|
...test.id !== void 0 ? [{ kind: "playwright-id", value: test.id }] : [],
|
|
3124
3339
|
{ kind: "path-title", value: pathTitle }
|
|
@@ -3146,19 +3361,20 @@ function pendingFrom(spec, test, result, file, titles) {
|
|
|
3146
3361
|
...result.workerIndex !== void 0 ? { worker: String(result.workerIndex) } : {}
|
|
3147
3362
|
}
|
|
3148
3363
|
} : {},
|
|
3149
|
-
...errorContext !== "" ? { errorContext } : {}
|
|
3364
|
+
...errorContext !== "" ? { errorContext } : {},
|
|
3365
|
+
...failure !== void 0 ? { failure } : {}
|
|
3150
3366
|
};
|
|
3151
3367
|
}
|
|
3152
|
-
function walk(suite, describes, depth, out) {
|
|
3368
|
+
function walk(suite, describes, depth, ctx, out) {
|
|
3153
3369
|
const here = depth === 0 ? [] : [...describes, suite.title ?? ""];
|
|
3154
3370
|
for (const spec of suite.specs ?? []) {
|
|
3155
3371
|
const file = spec.file ?? suite.file ?? suite.title ?? "";
|
|
3156
3372
|
const titles = [...here, spec.title ?? ""];
|
|
3157
3373
|
for (const test of spec.tests ?? []) {
|
|
3158
|
-
for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles));
|
|
3374
|
+
for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles, ctx));
|
|
3159
3375
|
}
|
|
3160
3376
|
}
|
|
3161
|
-
for (const child of suite.suites ?? []) walk(child, here, depth + 1, out);
|
|
3377
|
+
for (const child of suite.suites ?? []) walk(child, here, depth + 1, ctx, out);
|
|
3162
3378
|
}
|
|
3163
3379
|
function readPlaywrightJson(source, file) {
|
|
3164
3380
|
let parsed;
|
|
@@ -3170,22 +3386,34 @@ function readPlaywrightJson(source, file) {
|
|
|
3170
3386
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.suites)) {
|
|
3171
3387
|
throw new JsonReportError(file, 'no "suites" array \u2014 is this a Playwright JSON report?');
|
|
3172
3388
|
}
|
|
3389
|
+
const { config, suites } = parsed;
|
|
3390
|
+
const rootDir = typeof config?.rootDir === "string" ? config.rootDir : void 0;
|
|
3391
|
+
const buildHref = config?.metadata?.ci?.buildHref;
|
|
3392
|
+
const repoRoot = rootDir !== void 0 && (0, import_node_fs3.existsSync)(rootDir) ? repoRootOf(rootDir) : void 0;
|
|
3393
|
+
const ctx = {
|
|
3394
|
+
...rootDir !== void 0 ? { rootDir } : {},
|
|
3395
|
+
...repoRoot !== void 0 ? { repoRoot } : {},
|
|
3396
|
+
...typeof buildHref === "string" ? { buildHref } : {}
|
|
3397
|
+
};
|
|
3173
3398
|
const out = [];
|
|
3174
|
-
for (const suite of
|
|
3175
|
-
|
|
3399
|
+
for (const suite of suites) walk(suite, [], 0, ctx, out);
|
|
3400
|
+
const ciUrl = webLink(ctx.buildHref);
|
|
3401
|
+
return ciUrl === void 0 ? { results: out } : { results: out, ciUrl };
|
|
3176
3402
|
}
|
|
3177
3403
|
function looksLikePlaywrightJson(source) {
|
|
3178
|
-
|
|
3179
|
-
return head.trimStart().startsWith("{") && head.includes('"suites"');
|
|
3404
|
+
return /^\s*\{/.test(source) && source.includes('"suites"');
|
|
3180
3405
|
}
|
|
3181
|
-
var SOURCE2, TOKEN, JsonReportError;
|
|
3406
|
+
var import_node_fs3, SOURCE2, TOKEN, NOT_FAILED, JsonReportError;
|
|
3182
3407
|
var init_playwright_json = __esm({
|
|
3183
3408
|
"src/importers/playwright-json.ts"() {
|
|
3184
3409
|
"use strict";
|
|
3185
3410
|
init_cjs_shims();
|
|
3411
|
+
import_node_fs3 = require("fs");
|
|
3412
|
+
init_failure_detail();
|
|
3186
3413
|
init_result_key();
|
|
3187
3414
|
SOURCE2 = "playwright";
|
|
3188
3415
|
TOKEN = /@P([A-Za-z0-9_-]+)/;
|
|
3416
|
+
NOT_FAILED = /* @__PURE__ */ new Set(["passed", "skipped"]);
|
|
3189
3417
|
JsonReportError = class extends Error {
|
|
3190
3418
|
constructor(file, detail) {
|
|
3191
3419
|
super(`${file} \u2014 ${detail}`);
|
|
@@ -3216,11 +3444,9 @@ function detectFormat(source, file) {
|
|
|
3216
3444
|
);
|
|
3217
3445
|
}
|
|
3218
3446
|
function readReport(source, file, format) {
|
|
3219
|
-
const
|
|
3220
|
-
return {
|
|
3221
|
-
|
|
3222
|
-
results: chosen === "junit" ? readJUnit(source, file) : readPlaywrightJson(source, file)
|
|
3223
|
-
};
|
|
3447
|
+
const chosen2 = format ?? detectFormat(source, file);
|
|
3448
|
+
if (chosen2 === "junit") return { format: chosen2, results: readJUnit(source, file) };
|
|
3449
|
+
return { format: chosen2, ...readPlaywrightJson(source, file) };
|
|
3224
3450
|
}
|
|
3225
3451
|
var IMPORT_FORMATS, UnknownFormatError;
|
|
3226
3452
|
var init_importers = __esm({
|
|
@@ -3255,7 +3481,7 @@ async function handleRunImport(options) {
|
|
|
3255
3481
|
} catch {
|
|
3256
3482
|
throw new RunCommandError(`Cannot read ${options.file}.`);
|
|
3257
3483
|
}
|
|
3258
|
-
const { format, results } = readReport(source, options.file, options.format);
|
|
3484
|
+
const { format, results, ciUrl } = readReport(source, options.file, options.format);
|
|
3259
3485
|
if (results.length === 0) {
|
|
3260
3486
|
throw new RunCommandError(`No test cases in ${options.file} \u2014 it parsed, but there is nothing in it.`);
|
|
3261
3487
|
}
|
|
@@ -3271,7 +3497,8 @@ async function handleRunImport(options) {
|
|
|
3271
3497
|
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
3272
3498
|
...options.create === true ? { offerDiscovered: true } : {},
|
|
3273
3499
|
kind: "automated",
|
|
3274
|
-
|
|
3500
|
+
// The CI run the report names, never this machine's: whoever imports may be another job (#790).
|
|
3501
|
+
meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {} },
|
|
3275
3502
|
log: write
|
|
3276
3503
|
},
|
|
3277
3504
|
// The file is the complete list of what ran, so the platform can resolve every test in one
|
|
@@ -3312,7 +3539,9 @@ function describe(result, apiUrl) {
|
|
|
3312
3539
|
// «Imported 555 result(s): 0 accepted, 0 already there, 0 unmatched» was a sentence whose two
|
|
3313
3540
|
// halves disagreed, and readers stop at the verb — a run that reached nobody read as one that
|
|
3314
3541
|
// worked (#622).
|
|
3315
|
-
`
|
|
3542
|
+
// `not delivered` always, zero included (#790): a number that only appears when it is bad is one
|
|
3543
|
+
// nobody learns to look for. The `[0-9]+ unmatched` the workflows read stays whole.
|
|
3544
|
+
`Read ${result.parsed} result(s) from a ${result.format} report: ${result.accepted} accepted, ${result.duplicate} already there, ${result.unresolved} unmatched, ${result.deferred} not delivered.`
|
|
3316
3545
|
];
|
|
3317
3546
|
if (result.conflict > 0 || result.rejected > 0) {
|
|
3318
3547
|
lines.push(`${result.conflict} conflicted and ${result.rejected} were refused.`);
|
|
@@ -4545,11 +4774,11 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
4545
4774
|
init_cjs_shims();
|
|
4546
4775
|
init_api_url();
|
|
4547
4776
|
var import_commander = require("commander");
|
|
4548
|
-
var
|
|
4549
|
-
var
|
|
4777
|
+
var import_node_fs4 = require("fs");
|
|
4778
|
+
var import_node_path3 = require("path");
|
|
4550
4779
|
var import_node_url = require("url");
|
|
4551
|
-
var __dirname = (0,
|
|
4552
|
-
var pkg = JSON.parse((0,
|
|
4780
|
+
var __dirname = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
4781
|
+
var pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path3.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
4553
4782
|
function toFormat(s) {
|
|
4554
4783
|
return s === "json" || s === "markdown" ? s : "console";
|
|
4555
4784
|
}
|
|
@@ -4558,7 +4787,7 @@ function shouldColor(outputPath) {
|
|
|
4558
4787
|
}
|
|
4559
4788
|
function writeReport(text2, outputPath) {
|
|
4560
4789
|
if (outputPath !== void 0) {
|
|
4561
|
-
(0,
|
|
4790
|
+
(0, import_node_fs4.writeFileSync)(outputPath, text2);
|
|
4562
4791
|
} else {
|
|
4563
4792
|
process.stdout.write(text2 + "\n");
|
|
4564
4793
|
}
|
|
@@ -4624,7 +4853,7 @@ function createProgram() {
|
|
|
4624
4853
|
Promise.resolve().then(() => (init_errors(), errors_exports)),
|
|
4625
4854
|
Promise.resolve().then(() => (init_errors2(), errors_exports2))
|
|
4626
4855
|
]);
|
|
4627
|
-
loadEnv2(configPath !== void 0 ? (0,
|
|
4856
|
+
loadEnv2(configPath !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(configPath)) : process.cwd());
|
|
4628
4857
|
try {
|
|
4629
4858
|
const result = await handleRun2({
|
|
4630
4859
|
dryRun: options.dryRun,
|
|
@@ -4755,7 +4984,8 @@ function createProgram() {
|
|
|
4755
4984
|
Promise.resolve().then(() => (init_run_import(), run_import_exports)),
|
|
4756
4985
|
Promise.resolve().then(() => (init_importers(), importers_exports))
|
|
4757
4986
|
]);
|
|
4758
|
-
const
|
|
4987
|
+
const run = command.parent;
|
|
4988
|
+
const format = options.format ?? (run?.getOptionValueSource("format") === "cli" ? run.opts().format : void 0);
|
|
4759
4989
|
if (format !== void 0 && !IMPORT_FORMATS2.includes(format)) {
|
|
4760
4990
|
process.stderr.write(
|
|
4761
4991
|
`Unknown --format "${format}". Use ${IMPORT_FORMATS2.join(" or ")}.
|
|
@@ -4788,7 +5018,7 @@ function createProgram() {
|
|
|
4788
5018
|
Promise.resolve().then(() => (init_report(), report_exports)),
|
|
4789
5019
|
Promise.resolve().then(() => (init_reporters(), reporters_exports))
|
|
4790
5020
|
]);
|
|
4791
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
5021
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
|
|
4792
5022
|
loadEnv2(cwd);
|
|
4793
5023
|
try {
|
|
4794
5024
|
const result = handleReport2({ cwd });
|
|
@@ -4886,7 +5116,7 @@ function createProgram() {
|
|
|
4886
5116
|
const globals = command.optsWithGlobals();
|
|
4887
5117
|
const verbose = globals.verbose === true;
|
|
4888
5118
|
const { handleSync: handleSync2, reportSyncFailure: reportSyncFailure2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
4889
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
5119
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
|
|
4890
5120
|
try {
|
|
4891
5121
|
const { id, url } = await handleSync2({
|
|
4892
5122
|
cwd,
|
|
@@ -5004,7 +5234,7 @@ function isMainModule() {
|
|
|
5004
5234
|
const entry = process.argv[1];
|
|
5005
5235
|
if (entry === void 0) return false;
|
|
5006
5236
|
try {
|
|
5007
|
-
return (0,
|
|
5237
|
+
return (0, import_node_fs4.realpathSync)(entry) === (0, import_node_fs4.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5008
5238
|
} catch {
|
|
5009
5239
|
return false;
|
|
5010
5240
|
}
|