@plune-ai/cli 0.13.0 → 0.14.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/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((resolve7) => setTimeout(resolve7, ms));
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 ?? fetch;
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("***");
@@ -2253,12 +2282,15 @@ function createClient(opts) {
2253
2282
  del: (path14) => send("DELETE", path14)
2254
2283
  };
2255
2284
  }
2256
- var RETRY_DELAYS_MS, sleep;
2285
+ var import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, sleep;
2257
2286
  var init_client = __esm({
2258
2287
  "src/reporter-core/client.ts"() {
2259
2288
  "use strict";
2260
2289
  init_cjs_shims();
2290
+ import_node_http = require("http");
2291
+ import_node_https = require("https");
2261
2292
  RETRY_DELAYS_MS = [300, 900];
2293
+ IDLE_MS = 3e5;
2262
2294
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2263
2295
  }
2264
2296
  });
@@ -2367,6 +2399,23 @@ function boundKey(key) {
2367
2399
  function boundTitle(title) {
2368
2400
  return title.length <= TITLE_MAX ? title : `${title.slice(0, TITLE_MAX - 1)}\u2026`;
2369
2401
  }
2402
+ function packed(results) {
2403
+ const out = [];
2404
+ let part = [];
2405
+ let bytes = ENVELOPE_BYTES;
2406
+ for (const result of results) {
2407
+ const size = Buffer.byteLength(JSON.stringify(result));
2408
+ if (part.length > 0 && bytes + 1 + size > BATCH_BYTES) {
2409
+ out.push(part);
2410
+ part = [];
2411
+ bytes = ENVELOPE_BYTES;
2412
+ }
2413
+ bytes += (part.length > 0 ? 1 : 0) + size;
2414
+ part.push(result);
2415
+ }
2416
+ if (part.length > 0) out.push(part);
2417
+ return out;
2418
+ }
2370
2419
  function chunk(items, size) {
2371
2420
  const out = [];
2372
2421
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
@@ -2568,27 +2617,32 @@ async function startRun(passed, declared) {
2568
2617
  progress();
2569
2618
  return;
2570
2619
  }
2571
- const out = await client.post(
2572
- `/v1/runs/${runId}/results`,
2573
- { results: submissions }
2574
- );
2575
- if (out.ok) {
2576
- stats.accepted += out.body.counts.accepted ?? 0;
2577
- stats.duplicate += out.body.counts.duplicate ?? 0;
2578
- stats.conflict += out.body.counts.conflict ?? 0;
2579
- stats.rejected += out.body.counts.rejected ?? 0;
2580
- progress();
2581
- return;
2582
- }
2583
- if (out.kind === "auth") {
2584
- log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
2585
- offline = true;
2586
- } else if (out.kind === "conflict") {
2587
- log(`plune: this run is closed \u2014 ${out.detail}`);
2588
- } else {
2589
- log(`plune: could not send results (${out.detail || out.kind}).`);
2620
+ for (const part of packed(submissions)) {
2621
+ if (offline) {
2622
+ defer(part);
2623
+ continue;
2624
+ }
2625
+ const out = await client.post(`/v1/runs/${runId}/results`, {
2626
+ results: part
2627
+ });
2628
+ if (out.ok) {
2629
+ stats.accepted += out.body.counts.accepted ?? 0;
2630
+ stats.duplicate += out.body.counts.duplicate ?? 0;
2631
+ stats.conflict += out.body.counts.conflict ?? 0;
2632
+ stats.rejected += out.body.counts.rejected ?? 0;
2633
+ progress();
2634
+ continue;
2635
+ }
2636
+ if (out.kind === "auth") {
2637
+ log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
2638
+ offline = true;
2639
+ } else if (out.kind === "conflict") {
2640
+ log(`plune: this run is closed \u2014 ${out.detail}`);
2641
+ } else {
2642
+ log(`plune: could not send results (${out.detail || out.kind}).`);
2643
+ }
2644
+ defer(part);
2590
2645
  }
2591
- defer(submissions);
2592
2646
  }
2593
2647
  function progress() {
2594
2648
  const total = expected?.length ?? 0;
@@ -2604,9 +2658,12 @@ async function startRun(passed, declared) {
2604
2658
  if (stats.unresolved > 0) parts.push(`${stats.unresolved} with no matching test case`);
2605
2659
  if (stats.offered > 0) parts.push(`${stats.offered} offered for review`);
2606
2660
  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}`);
2661
+ if (stats.deferred > 0) parts.push(`${stats.deferred} not delivered \u2014 written to ${fallbackPath}`);
2608
2662
  if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
2609
2663
  log(`plune: ${parts.join(" \xB7 ")}`);
2664
+ if (stats.deferred > 0 && process.env["GITHUB_ACTIONS"] === "true") {
2665
+ log(`::warning::${stats.deferred} result(s) were not delivered to Plune \u2014 see the reporting step's log.`);
2666
+ }
2610
2667
  }
2611
2668
  return {
2612
2669
  get runId() {
@@ -2652,7 +2709,7 @@ async function startRun(passed, declared) {
2652
2709
  }
2653
2710
  };
2654
2711
  }
2655
- var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX;
2712
+ var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX, BATCH_BYTES, ENVELOPE_BYTES;
2656
2713
  var init_session = __esm({
2657
2714
  "src/reporter-core/session.ts"() {
2658
2715
  "use strict";
@@ -2670,6 +2727,8 @@ var init_session = __esm({
2670
2727
  EXPECTED_MAX = 1e4;
2671
2728
  KEY_MAX = 1024;
2672
2729
  TITLE_MAX = 300;
2730
+ BATCH_BYTES = 8 * 1024 * 1024;
2731
+ ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
2673
2732
  }
2674
2733
  });
2675
2734
 
@@ -2765,14 +2824,14 @@ async function handleRunExec(options) {
2765
2824
  if (command === void 0) throw new RunCommandError("Nothing to run \u2014 pass a command after `--`.");
2766
2825
  const run = await handleRunStart(options);
2767
2826
  const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
2768
- const code = await new Promise((resolve7) => {
2827
+ const code = await new Promise((resolve8) => {
2769
2828
  const child = spawnImpl(command, args, {
2770
2829
  stdio: "inherit",
2771
2830
  shell: process.platform === "win32",
2772
2831
  env: { ...process.env, PLUNE_RUN: run.externalKey, PLUNE_PROCEED: "1" }
2773
2832
  });
2774
- child.on("close", (exitCode) => resolve7(exitCode ?? 1));
2775
- child.on("error", () => resolve7(1));
2833
+ child.on("close", (exitCode) => resolve8(exitCode ?? 1));
2834
+ child.on("error", () => resolve8(1));
2776
2835
  });
2777
2836
  await handleRunFinish(run.id, options);
2778
2837
  return code;
@@ -3104,21 +3163,163 @@ var init_junit = __esm({
3104
3163
  }
3105
3164
  });
3106
3165
 
3166
+ // src/reporter-core/failure-detail.ts
3167
+ function failureOf(attempt) {
3168
+ const error = chosen(attempt);
3169
+ const headline = error === void 0 ? void 0 : firstLine(error.text);
3170
+ const steps = chainOf(attempt.steps);
3171
+ const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
3172
+ const artifacts = attempt.attachments.filter((a) => a.path !== void 0 && a.name !== "" && !a.name.startsWith("_") && a.contentType !== METADATA_TYPE).map(({ name, contentType }) => contentType === void 0 ? { name } : { name, contentType });
3173
+ const ciUrl = webLink(attempt.buildHref);
3174
+ const failure = {
3175
+ ...headline !== void 0 ? { headline } : {},
3176
+ ...steps.length > 0 ? { steps } : {},
3177
+ ...location !== void 0 ? { location } : {},
3178
+ ...artifacts.length > 0 ? { artifacts } : {},
3179
+ ...ciUrl !== void 0 ? { ciUrl } : {}
3180
+ };
3181
+ return Object.keys(failure).length === 0 ? void 0 : failure;
3182
+ }
3183
+ function repoRootOf(dir, stopAt) {
3184
+ for (let here = (0, import_node_path2.resolve)(dir); ; here = (0, import_node_path2.dirname)(here)) {
3185
+ if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(here, ".git"))) return here;
3186
+ if (here === stopAt || (0, import_node_path2.dirname)(here) === here) return void 0;
3187
+ }
3188
+ }
3189
+ function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
3190
+ let text2 = (0, import_node_util.stripVTControlCharacters)(
3191
+ attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n")
3192
+ ).replaceAll("\x1B", "");
3193
+ const root = attempt.repoRoot === void 0 ? void 0 : pathIn(attempt.repoRoot);
3194
+ if (root !== void 0) text2 = text2.replace(new RegExp(`${root.source}[\\\\/]`, root.flags), "");
3195
+ const own = pathIn(home);
3196
+ if (own !== void 0) text2 = text2.replace(new RegExp(`${own.source}(?![\\w.-])`, own.flags), "~");
3197
+ if (attempt.repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
3198
+ return cut(text2);
3199
+ }
3200
+ function pathIn(path14) {
3201
+ const parts = path14.split(/[\\/]+/);
3202
+ while (parts.length > 1 && parts.at(-1) === "") parts.pop();
3203
+ if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
3204
+ const escaped = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]");
3205
+ return new RegExp(`(?:file:\\/\\/\\/?)?${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
3206
+ }
3207
+ function cut(text2) {
3208
+ if (Buffer.byteLength(text2) <= TEXT_LIMIT) return text2;
3209
+ const lines = text2.split("\n");
3210
+ const size = lines.map((line) => Buffer.byteLength(line) + 1);
3211
+ const budget = TEXT_LIMIT - Buffer.byteLength(omitted(lines.length));
3212
+ let used = 0;
3213
+ let head = 0;
3214
+ let tail = lines.length;
3215
+ while (head < tail && used + size[head] <= budget / 2) used += size[head++];
3216
+ while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
3217
+ return [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n");
3218
+ }
3219
+ function chosen({ status, errors }) {
3220
+ if (status === "timedOut") {
3221
+ const timeout = errors.findIndex((e) => firstLine(e.text)?.startsWith("Test timeout of ") === true);
3222
+ if (timeout !== -1 && timeout + 1 < errors.length) return errors[timeout + 1];
3223
+ }
3224
+ return errors[0];
3225
+ }
3226
+ function chainOf(steps) {
3227
+ const chain = [];
3228
+ for (let failed = steps.find((s) => s.failed); failed !== void 0; failed = failed.steps.find((s) => s.failed)) {
3229
+ chain.push(failed.title);
3230
+ }
3231
+ return chain;
3232
+ }
3233
+ function placeOf(error, testFile) {
3234
+ const own = normal(testFile);
3235
+ let first;
3236
+ for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
3237
+ const frame = FRAME.exec(line);
3238
+ if (frame === null) continue;
3239
+ const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
3240
+ if (normal(place.file) === own) return place;
3241
+ if (first === void 0 && !normal(place.file).includes("/node_modules/")) first = place;
3242
+ }
3243
+ return error.location ?? first;
3244
+ }
3245
+ function fileOf(raw) {
3246
+ if (!raw.startsWith("file://")) return raw;
3247
+ const path14 = decodeURIComponent(raw.slice("file://".length));
3248
+ return /^\/[A-Za-z]:\//.test(path14) ? path14.slice(1) : path14;
3249
+ }
3250
+ function fromRoot(place, root) {
3251
+ if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
3252
+ const base = normal(root).replace(/\/+$/, "");
3253
+ const file = normal(place.file);
3254
+ if (!file.startsWith(`${base}/`)) return void 0;
3255
+ const relative2 = file.slice(base.length + 1);
3256
+ if (!REPO_RELATIVE.test(relative2)) return void 0;
3257
+ const column = place.column !== void 0 && Number.isInteger(place.column) && place.column >= 1 ? place.column : void 0;
3258
+ return column === void 0 ? { file: relative2, line: place.line } : { file: relative2, line: place.line, column };
3259
+ }
3260
+ function webLink(href) {
3261
+ if (href === void 0) return void 0;
3262
+ try {
3263
+ const { protocol } = new URL(href);
3264
+ return protocol === "http:" || protocol === "https:" ? href : void 0;
3265
+ } catch {
3266
+ return void 0;
3267
+ }
3268
+ }
3269
+ var import_node_fs2, import_node_os2, import_node_path2, import_node_util, TEXT_LIMIT, ANY_HOME, omitted, METADATA_TYPE, REPO_RELATIVE, FRAME, firstLine, normal;
3270
+ var init_failure_detail = __esm({
3271
+ "src/reporter-core/failure-detail.ts"() {
3272
+ "use strict";
3273
+ init_cjs_shims();
3274
+ import_node_fs2 = require("fs");
3275
+ import_node_os2 = require("os");
3276
+ import_node_path2 = require("path");
3277
+ import_node_util = require("util");
3278
+ TEXT_LIMIT = 524288;
3279
+ ANY_HOME = /(?<=^|[\s'"`(=,[])(?:file:\/\/\/?)?(?:\/home\/|\/Users\/|[A-Za-z]:[\\/]Users[\\/])[^\\/\s'"`:*?<>|]+/g;
3280
+ omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
3281
+ METADATA_TYPE = "application/plune.metadata+json";
3282
+ REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
3283
+ FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
3284
+ firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
3285
+ normal = (path14) => import_node_path2.posix.normalize(path14.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
3286
+ }
3287
+ });
3288
+
3107
3289
  // src/importers/playwright-json.ts
3108
3290
  function statedIdOf(test, title) {
3109
3291
  const annotation = test.annotations?.find((a) => a.type === "PluneId")?.description;
3110
3292
  if (annotation !== void 0 && annotation !== "") return annotation;
3111
3293
  return TOKEN.exec(title)?.[1];
3112
3294
  }
3113
- function errorTextOf2(result) {
3114
- return (result.errors ?? []).map((e) => e.stack ?? e.message ?? e.value ?? "").filter((text2) => text2 !== "").join("\n\n");
3295
+ function attemptOf(result, file, ctx) {
3296
+ const steps = (list2) => (list2 ?? []).map((s) => ({ title: s.title ?? "", failed: s.error !== void 0, steps: steps(s.steps) }));
3297
+ return {
3298
+ status: result.status ?? "unknown",
3299
+ errors: (result.errors ?? []).map(({ message, value: value2, location: at }) => ({
3300
+ text: message ?? value2 ?? "",
3301
+ ...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
3302
+ })),
3303
+ steps: steps(result.steps),
3304
+ attachments: (result.attachments ?? []).map(({ name, contentType, path: path14 }) => ({
3305
+ name: name ?? "",
3306
+ ...contentType !== void 0 ? { contentType } : {},
3307
+ ...path14 !== void 0 ? { path: path14 } : {}
3308
+ })),
3309
+ ...ctx.buildHref !== void 0 ? { buildHref: ctx.buildHref } : {},
3310
+ // Spec files are posix and relative to the root dir; the core reads either slash.
3311
+ testFile: ctx.rootDir === void 0 ? file : `${ctx.rootDir.replace(/[\\/]+$/, "")}/${file}`,
3312
+ ...ctx.repoRoot !== void 0 ? { repoRoot: ctx.repoRoot } : {}
3313
+ };
3115
3314
  }
3116
- function pendingFrom(spec, test, result, file, titles) {
3315
+ function pendingFrom(spec, test, result, file, titles, ctx) {
3117
3316
  const pathTitle = [file, ...titles].join("#");
3118
3317
  const seed = test.id ?? pathTitle;
3119
3318
  const stated = statedIdOf(test, spec.title ?? "");
3120
3319
  const expected = test.expectedStatus;
3121
- const errorContext = errorTextOf2(result);
3320
+ const attempt = attemptOf(result, file, ctx);
3321
+ const errorContext = errorContextOf(attempt);
3322
+ const failure = NOT_FAILED.has(attempt.status) ? void 0 : failureOf(attempt);
3122
3323
  const keys = [
3123
3324
  ...test.id !== void 0 ? [{ kind: "playwright-id", value: test.id }] : [],
3124
3325
  { kind: "path-title", value: pathTitle }
@@ -3146,19 +3347,20 @@ function pendingFrom(spec, test, result, file, titles) {
3146
3347
  ...result.workerIndex !== void 0 ? { worker: String(result.workerIndex) } : {}
3147
3348
  }
3148
3349
  } : {},
3149
- ...errorContext !== "" ? { errorContext } : {}
3350
+ ...errorContext !== "" ? { errorContext } : {},
3351
+ ...failure !== void 0 ? { failure } : {}
3150
3352
  };
3151
3353
  }
3152
- function walk(suite, describes, depth, out) {
3354
+ function walk(suite, describes, depth, ctx, out) {
3153
3355
  const here = depth === 0 ? [] : [...describes, suite.title ?? ""];
3154
3356
  for (const spec of suite.specs ?? []) {
3155
3357
  const file = spec.file ?? suite.file ?? suite.title ?? "";
3156
3358
  const titles = [...here, spec.title ?? ""];
3157
3359
  for (const test of spec.tests ?? []) {
3158
- for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles));
3360
+ for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles, ctx));
3159
3361
  }
3160
3362
  }
3161
- for (const child of suite.suites ?? []) walk(child, here, depth + 1, out);
3363
+ for (const child of suite.suites ?? []) walk(child, here, depth + 1, ctx, out);
3162
3364
  }
3163
3365
  function readPlaywrightJson(source, file) {
3164
3366
  let parsed;
@@ -3170,22 +3372,34 @@ function readPlaywrightJson(source, file) {
3170
3372
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.suites)) {
3171
3373
  throw new JsonReportError(file, 'no "suites" array \u2014 is this a Playwright JSON report?');
3172
3374
  }
3375
+ const { config, suites } = parsed;
3376
+ const rootDir = typeof config?.rootDir === "string" ? config.rootDir : void 0;
3377
+ const buildHref = config?.metadata?.ci?.buildHref;
3378
+ const repoRoot = rootDir !== void 0 && (0, import_node_fs3.existsSync)(rootDir) ? repoRootOf(rootDir) : void 0;
3379
+ const ctx = {
3380
+ ...rootDir !== void 0 ? { rootDir } : {},
3381
+ ...repoRoot !== void 0 ? { repoRoot } : {},
3382
+ ...typeof buildHref === "string" ? { buildHref } : {}
3383
+ };
3173
3384
  const out = [];
3174
- for (const suite of parsed.suites) walk(suite, [], 0, out);
3175
- return out;
3385
+ for (const suite of suites) walk(suite, [], 0, ctx, out);
3386
+ const ciUrl = webLink(ctx.buildHref);
3387
+ return ciUrl === void 0 ? { results: out } : { results: out, ciUrl };
3176
3388
  }
