@plune-ai/cli 0.14.0 → 0.14.2
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 +62 -44
- package/dist/reporter-core.cjs +60 -42
- package/dist/reporter-core.d.cts +6 -2
- package/dist/reporter-core.d.ts +6 -2
- package/dist/reporter-core.js +60 -42
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -2265,7 +2265,11 @@ function createClient(opts) {
|
|
|
2265
2265
|
}
|
|
2266
2266
|
if (res.ok) {
|
|
2267
2267
|
if (res.status === 204) return { ok: true, status: res.status, body: void 0 };
|
|
2268
|
-
|
|
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
|
+
}
|
|
2269
2273
|
}
|
|
2270
2274
|
const detail = scrub(await detailOf(res));
|
|
2271
2275
|
const kind = classify3(res.status);
|
|
@@ -2497,11 +2501,13 @@ async function startRun(passed, declared) {
|
|
|
2497
2501
|
"/v1/test-cases/resolve",
|
|
2498
2502
|
{ keys: part }
|
|
2499
2503
|
);
|
|
2500
|
-
if (!out.ok) {
|
|
2501
|
-
log(`plune: could not look up test cases (${out.detail || out.kind}).`);
|
|
2504
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
2505
|
+
log(`plune: could not look up test cases (${out.ok ? "the answer had no results" : out.detail || out.kind}).`);
|
|
2502
2506
|
return;
|
|
2503
2507
|
}
|
|
2504
|
-
for (const row of out.body.results)
|
|
2508
|
+
for (const row of out.body.results) {
|
|
2509
|
+
if (typeof row?.key?.value === "string") resolved.set(row.key.value, row.testCaseId);
|
|
2510
|
+
}
|
|
2505
2511
|
}
|
|
2506
2512
|
}
|
|
2507
2513
|
if (!offline) {
|
|
@@ -2538,12 +2544,13 @@ async function startRun(passed, declared) {
|
|
|
2538
2544
|
title: defaultRunTitle(),
|
|
2539
2545
|
...described
|
|
2540
2546
|
});
|
|
2541
|
-
if (start.ok) {
|
|
2547
|
+
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
2542
2548
|
runId = start.body.run.id;
|
|
2543
2549
|
joined = start.status === 200;
|
|
2544
2550
|
warnIfDropped(described, start.body.run, log);
|
|
2545
2551
|
} else {
|
|
2546
|
-
|
|
2552
|
+
const why = start.ok ? "the answer had no run" : start.detail || start.kind;
|
|
2553
|
+
log(`plune: could not start the run (${why}). Results will be written to ${fallbackPath}.`);
|
|
2547
2554
|
}
|
|
2548
2555
|
}
|
|
2549
2556
|
function matchFor(result) {
|
|
@@ -2578,15 +2585,15 @@ async function startRun(passed, declared) {
|
|
|
2578
2585
|
discovered: batch,
|
|
2579
2586
|
runId
|
|
2580
2587
|
});
|
|
2581
|
-
if (!out.ok) {
|
|
2588
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
2582
2589
|
stats.unoffered = batches.slice(index).reduce((n, rest) => n + rest.length, 0);
|
|
2583
2590
|
log(
|
|
2584
|
-
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.detail || out.kind}).`
|
|
2591
|
+
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.ok ? "the answer had no results" : out.detail || out.kind}).`
|
|
2585
2592
|
);
|
|
2586
2593
|
return;
|
|
2587
2594
|
}
|
|
2588
|
-
stats.offered += out.body.results.filter((r) => r
|
|
2589
|
-
stats.created += out.body.results.filter((r) => r
|
|
2595
|
+
stats.offered += out.body.results.filter((r) => r?.outcome === "queued").length;
|
|
2596
|
+
stats.created += out.body.results.filter((r) => r?.outcome === "created").length;
|
|
2590
2597
|
}
|
|
2591
2598
|
}
|
|
2592
2599
|
async function flush() {
|
|
@@ -2625,15 +2632,16 @@ async function startRun(passed, declared) {
|
|
|
2625
2632
|
const out = await client.post(`/v1/runs/${runId}/results`, {
|
|
2626
2633
|
results: part
|
|
2627
2634
|
});
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
stats
|
|
2632
|
-
stats.rejected += out.body.counts.rejected ?? 0;
|
|
2635
|
+
const counts = out.ok ? out.body?.counts : void 0;
|
|
2636
|
+
const told = COUNTED.filter((k) => typeof counts?.[k] === "number");
|
|
2637
|
+
if (told.length > 0) {
|
|
2638
|
+
for (const k of told) stats[k] += counts[k];
|
|
2633
2639
|
progress();
|
|
2634
2640
|
continue;
|
|
2635
2641
|
}
|
|
2636
|
-
if (out.
|
|
2642
|
+
if (out.ok) {
|
|
2643
|
+
log("plune: could not send results (the answer carried no counts).");
|
|
2644
|
+
} else if (out.kind === "auth") {
|
|
2637
2645
|
log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
|
|
2638
2646
|
offline = true;
|
|
2639
2647
|
} else if (out.kind === "conflict") {
|
|
@@ -2691,7 +2699,7 @@ async function startRun(passed, declared) {
|
|
|
2691
2699
|
...reason !== void 0 ? { reason } : {}
|
|
2692
2700
|
});
|
|
2693
2701
|
if (!out.ok) log(`plune: could not close run ${runId} (${out.detail || out.kind}).`);
|
|
2694
|
-
else if (typeof out.body
|
|
2702
|
+
else if (typeof out.body?.detached === "number" && out.body.detached > 0) {
|
|
2695
2703
|
log(`plune: ${out.body.detached} test case(s) marked detached \u2014 this full run no longer reports them.`);
|
|
2696
2704
|
}
|
|
2697
2705
|
}
|
|
@@ -2709,7 +2717,7 @@ async function startRun(passed, declared) {
|
|
|
2709
2717
|
}
|
|
2710
2718
|
};
|
|
2711
2719
|
}
|
|
2712
|
-
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX, BATCH_BYTES, ENVELOPE_BYTES;
|
|
2720
|
+
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX, COUNTED, BATCH_BYTES, ENVELOPE_BYTES;
|
|
2713
2721
|
var init_session = __esm({
|
|
2714
2722
|
"src/reporter-core/session.ts"() {
|
|
2715
2723
|
"use strict";
|
|
@@ -2727,6 +2735,7 @@ var init_session = __esm({
|
|
|
2727
2735
|
EXPECTED_MAX = 1e4;
|
|
2728
2736
|
KEY_MAX = 1024;
|
|
2729
2737
|
TITLE_MAX = 300;
|
|
2738
|
+
COUNTED = ["accepted", "duplicate", "conflict", "rejected"];
|
|
2730
2739
|
BATCH_BYTES = 8 * 1024 * 1024;
|
|
2731
2740
|
ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
|
|
2732
2741
|
}
|
|
@@ -3164,12 +3173,13 @@ var init_junit = __esm({
|
|
|
3164
3173
|
});
|
|
3165
3174
|
|
|
3166
3175
|
// src/reporter-core/failure-detail.ts
|
|
3167
|
-
function failureOf(attempt) {
|
|
3176
|
+
function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3168
3177
|
const error = chosen(attempt);
|
|
3169
|
-
const
|
|
3178
|
+
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
3179
|
+
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
3170
3180
|
const steps = chainOf(attempt.steps);
|
|
3171
3181
|
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 &&
|
|
3182
|
+
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 !== "");
|
|
3173
3183
|
const ciUrl = webLink(attempt.buildHref);
|
|
3174
3184
|
const failure = {
|
|
3175
3185
|
...headline !== void 0 ? { headline } : {},
|
|
@@ -3187,28 +3197,32 @@ function repoRootOf(dir, stopAt) {
|
|
|
3187
3197
|
}
|
|
3188
3198
|
}
|
|
3189
3199
|
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3200
|
+
const text2 = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
3201
|
+
return cut(machineless(text2, attempt.repoRoot, home));
|
|
3202
|
+
}
|
|
3203
|
+
function machineless(raw, repoRoot, home) {
|
|
3204
|
+
let text2 = (0, import_node_util.stripVTControlCharacters)(raw).replaceAll("\x1B", "");
|
|
3205
|
+
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
3206
|
+
if (root !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
3195
3207
|
const own = pathIn(home);
|
|
3196
|
-
if (own !== void 0) text2 = text2.replace(new RegExp(`${own.source}(?![\\w.-])`, own.flags), "~");
|
|
3197
|
-
if (
|
|
3198
|
-
return
|
|
3208
|
+
if (own !== void 0) text2 = text2.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
3209
|
+
if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
3210
|
+
return text2;
|
|
3199
3211
|
}
|
|
3200
3212
|
function pathIn(path14) {
|
|
3201
3213
|
const parts = path14.split(/[\\/]+/);
|
|
3202
3214
|
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
3203
3215
|
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
3204
|
-
const
|
|
3205
|
-
|
|
3216
|
+
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
3217
|
+
const escaped = /^\/(?!\/)/.test(path14) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
3218
|
+
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
3219
|
+
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
3206
3220
|
}
|
|
3207
3221
|
function cut(text2) {
|
|
3208
|
-
if (
|
|
3222
|
+
if (jsonBytes(text2) <= TEXT_LIMIT) return text2;
|
|
3209
3223
|
const lines = text2.split("\n");
|
|
3210
|
-
const size = lines.map((line) =>
|
|
3211
|
-
const budget = TEXT_LIMIT -
|
|
3224
|
+
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
3225
|
+
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
3212
3226
|
let used = 0;
|
|
3213
3227
|
let head = 0;
|
|
3214
3228
|
let tail = lines.length;
|
|
@@ -3234,6 +3248,7 @@ function placeOf(error, testFile) {
|
|
|
3234
3248
|
const own = normal(testFile);
|
|
3235
3249
|
let first;
|
|
3236
3250
|
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
3251
|
+
if (line.length > FRAME_MAX) continue;
|
|
3237
3252
|
const frame = FRAME.exec(line);
|
|
3238
3253
|
if (frame === null) continue;
|
|
3239
3254
|
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
@@ -3244,7 +3259,11 @@ function placeOf(error, testFile) {
|
|
|
3244
3259
|
}
|
|
3245
3260
|
function fileOf(raw) {
|
|
3246
3261
|
if (!raw.startsWith("file://")) return raw;
|
|
3247
|
-
|
|
3262
|
+
let path14 = raw.slice("file://".length);
|
|
3263
|
+
try {
|
|
3264
|
+
path14 = decodeURIComponent(path14);
|
|
3265
|
+
} catch {
|
|
3266
|
+
}
|
|
3248
3267
|
return /^\/[A-Za-z]:\//.test(path14) ? path14.slice(1) : path14;
|
|
3249
3268
|
}
|
|
3250
3269
|
function fromRoot(place, root) {
|
|
@@ -3258,15 +3277,9 @@ function fromRoot(place, root) {
|
|
|
3258
3277
|
return column === void 0 ? { file: relative2, line: place.line } : { file: relative2, line: place.line, column };
|
|
3259
3278
|
}
|
|
3260
3279
|
function webLink(href) {
|
|
3261
|
-
|
|
3262
|
-
try {
|
|
3263
|
-
const { protocol } = new URL(href);
|
|
3264
|
-
return protocol === "http:" || protocol === "https:" ? href : void 0;
|
|
3265
|
-
} catch {
|
|
3266
|
-
return void 0;
|
|
3267
|
-
}
|
|
3280
|
+
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
3268
3281
|
}
|
|
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;
|
|
3282
|
+
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;
|
|
3270
3283
|
var init_failure_detail = __esm({
|
|
3271
3284
|
"src/reporter-core/failure-detail.ts"() {
|
|
3272
3285
|
"use strict";
|
|
@@ -3275,12 +3288,17 @@ var init_failure_detail = __esm({
|
|
|
3275
3288
|
import_node_os2 = require("os");
|
|
3276
3289
|
import_node_path2 = require("path");
|
|
3277
3290
|
import_node_util = require("util");
|
|
3291
|
+
PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
3278
3292
|
TEXT_LIMIT = 524288;
|
|
3279
|
-
|
|
3293
|
+
HEADLINE_LIMIT = 8 * 1024;
|
|
3294
|
+
jsonBytes = (text2) => Buffer.byteLength(JSON.stringify(text2)) - 2;
|
|
3295
|
+
ANY_HOME = /(?<=^|[\s'"`(=,[])(?:(?:file|webpack|webpack-internal):(?:\\*\/){2,3})?(?:\\*\/home\\*\/|\\*\/Users\\*\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
3296
|
+
fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
3280
3297
|
omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
3281
3298
|
METADATA_TYPE = "application/plune.metadata+json";
|
|
3282
3299
|
REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
3283
3300
|
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
3301
|
+
FRAME_MAX = 8192;
|
|
3284
3302
|
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
3285
3303
|
normal = (path14) => import_node_path2.posix.normalize(path14.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
3286
3304
|
}
|
package/dist/reporter-core.cjs
CHANGED
|
@@ -159,7 +159,11 @@ function createClient(opts) {
|
|
|
159
159
|
}
|
|
160
160
|
if (res.ok) {
|
|
161
161
|
if (res.status === 204) return { ok: true, status: res.status, body: void 0 };
|
|
162
|
-
|
|
162
|
+
try {
|
|
163
|
+
return { ok: true, status: res.status, body: await res.json() };
|
|
164
|
+
} catch {
|
|
165
|
+
return { ok: false, kind: "other", status: res.status, detail: "the answer was not JSON" };
|
|
166
|
+
}
|
|
163
167
|
}
|
|
164
168
|
const detail = scrub(await detailOf(res));
|
|
165
169
|
const kind = classify(res.status);
|
|
@@ -264,6 +268,7 @@ var BATCH_DEFAULT = 100;
|
|
|
264
268
|
var EXPECTED_MAX = 1e4;
|
|
265
269
|
var KEY_MAX = 1024;
|
|
266
270
|
var TITLE_MAX = 300;
|
|
271
|
+
var COUNTED = ["accepted", "duplicate", "conflict", "rejected"];
|
|
267
272
|
function boundKey(key) {
|
|
268
273
|
if (key.value.length <= KEY_MAX) return key;
|
|
269
274
|
const digest = (0, import_node_crypto.createHash)("sha256").update(key.value).digest("hex").slice(0, 16);
|
|
@@ -374,11 +379,13 @@ async function startRun(passed, declared) {
|
|
|
374
379
|
"/v1/test-cases/resolve",
|
|
375
380
|
{ keys: part }
|
|
376
381
|
);
|
|
377
|
-
if (!out.ok) {
|
|
378
|
-
log(`plune: could not look up test cases (${out.detail || out.kind}).`);
|
|
382
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
383
|
+
log(`plune: could not look up test cases (${out.ok ? "the answer had no results" : out.detail || out.kind}).`);
|
|
379
384
|
return;
|
|
380
385
|
}
|
|
381
|
-
for (const row of out.body.results)
|
|
386
|
+
for (const row of out.body.results) {
|
|
387
|
+
if (typeof row?.key?.value === "string") resolved.set(row.key.value, row.testCaseId);
|
|
388
|
+
}
|
|
382
389
|
}
|
|
383
390
|
}
|
|
384
391
|
if (!offline) {
|
|
@@ -415,12 +422,13 @@ async function startRun(passed, declared) {
|
|
|
415
422
|
title: defaultRunTitle(),
|
|
416
423
|
...described
|
|
417
424
|
});
|
|
418
|
-
if (start.ok) {
|
|
425
|
+
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
419
426
|
runId = start.body.run.id;
|
|
420
427
|
joined = start.status === 200;
|
|
421
428
|
warnIfDropped(described, start.body.run, log);
|
|
422
429
|
} else {
|
|
423
|
-
|
|
430
|
+
const why = start.ok ? "the answer had no run" : start.detail || start.kind;
|
|
431
|
+
log(`plune: could not start the run (${why}). Results will be written to ${fallbackPath}.`);
|
|
424
432
|
}
|
|
425
433
|
}
|
|
426
434
|
function matchFor(result) {
|
|
@@ -455,15 +463,15 @@ async function startRun(passed, declared) {
|
|
|
455
463
|
discovered: batch,
|
|
456
464
|
runId
|
|
457
465
|
});
|
|
458
|
-
if (!out.ok) {
|
|
466
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
459
467
|
stats.unoffered = batches.slice(index).reduce((n, rest) => n + rest.length, 0);
|
|
460
468
|
log(
|
|
461
|
-
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.detail || out.kind}).`
|
|
469
|
+
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.ok ? "the answer had no results" : out.detail || out.kind}).`
|
|
462
470
|
);
|
|
463
471
|
return;
|
|
464
472
|
}
|
|
465
|
-
stats.offered += out.body.results.filter((r) => r
|
|
466
|
-
stats.created += out.body.results.filter((r) => r
|
|
473
|
+
stats.offered += out.body.results.filter((r) => r?.outcome === "queued").length;
|
|
474
|
+
stats.created += out.body.results.filter((r) => r?.outcome === "created").length;
|
|
467
475
|
}
|
|
468
476
|
}
|
|
469
477
|
async function flush() {
|
|
@@ -502,15 +510,16 @@ async function startRun(passed, declared) {
|
|
|
502
510
|
const out = await client.post(`/v1/runs/${runId}/results`, {
|
|
503
511
|
results: part
|
|
504
512
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
stats
|
|
509
|
-
stats.rejected += out.body.counts.rejected ?? 0;
|
|
513
|
+
const counts = out.ok ? out.body?.counts : void 0;
|
|
514
|
+
const told = COUNTED.filter((k) => typeof counts?.[k] === "number");
|
|
515
|
+
if (told.length > 0) {
|
|
516
|
+
for (const k of told) stats[k] += counts[k];
|
|
510
517
|
progress();
|
|
511
518
|
continue;
|
|
512
519
|
}
|
|
513
|
-
if (out.
|
|
520
|
+
if (out.ok) {
|
|
521
|
+
log("plune: could not send results (the answer carried no counts).");
|
|
522
|
+
} else if (out.kind === "auth") {
|
|
514
523
|
log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
|
|
515
524
|
offline = true;
|
|
516
525
|
} else if (out.kind === "conflict") {
|
|
@@ -568,7 +577,7 @@ async function startRun(passed, declared) {
|
|
|
568
577
|
...reason !== void 0 ? { reason } : {}
|
|
569
578
|
});
|
|
570
579
|
if (!out.ok) log(`plune: could not close run ${runId} (${out.detail || out.kind}).`);
|
|
571
|
-
else if (typeof out.body
|
|
580
|
+
else if (typeof out.body?.detached === "number" && out.body.detached > 0) {
|
|
572
581
|
log(`plune: ${out.body.detached} test case(s) marked detached \u2014 this full run no longer reports them.`);
|
|
573
582
|
}
|
|
574
583
|
}
|
|
@@ -606,12 +615,13 @@ var import_node_fs2 = require("fs");
|
|
|
606
615
|
var import_node_os2 = require("os");
|
|
607
616
|
var import_node_path2 = require("path");
|
|
608
617
|
var import_node_util = require("util");
|
|
609
|
-
function failureOf(attempt) {
|
|
618
|
+
function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
610
619
|
const error = chosen(attempt);
|
|
611
|
-
const
|
|
620
|
+
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
621
|
+
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
612
622
|
const steps = chainOf(attempt.steps);
|
|
613
623
|
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
614
|
-
const artifacts = attempt.attachments.filter((a) => a.path !== void 0 &&
|
|
624
|
+
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 !== "");
|
|
615
625
|
const ciUrl = webLink(attempt.buildHref);
|
|
616
626
|
const failure = {
|
|
617
627
|
...headline !== void 0 ? { headline } : {},
|
|
@@ -629,30 +639,38 @@ function repoRootOf(dir, stopAt) {
|
|
|
629
639
|
}
|
|
630
640
|
}
|
|
631
641
|
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
642
|
+
const text = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
643
|
+
return cut(machineless(text, attempt.repoRoot, home));
|
|
644
|
+
}
|
|
645
|
+
function machineless(raw, repoRoot, home) {
|
|
646
|
+
let text = (0, import_node_util.stripVTControlCharacters)(raw).replaceAll("\x1B", "");
|
|
647
|
+
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
648
|
+
if (root !== void 0) text = text.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
637
649
|
const own = pathIn(home);
|
|
638
|
-
if (own !== void 0) text = text.replace(new RegExp(`${own.source}(?![\\w.-])`, own.flags), "~");
|
|
639
|
-
if (
|
|
640
|
-
return
|
|
650
|
+
if (own !== void 0) text = text.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
651
|
+
if (repoRoot === void 0) text = text.replace(ANY_HOME, "~");
|
|
652
|
+
return text;
|
|
641
653
|
}
|
|
654
|
+
var PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
642
655
|
var TEXT_LIMIT = 524288;
|
|
643
|
-
var
|
|
656
|
+
var HEADLINE_LIMIT = 8 * 1024;
|
|
657
|
+
var jsonBytes = (text) => Buffer.byteLength(JSON.stringify(text)) - 2;
|
|
658
|
+
var ANY_HOME = /(?<=^|[\s'"`(=,[])(?:(?:file|webpack|webpack-internal):(?:\\*\/){2,3})?(?:\\*\/home\\*\/|\\*\/Users\\*\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
644
659
|
function pathIn(path3) {
|
|
645
660
|
const parts = path3.split(/[\\/]+/);
|
|
646
661
|
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
647
662
|
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
648
|
-
const
|
|
649
|
-
|
|
663
|
+
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
664
|
+
const escaped = /^\/(?!\/)/.test(path3) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
665
|
+
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
666
|
+
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
650
667
|
}
|
|
668
|
+
var fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
651
669
|
function cut(text) {
|
|
652
|
-
if (
|
|
670
|
+
if (jsonBytes(text) <= TEXT_LIMIT) return text;
|
|
653
671
|
const lines = text.split("\n");
|
|
654
|
-
const size = lines.map((line) =>
|
|
655
|
-
const budget = TEXT_LIMIT -
|
|
672
|
+
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
673
|
+
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
656
674
|
let used = 0;
|
|
657
675
|
let head = 0;
|
|
658
676
|
let tail = lines.length;
|
|
@@ -664,6 +682,7 @@ var omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
|
664
682
|
var METADATA_TYPE = "application/plune.metadata+json";
|
|
665
683
|
var REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
666
684
|
var FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
685
|
+
var FRAME_MAX = 8192;
|
|
667
686
|
var firstLine = (text) => (0, import_node_util.stripVTControlCharacters)(text).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
668
687
|
function chosen({ status, errors }) {
|
|
669
688
|
if (status === "timedOut") {
|
|
@@ -683,6 +702,7 @@ function placeOf(error, testFile) {
|
|
|
683
702
|
const own = normal(testFile);
|
|
684
703
|
let first;
|
|
685
704
|
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
705
|
+
if (line.length > FRAME_MAX) continue;
|
|
686
706
|
const frame = FRAME.exec(line);
|
|
687
707
|
if (frame === null) continue;
|
|
688
708
|
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
@@ -693,7 +713,11 @@ function placeOf(error, testFile) {
|
|
|
693
713
|
}
|
|
694
714
|
function fileOf(raw) {
|
|
695
715
|
if (!raw.startsWith("file://")) return raw;
|
|
696
|
-
|
|
716
|
+
let path3 = raw.slice("file://".length);
|
|
717
|
+
try {
|
|
718
|
+
path3 = decodeURIComponent(path3);
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
697
721
|
return /^\/[A-Za-z]:\//.test(path3) ? path3.slice(1) : path3;
|
|
698
722
|
}
|
|
699
723
|
var normal = (path3) => import_node_path2.posix.normalize(path3.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
@@ -708,13 +732,7 @@ function fromRoot(place, root) {
|
|
|
708
732
|
return column === void 0 ? { file: relative, line: place.line } : { file: relative, line: place.line, column };
|
|
709
733
|
}
|
|
710
734
|
function webLink(href) {
|
|
711
|
-
|
|
712
|
-
try {
|
|
713
|
-
const { protocol } = new URL(href);
|
|
714
|
-
return protocol === "http:" || protocol === "https:" ? href : void 0;
|
|
715
|
-
} catch {
|
|
716
|
-
return void 0;
|
|
717
|
-
}
|
|
735
|
+
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
718
736
|
}
|
|
719
737
|
// Annotate the CommonJS export names for ESM import in node:
|
|
720
738
|
0 && (module.exports = {
|
package/dist/reporter-core.d.cts
CHANGED
|
@@ -402,7 +402,7 @@ declare function appendBatch(file: string, batch: DeferredBatch): void;
|
|
|
402
402
|
* and nothing is searched for credentials — the platform does both after its own cleaning, and a cut
|
|
403
403
|
* here could halve a credential it would then not recognise.
|
|
404
404
|
*/
|
|
405
|
-
declare function failureOf(attempt: FailedAttempt): FailureDetail | undefined;
|
|
405
|
+
declare function failureOf(attempt: FailedAttempt, home?: string): FailureDetail | undefined;
|
|
406
406
|
/**
|
|
407
407
|
* The nearest folder with a `.git` above the runner's root dir — a folder, or the file a worktree
|
|
408
408
|
* keeps instead. Found once per run by the caller; `stopAt` bounds the walk for a test.
|
|
@@ -415,7 +415,11 @@ declare function repoRootOf(dir: string, stopAt?: string): string | undefined;
|
|
|
415
415
|
* searched for credentials: the platform does that before its own cut (ADR-0005).
|
|
416
416
|
*/
|
|
417
417
|
declare function errorContextOf(attempt: FailedAttempt, home?: string): string;
|
|
418
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* A link only for an address a browser opens as a page — never `file:`, `javascript:` or half a URL.
|
|
420
|
+
* As the platform checks it: `new URL` alone mends `https:/host` and `https:host` into https, and a
|
|
421
|
+
* link the platform refuses costs the run's start or the whole batch.
|
|
422
|
+
*/
|
|
419
423
|
declare function webLink(href: string | undefined): string | undefined;
|
|
420
424
|
|
|
421
425
|
type ClientFailureKind =
|
package/dist/reporter-core.d.ts
CHANGED
|
@@ -402,7 +402,7 @@ declare function appendBatch(file: string, batch: DeferredBatch): void;
|
|
|
402
402
|
* and nothing is searched for credentials — the platform does both after its own cleaning, and a cut
|
|
403
403
|
* here could halve a credential it would then not recognise.
|
|
404
404
|
*/
|
|
405
|
-
declare function failureOf(attempt: FailedAttempt): FailureDetail | undefined;
|
|
405
|
+
declare function failureOf(attempt: FailedAttempt, home?: string): FailureDetail | undefined;
|
|
406
406
|
/**
|
|
407
407
|
* The nearest folder with a `.git` above the runner's root dir — a folder, or the file a worktree
|
|
408
408
|
* keeps instead. Found once per run by the caller; `stopAt` bounds the walk for a test.
|
|
@@ -415,7 +415,11 @@ declare function repoRootOf(dir: string, stopAt?: string): string | undefined;
|
|
|
415
415
|
* searched for credentials: the platform does that before its own cut (ADR-0005).
|
|
416
416
|
*/
|
|
417
417
|
declare function errorContextOf(attempt: FailedAttempt, home?: string): string;
|
|
418
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* A link only for an address a browser opens as a page — never `file:`, `javascript:` or half a URL.
|
|
420
|
+
* As the platform checks it: `new URL` alone mends `https:/host` and `https:host` into https, and a
|
|
421
|
+
* link the platform refuses costs the run's start or the whole batch.
|
|
422
|
+
*/
|
|
419
423
|
declare function webLink(href: string | undefined): string | undefined;
|
|
420
424
|
|
|
421
425
|
type ClientFailureKind =
|
package/dist/reporter-core.js
CHANGED
|
@@ -112,7 +112,11 @@ function createClient(opts) {
|
|
|
112
112
|
}
|
|
113
113
|
if (res.ok) {
|
|
114
114
|
if (res.status === 204) return { ok: true, status: res.status, body: void 0 };
|
|
115
|
-
|
|
115
|
+
try {
|
|
116
|
+
return { ok: true, status: res.status, body: await res.json() };
|
|
117
|
+
} catch {
|
|
118
|
+
return { ok: false, kind: "other", status: res.status, detail: "the answer was not JSON" };
|
|
119
|
+
}
|
|
116
120
|
}
|
|
117
121
|
const detail = scrub(await detailOf(res));
|
|
118
122
|
const kind = classify(res.status);
|
|
@@ -217,6 +221,7 @@ var BATCH_DEFAULT = 100;
|
|
|
217
221
|
var EXPECTED_MAX = 1e4;
|
|
218
222
|
var KEY_MAX = 1024;
|
|
219
223
|
var TITLE_MAX = 300;
|
|
224
|
+
var COUNTED = ["accepted", "duplicate", "conflict", "rejected"];
|
|
220
225
|
function boundKey(key) {
|
|
221
226
|
if (key.value.length <= KEY_MAX) return key;
|
|
222
227
|
const digest = createHash("sha256").update(key.value).digest("hex").slice(0, 16);
|
|
@@ -327,11 +332,13 @@ async function startRun(passed, declared) {
|
|
|
327
332
|
"/v1/test-cases/resolve",
|
|
328
333
|
{ keys: part }
|
|
329
334
|
);
|
|
330
|
-
if (!out.ok) {
|
|
331
|
-
log(`plune: could not look up test cases (${out.detail || out.kind}).`);
|
|
335
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
336
|
+
log(`plune: could not look up test cases (${out.ok ? "the answer had no results" : out.detail || out.kind}).`);
|
|
332
337
|
return;
|
|
333
338
|
}
|
|
334
|
-
for (const row of out.body.results)
|
|
339
|
+
for (const row of out.body.results) {
|
|
340
|
+
if (typeof row?.key?.value === "string") resolved.set(row.key.value, row.testCaseId);
|
|
341
|
+
}
|
|
335
342
|
}
|
|
336
343
|
}
|
|
337
344
|
if (!offline) {
|
|
@@ -368,12 +375,13 @@ async function startRun(passed, declared) {
|
|
|
368
375
|
title: defaultRunTitle(),
|
|
369
376
|
...described
|
|
370
377
|
});
|
|
371
|
-
if (start.ok) {
|
|
378
|
+
if (start.ok && typeof start.body?.run?.id === "string") {
|
|
372
379
|
runId = start.body.run.id;
|
|
373
380
|
joined = start.status === 200;
|
|
374
381
|
warnIfDropped(described, start.body.run, log);
|
|
375
382
|
} else {
|
|
376
|
-
|
|
383
|
+
const why = start.ok ? "the answer had no run" : start.detail || start.kind;
|
|
384
|
+
log(`plune: could not start the run (${why}). Results will be written to ${fallbackPath}.`);
|
|
377
385
|
}
|
|
378
386
|
}
|
|
379
387
|
function matchFor(result) {
|
|
@@ -408,15 +416,15 @@ async function startRun(passed, declared) {
|
|
|
408
416
|
discovered: batch,
|
|
409
417
|
runId
|
|
410
418
|
});
|
|
411
|
-
if (!out.ok) {
|
|
419
|
+
if (!out.ok || !Array.isArray(out.body?.results)) {
|
|
412
420
|
stats.unoffered = batches.slice(index).reduce((n, rest) => n + rest.length, 0);
|
|
413
421
|
log(
|
|
414
|
-
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.detail || out.kind}).`
|
|
422
|
+
`plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.ok ? "the answer had no results" : out.detail || out.kind}).`
|
|
415
423
|
);
|
|
416
424
|
return;
|
|
417
425
|
}
|
|
418
|
-
stats.offered += out.body.results.filter((r) => r
|
|
419
|
-
stats.created += out.body.results.filter((r) => r
|
|
426
|
+
stats.offered += out.body.results.filter((r) => r?.outcome === "queued").length;
|
|
427
|
+
stats.created += out.body.results.filter((r) => r?.outcome === "created").length;
|
|
420
428
|
}
|
|
421
429
|
}
|
|
422
430
|
async function flush() {
|
|
@@ -455,15 +463,16 @@ async function startRun(passed, declared) {
|
|
|
455
463
|
const out = await client.post(`/v1/runs/${runId}/results`, {
|
|
456
464
|
results: part
|
|
457
465
|
});
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
stats
|
|
462
|
-
stats.rejected += out.body.counts.rejected ?? 0;
|
|
466
|
+
const counts = out.ok ? out.body?.counts : void 0;
|
|
467
|
+
const told = COUNTED.filter((k) => typeof counts?.[k] === "number");
|
|
468
|
+
if (told.length > 0) {
|
|
469
|
+
for (const k of told) stats[k] += counts[k];
|
|
463
470
|
progress();
|
|
464
471
|
continue;
|
|
465
472
|
}
|
|
466
|
-
if (out.
|
|
473
|
+
if (out.ok) {
|
|
474
|
+
log("plune: could not send results (the answer carried no counts).");
|
|
475
|
+
} else if (out.kind === "auth") {
|
|
467
476
|
log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
|
|
468
477
|
offline = true;
|
|
469
478
|
} else if (out.kind === "conflict") {
|
|
@@ -521,7 +530,7 @@ async function startRun(passed, declared) {
|
|
|
521
530
|
...reason !== void 0 ? { reason } : {}
|
|
522
531
|
});
|
|
523
532
|
if (!out.ok) log(`plune: could not close run ${runId} (${out.detail || out.kind}).`);
|
|
524
|
-
else if (typeof out.body
|
|
533
|
+
else if (typeof out.body?.detached === "number" && out.body.detached > 0) {
|
|
525
534
|
log(`plune: ${out.body.detached} test case(s) marked detached \u2014 this full run no longer reports them.`);
|
|
526
535
|
}
|
|
527
536
|
}
|
|
@@ -559,12 +568,13 @@ import { existsSync as existsSync2 } from "fs";
|
|
|
559
568
|
import { homedir as homedir2 } from "os";
|
|
560
569
|
import { dirname as dirname3, join as join3, posix, resolve } from "path";
|
|
561
570
|
import { stripVTControlCharacters } from "util";
|
|
562
|
-
function failureOf(attempt) {
|
|
571
|
+
function failureOf(attempt, home = homedir2()) {
|
|
563
572
|
const error = chosen(attempt);
|
|
564
|
-
const
|
|
573
|
+
const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
|
|
574
|
+
const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
|
|
565
575
|
const steps = chainOf(attempt.steps);
|
|
566
576
|
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
567
|
-
const artifacts = attempt.attachments.filter((a) => a.path !== void 0 &&
|
|
577
|
+
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 !== "");
|
|
568
578
|
const ciUrl = webLink(attempt.buildHref);
|
|
569
579
|
const failure = {
|
|
570
580
|
...headline !== void 0 ? { headline } : {},
|
|
@@ -582,30 +592,38 @@ function repoRootOf(dir, stopAt) {
|
|
|
582
592
|
}
|
|
583
593
|
}
|
|
584
594
|
function errorContextOf(attempt, home = homedir2()) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
595
|
+
const text = attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n");
|
|
596
|
+
return cut(machineless(text, attempt.repoRoot, home));
|
|
597
|
+
}
|
|
598
|
+
function machineless(raw, repoRoot, home) {
|
|
599
|
+
let text = stripVTControlCharacters(raw).replaceAll("\x1B", "");
|
|
600
|
+
const root = repoRoot === void 0 ? void 0 : pathIn(repoRoot);
|
|
601
|
+
if (root !== void 0) text = text.replace(new RegExp(`${PATH_START}${root.source}[\\\\/]+`, root.flags), "");
|
|
590
602
|
const own = pathIn(home);
|
|
591
|
-
if (own !== void 0) text = text.replace(new RegExp(`${own.source}(?![\\w.-])`, own.flags), "~");
|
|
592
|
-
if (
|
|
593
|
-
return
|
|
603
|
+
if (own !== void 0) text = text.replace(new RegExp(`${PATH_START}${own.source}(?![\\w.-])`, own.flags), "~");
|
|
604
|
+
if (repoRoot === void 0) text = text.replace(ANY_HOME, "~");
|
|
605
|
+
return text;
|
|
594
606
|
}
|
|
607
|
+
var PATH_START = "(?<![\\w.~%/\\\\-])";
|
|
595
608
|
var TEXT_LIMIT = 524288;
|
|
596
|
-
var
|
|
609
|
+
var HEADLINE_LIMIT = 8 * 1024;
|
|
610
|
+
var jsonBytes = (text) => Buffer.byteLength(JSON.stringify(text)) - 2;
|
|
611
|
+
var ANY_HOME = /(?<=^|[\s'"`(=,[])(?:(?:file|webpack|webpack-internal):(?:\\*\/){2,3})?(?:\\*\/home\\*\/|\\*\/Users\\*\/|[A-Za-z]:[\\/]+Users[\\/]+)[^\\/\s'"`:*?<>|]+/g;
|
|
597
612
|
function pathIn(path3) {
|
|
598
613
|
const parts = path3.split(/[\\/]+/);
|
|
599
614
|
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
600
615
|
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
601
|
-
const
|
|
602
|
-
|
|
616
|
+
const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
|
|
617
|
+
const escaped = /^\/(?!\/)/.test(path3) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
|
|
618
|
+
const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
|
|
619
|
+
return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
603
620
|
}
|
|
621
|
+
var fileNameOf = (name) => /^(?:file:\/\/|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(name) ? name.split(/[\\/]/).pop() ?? "" : name;
|
|
604
622
|
function cut(text) {
|
|
605
|
-
if (
|
|
623
|
+
if (jsonBytes(text) <= TEXT_LIMIT) return text;
|
|
606
624
|
const lines = text.split("\n");
|
|
607
|
-
const size = lines.map((line) =>
|
|
608
|
-
const budget = TEXT_LIMIT -
|
|
625
|
+
const size = lines.map((line) => jsonBytes(line) + 2);
|
|
626
|
+
const budget = TEXT_LIMIT - jsonBytes(omitted(lines.length));
|
|
609
627
|
let used = 0;
|
|
610
628
|
let head = 0;
|
|
611
629
|
let tail = lines.length;
|
|
@@ -617,6 +635,7 @@ var omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
|
617
635
|
var METADATA_TYPE = "application/plune.metadata+json";
|
|
618
636
|
var REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
619
637
|
var FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
638
|
+
var FRAME_MAX = 8192;
|
|
620
639
|
var firstLine = (text) => stripVTControlCharacters(text).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
621
640
|
function chosen({ status, errors }) {
|
|
622
641
|
if (status === "timedOut") {
|
|
@@ -636,6 +655,7 @@ function placeOf(error, testFile) {
|
|
|
636
655
|
const own = normal(testFile);
|
|
637
656
|
let first;
|
|
638
657
|
for (const line of stripVTControlCharacters(error.text).split("\n")) {
|
|
658
|
+
if (line.length > FRAME_MAX) continue;
|
|
639
659
|
const frame = FRAME.exec(line);
|
|
640
660
|
if (frame === null) continue;
|
|
641
661
|
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
@@ -646,7 +666,11 @@ function placeOf(error, testFile) {
|
|
|
646
666
|
}
|
|
647
667
|
function fileOf(raw) {
|
|
648
668
|
if (!raw.startsWith("file://")) return raw;
|
|
649
|
-
|
|
669
|
+
let path3 = raw.slice("file://".length);
|
|
670
|
+
try {
|
|
671
|
+
path3 = decodeURIComponent(path3);
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
650
674
|
return /^\/[A-Za-z]:\//.test(path3) ? path3.slice(1) : path3;
|
|
651
675
|
}
|
|
652
676
|
var normal = (path3) => posix.normalize(path3.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
@@ -661,13 +685,7 @@ function fromRoot(place, root) {
|
|
|
661
685
|
return column === void 0 ? { file: relative, line: place.line } : { file: relative, line: place.line, column };
|
|
662
686
|
}
|
|
663
687
|
function webLink(href) {
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
const { protocol } = new URL(href);
|
|
667
|
-
return protocol === "http:" || protocol === "https:" ? href : void 0;
|
|
668
|
-
} catch {
|
|
669
|
-
return void 0;
|
|
670
|
-
}
|
|
688
|
+
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
671
689
|
}
|
|
672
690
|
export {
|
|
673
691
|
DEFAULT_FALLBACK_PATH,
|
package/package.json
CHANGED