3177
3389
  function looksLikePlaywrightJson(source) {
3178
- const head = source.slice(0, 4096);
3179
- return head.trimStart().startsWith("{") && head.includes('"suites"');
3390
+ return /^\s*\{/.test(source) && source.includes('"suites"');
3180
3391
  }
3181
- var SOURCE2, TOKEN, JsonReportError;
3392
+ var import_node_fs3, SOURCE2, TOKEN, NOT_FAILED, JsonReportError;
3182
3393
  var init_playwright_json = __esm({
3183
3394
  "src/importers/playwright-json.ts"() {
3184
3395
  "use strict";
3185
3396
  init_cjs_shims();
3397
+ import_node_fs3 = require("fs");
3398
+ init_failure_detail();
3186
3399
  init_result_key();
3187
3400
  SOURCE2 = "playwright";
3188
3401
  TOKEN = /@P([A-Za-z0-9_-]+)/;
3402
+ NOT_FAILED = /* @__PURE__ */ new Set(["passed", "skipped"]);
3189
3403
  JsonReportError = class extends Error {
3190
3404
  constructor(file, detail) {
3191
3405
  super(`${file} \u2014 ${detail}`);
@@ -3216,11 +3430,9 @@ function detectFormat(source, file) {
3216
3430
  );
3217
3431
  }
3218
3432
  function readReport(source, file, format) {
3219
- const chosen = format ?? detectFormat(source, file);
3220
- return {
3221
- format: chosen,
3222
- results: chosen === "junit" ? readJUnit(source, file) : readPlaywrightJson(source, file)
3223
- };
3433
+ const chosen2 = format ?? detectFormat(source, file);
3434
+ if (chosen2 === "junit") return { format: chosen2, results: readJUnit(source, file) };
3435
+ return { format: chosen2, ...readPlaywrightJson(source, file) };
3224
3436
  }
3225
3437
  var IMPORT_FORMATS, UnknownFormatError;
3226
3438
  var init_importers = __esm({
@@ -3255,7 +3467,7 @@ async function handleRunImport(options) {
3255
3467
  } catch {
3256
3468
  throw new RunCommandError(`Cannot read ${options.file}.`);
3257
3469
  }
3258
- const { format, results } = readReport(source, options.file, options.format);
3470
+ const { format, results, ciUrl } = readReport(source, options.file, options.format);
3259
3471
  if (results.length === 0) {
3260
3472
  throw new RunCommandError(`No test cases in ${options.file} \u2014 it parsed, but there is nothing in it.`);
3261
3473
  }
@@ -3271,7 +3483,8 @@ async function handleRunImport(options) {
3271
3483
  ...options.key !== void 0 ? { externalKey: options.key } : {},
3272
3484
  ...options.create === true ? { offerDiscovered: true } : {},
3273
3485
  kind: "automated",
3274
- meta: { runner: format },
3486
+ // The CI run the report names, never this machine's: whoever imports may be another job (#790).
3487
+ meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {} },
3275
3488
  log: write
3276
3489
  },
3277
3490
  // The file is the complete list of what ran, so the platform can resolve every test in one
@@ -3312,7 +3525,9 @@ function describe(result, apiUrl) {
3312
3525
  // «Imported 555 result(s): 0 accepted, 0 already there, 0 unmatched» was a sentence whose two
3313
3526
  // halves disagreed, and readers stop at the verb — a run that reached nobody read as one that
3314
3527
  // worked (#622).
3315
- `Read ${result.parsed} result(s) from a ${result.format} report: ${result.accepted} accepted, ${result.duplicate} already there, ${result.unresolved} unmatched.`
3528
+ // `not delivered` always, zero included (#790): a number that only appears when it is bad is one
3529
+ // nobody learns to look for. The `[0-9]+ unmatched` the workflows read stays whole.
3530
+ `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
3531
  ];
3317
3532
  if (result.conflict > 0 || result.rejected > 0) {
3318
3533
  lines.push(`${result.conflict} conflicted and ${result.rejected} were refused.`);
@@ -4545,11 +4760,11 @@ module.exports = __toCommonJS(cli_exports);
4545
4760
  init_cjs_shims();
4546
4761
  init_api_url();
4547
4762
  var import_commander = require("commander");
4548
- var import_node_fs2 = require("fs");
4549
- var import_node_path2 = require("path");
4763
+ var import_node_fs4 = require("fs");
4764
+ var import_node_path3 = require("path");
4550
4765
  var import_node_url = require("url");
4551
- var __dirname = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
4552
- var pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(__dirname, "..", "package.json"), "utf-8"));
4766
+ var __dirname = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
4767
+ var pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path3.join)(__dirname, "..", "package.json"), "utf-8"));
4553
4768
  function toFormat(s) {
4554
4769
  return s === "json" || s === "markdown" ? s : "console";
4555
4770
  }
@@ -4558,7 +4773,7 @@ function shouldColor(outputPath) {
4558
4773
  }
4559
4774
  function writeReport(text2, outputPath) {
4560
4775
  if (outputPath !== void 0) {
4561
- (0, import_node_fs2.writeFileSync)(outputPath, text2);
4776
+ (0, import_node_fs4.writeFileSync)(outputPath, text2);
4562
4777
  } else {
4563
4778
  process.stdout.write(text2 + "\n");
4564
4779
  }
@@ -4624,7 +4839,7 @@ function createProgram() {
4624
4839
  Promise.resolve().then(() => (init_errors(), errors_exports)),
4625
4840
  Promise.resolve().then(() => (init_errors2(), errors_exports2))
4626
4841
  ]);
4627
- loadEnv2(configPath !== void 0 ? (0, import_node_path2.dirname)((0, import_node_path2.resolve)(configPath)) : process.cwd());
4842
+ loadEnv2(configPath !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(configPath)) : process.cwd());
4628
4843
  try {
4629
4844
  const result = await handleRun2({
4630
4845
  dryRun: options.dryRun,
@@ -4755,7 +4970,8 @@ function createProgram() {
4755
4970
  Promise.resolve().then(() => (init_run_import(), run_import_exports)),
4756
4971
  Promise.resolve().then(() => (init_importers(), importers_exports))
4757
4972
  ]);
4758
- const format = options.format;
4973
+ const run = command.parent;
4974
+ const format = options.format ?? (run?.getOptionValueSource("format") === "cli" ? run.opts().format : void 0);
4759
4975
  if (format !== void 0 && !IMPORT_FORMATS2.includes(format)) {
4760
4976
  process.stderr.write(
4761
4977
  `Unknown --format "${format}". Use ${IMPORT_FORMATS2.join(" or ")}.
@@ -4788,7 +5004,7 @@ function createProgram() {
4788
5004
  Promise.resolve().then(() => (init_report(), report_exports)),
4789
5005
  Promise.resolve().then(() => (init_reporters(), reporters_exports))
4790
5006
  ]);
4791
- const cwd = globals.config !== void 0 ? (0, import_node_path2.dirname)((0, import_node_path2.resolve)(globals.config)) : process.cwd();
5007
+ const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
4792
5008
  loadEnv2(cwd);
4793
5009
  try {
4794
5010
  const result = handleReport2({ cwd });
@@ -4886,7 +5102,7 @@ function createProgram() {
4886
5102
  const globals = command.optsWithGlobals();
4887
5103
  const verbose = globals.verbose === true;
4888
5104
  const { handleSync: handleSync2, reportSyncFailure: reportSyncFailure2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
4889
- const cwd = globals.config !== void 0 ? (0, import_node_path2.dirname)((0, import_node_path2.resolve)(globals.config)) : process.cwd();
5105
+ const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
4890
5106
  try {
4891
5107
  const { id, url } = await handleSync2({
4892
5108
  cwd,
@@ -5004,7 +5220,7 @@ function isMainModule() {
5004
5220
  const entry = process.argv[1];
5005
5221
  if (entry === void 0) return false;
5006
5222
  try {
5007
- return (0, import_node_fs2.realpathSync)(entry) === (0, import_node_fs2.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
5223
+ return (0, import_node_fs4.realpathSync)(entry) === (0, import_node_fs4.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
5008
5224
  } catch {
5009
5225
  return false;
5010
5226
  }