executable-stories-formatters 0.7.0 → 0.7.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/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +307 -98
- package/dist/cli.js.map +1 -1
- package/dist/{index-BndkylIV.d.cts → index-C0OOaaiK.d.cts} +19 -2
- package/dist/{index-BndkylIV.d.ts → index-C0OOaaiK.d.ts} +19 -2
- package/dist/index.cjs +242 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +240 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/raw-run.schema.json +35 -12
- package/skills/formatters-cli/SKILL.md +41 -0
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
5
|
+
import * as fs5 from "fs";
|
|
6
|
+
import * as path5 from "path";
|
|
7
7
|
|
|
8
8
|
// src/validation/schema-validator.ts
|
|
9
9
|
import Ajv from "ajv/dist/2020.js";
|
|
@@ -175,8 +175,21 @@ var raw_run_schema_default = {
|
|
|
175
175
|
},
|
|
176
176
|
tickets: {
|
|
177
177
|
type: "array",
|
|
178
|
-
items: {
|
|
179
|
-
|
|
178
|
+
items: {
|
|
179
|
+
oneOf: [
|
|
180
|
+
{ type: "string" },
|
|
181
|
+
{
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
id: { type: "string" },
|
|
185
|
+
url: { type: "string" }
|
|
186
|
+
},
|
|
187
|
+
required: ["id"],
|
|
188
|
+
additionalProperties: false
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
description: "Ticket/issue references. Each item is either a string ID or an object with id and optional url."
|
|
180
193
|
},
|
|
181
194
|
meta: {
|
|
182
195
|
type: "object",
|
|
@@ -261,7 +274,8 @@ var raw_run_schema_default = {
|
|
|
261
274
|
properties: {
|
|
262
275
|
kind: { const: "note" },
|
|
263
276
|
text: { type: "string" },
|
|
264
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
277
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
278
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
265
279
|
},
|
|
266
280
|
required: ["kind", "text", "phase"],
|
|
267
281
|
additionalProperties: false
|
|
@@ -275,7 +289,8 @@ var raw_run_schema_default = {
|
|
|
275
289
|
type: "array",
|
|
276
290
|
items: { type: "string" }
|
|
277
291
|
},
|
|
278
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
292
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
293
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
279
294
|
},
|
|
280
295
|
required: ["kind", "names", "phase"],
|
|
281
296
|
additionalProperties: false
|
|
@@ -287,7 +302,8 @@ var raw_run_schema_default = {
|
|
|
287
302
|
kind: { const: "kv" },
|
|
288
303
|
label: { type: "string" },
|
|
289
304
|
value: {},
|
|
290
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
305
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
306
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
291
307
|
},
|
|
292
308
|
required: ["kind", "label", "value", "phase"],
|
|
293
309
|
additionalProperties: false
|
|
@@ -300,7 +316,8 @@ var raw_run_schema_default = {
|
|
|
300
316
|
label: { type: "string" },
|
|
301
317
|
content: { type: "string" },
|
|
302
318
|
lang: { type: "string" },
|
|
303
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
319
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
320
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
304
321
|
},
|
|
305
322
|
required: ["kind", "label", "content", "phase"],
|
|
306
323
|
additionalProperties: false
|
|
@@ -322,7 +339,8 @@ var raw_run_schema_default = {
|
|
|
322
339
|
items: { type: "string" }
|
|
323
340
|
}
|
|
324
341
|
},
|
|
325
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
342
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
343
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
326
344
|
},
|
|
327
345
|
required: ["kind", "label", "columns", "rows", "phase"],
|
|
328
346
|
additionalProperties: false
|
|
@@ -334,7 +352,8 @@ var raw_run_schema_default = {
|
|
|
334
352
|
kind: { const: "link" },
|
|
335
353
|
label: { type: "string" },
|
|
336
354
|
url: { type: "string" },
|
|
337
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
355
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
356
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
338
357
|
},
|
|
339
358
|
required: ["kind", "label", "url", "phase"],
|
|
340
359
|
additionalProperties: false
|
|
@@ -346,7 +365,8 @@ var raw_run_schema_default = {
|
|
|
346
365
|
kind: { const: "section" },
|
|
347
366
|
title: { type: "string" },
|
|
348
367
|
markdown: { type: "string" },
|
|
349
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
368
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
369
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
350
370
|
},
|
|
351
371
|
required: ["kind", "title", "markdown", "phase"],
|
|
352
372
|
additionalProperties: false
|
|
@@ -358,7 +378,8 @@ var raw_run_schema_default = {
|
|
|
358
378
|
kind: { const: "mermaid" },
|
|
359
379
|
code: { type: "string" },
|
|
360
380
|
title: { type: "string" },
|
|
361
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
381
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
382
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
362
383
|
},
|
|
363
384
|
required: ["kind", "code", "phase"],
|
|
364
385
|
additionalProperties: false
|
|
@@ -370,7 +391,8 @@ var raw_run_schema_default = {
|
|
|
370
391
|
kind: { const: "screenshot" },
|
|
371
392
|
path: { type: "string" },
|
|
372
393
|
alt: { type: "string" },
|
|
373
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
394
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
395
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
374
396
|
},
|
|
375
397
|
required: ["kind", "path", "phase"],
|
|
376
398
|
additionalProperties: false
|
|
@@ -382,7 +404,8 @@ var raw_run_schema_default = {
|
|
|
382
404
|
kind: { const: "custom" },
|
|
383
405
|
type: { type: "string" },
|
|
384
406
|
data: {},
|
|
385
|
-
phase: { $ref: "#/$defs/DocPhase" }
|
|
407
|
+
phase: { $ref: "#/$defs/DocPhase" },
|
|
408
|
+
children: { type: "array", items: { $ref: "#/$defs/DocEntry" }, description: "Nested child doc entries for grouping." }
|
|
386
409
|
},
|
|
387
410
|
required: ["kind", "type", "data", "phase"],
|
|
388
411
|
additionalProperties: false
|
|
@@ -469,17 +492,17 @@ function validateRawRun(data) {
|
|
|
469
492
|
return { valid: true, errors: [] };
|
|
470
493
|
}
|
|
471
494
|
const errors = (validate.errors ?? []).map((err) => {
|
|
472
|
-
const
|
|
495
|
+
const path6 = err.instancePath || "/";
|
|
473
496
|
const message = err.message ?? "unknown error";
|
|
474
497
|
if (err.keyword === "additionalProperties") {
|
|
475
498
|
const extra = err.params.additionalProperty;
|
|
476
|
-
return `${
|
|
499
|
+
return `${path6}: ${message} \u2014 '${extra}'`;
|
|
477
500
|
}
|
|
478
501
|
if (err.keyword === "enum") {
|
|
479
502
|
const allowed = err.params.allowedValues;
|
|
480
|
-
return `${
|
|
503
|
+
return `${path6}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
|
|
481
504
|
}
|
|
482
|
-
return `${
|
|
505
|
+
return `${path6}: ${message}`;
|
|
483
506
|
});
|
|
484
507
|
return { valid: false, errors };
|
|
485
508
|
}
|
|
@@ -793,6 +816,9 @@ function canonicalizeTestCase(raw, options, projectRoot) {
|
|
|
793
816
|
projectRoot
|
|
794
817
|
});
|
|
795
818
|
const tags = normalizeTags(story);
|
|
819
|
+
if (story.tickets) {
|
|
820
|
+
story.tickets = normalizeTickets(story.tickets);
|
|
821
|
+
}
|
|
796
822
|
const titlePath = buildTitlePath(raw, story);
|
|
797
823
|
return {
|
|
798
824
|
id,
|
|
@@ -816,6 +842,9 @@ function normalizeTags(story) {
|
|
|
816
842
|
const tags = story.tags ?? [];
|
|
817
843
|
return [...new Set(tags)].sort();
|
|
818
844
|
}
|
|
845
|
+
function normalizeTickets(raw) {
|
|
846
|
+
return raw.map((t) => typeof t === "string" ? { id: t } : t);
|
|
847
|
+
}
|
|
819
848
|
function buildTitlePath(raw, story) {
|
|
820
849
|
if (story.suitePath && story.suitePath.length > 0) {
|
|
821
850
|
return story.suitePath;
|
|
@@ -937,7 +966,7 @@ ${result.errors.join("\n")}`);
|
|
|
937
966
|
|
|
938
967
|
// src/index.ts
|
|
939
968
|
import "fs";
|
|
940
|
-
import * as
|
|
969
|
+
import * as path4 from "path";
|
|
941
970
|
import * as fsPromises from "fs/promises";
|
|
942
971
|
|
|
943
972
|
// src/converters/acl/lines.ts
|
|
@@ -3247,6 +3276,16 @@ body {
|
|
|
3247
3276
|
background: none;
|
|
3248
3277
|
}
|
|
3249
3278
|
|
|
3279
|
+
/* ============================================================================
|
|
3280
|
+
Documentation Entries - Children
|
|
3281
|
+
============================================================================ */
|
|
3282
|
+
.doc-children {
|
|
3283
|
+
margin-left: 1rem;
|
|
3284
|
+
padding-left: 1rem;
|
|
3285
|
+
border-left: 2px solid var(--border);
|
|
3286
|
+
margin-top: 0.25rem;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3250
3289
|
/* ============================================================================
|
|
3251
3290
|
Trace View - OTel span waterfall
|
|
3252
3291
|
============================================================================ */
|
|
@@ -12958,30 +12997,46 @@ function renderDocCustom(entry, deps) {
|
|
|
12958
12997
|
</div>`;
|
|
12959
12998
|
}
|
|
12960
12999
|
function renderDocEntry(entry, deps) {
|
|
13000
|
+
let html;
|
|
12961
13001
|
switch (entry.kind) {
|
|
12962
13002
|
case "note":
|
|
12963
|
-
|
|
13003
|
+
html = renderDocNote(entry, deps);
|
|
13004
|
+
break;
|
|
12964
13005
|
case "tag":
|
|
12965
|
-
|
|
13006
|
+
html = renderDocTag(entry, deps);
|
|
13007
|
+
break;
|
|
12966
13008
|
case "kv":
|
|
12967
|
-
|
|
13009
|
+
html = renderDocKv(entry, deps);
|
|
13010
|
+
break;
|
|
12968
13011
|
case "code":
|
|
12969
|
-
|
|
13012
|
+
html = renderDocCode(entry, deps);
|
|
13013
|
+
break;
|
|
12970
13014
|
case "table":
|
|
12971
|
-
|
|
13015
|
+
html = renderDocTable(entry, deps);
|
|
13016
|
+
break;
|
|
12972
13017
|
case "link":
|
|
12973
|
-
|
|
13018
|
+
html = renderDocLink(entry, deps);
|
|
13019
|
+
break;
|
|
12974
13020
|
case "section":
|
|
12975
|
-
|
|
13021
|
+
html = renderDocSection(entry, deps);
|
|
13022
|
+
break;
|
|
12976
13023
|
case "mermaid":
|
|
12977
|
-
|
|
13024
|
+
html = renderDocMermaid(entry, deps);
|
|
13025
|
+
break;
|
|
12978
13026
|
case "screenshot":
|
|
12979
|
-
|
|
13027
|
+
html = renderDocScreenshot(entry, deps);
|
|
13028
|
+
break;
|
|
12980
13029
|
case "custom":
|
|
12981
|
-
|
|
13030
|
+
html = renderDocCustom(entry, deps);
|
|
13031
|
+
break;
|
|
12982
13032
|
default:
|
|
12983
|
-
|
|
13033
|
+
html = "";
|
|
13034
|
+
}
|
|
13035
|
+
if (entry.children && entry.children.length > 0) {
|
|
13036
|
+
const childrenHtml = entry.children.map((child) => renderDocEntry(child, deps)).join("");
|
|
13037
|
+
html += `<div class="doc-children">${childrenHtml}</div>`;
|
|
12984
13038
|
}
|
|
13039
|
+
return html;
|
|
12985
13040
|
}
|
|
12986
13041
|
|
|
12987
13042
|
// src/formatters/html/renderers/steps.ts
|
|
@@ -13038,12 +13093,20 @@ function highlightStepParams(text, deps) {
|
|
|
13038
13093
|
var MIN_METRIC_SAMPLES = 5;
|
|
13039
13094
|
|
|
13040
13095
|
// src/formatters/html/renderers/scenario.ts
|
|
13096
|
+
function renderTicket(ticket, template, escapeHtml3) {
|
|
13097
|
+
const url = ticket.url ?? (template ? template.replace("{ticket}", ticket.id) : void 0);
|
|
13098
|
+
if (url) {
|
|
13099
|
+
return `<a class="tag ticket-tag" href="${escapeHtml3(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml3(ticket.id)}</a>`;
|
|
13100
|
+
}
|
|
13101
|
+
return `<span class="tag ticket-tag">${escapeHtml3(ticket.id)}</span>`;
|
|
13102
|
+
}
|
|
13041
13103
|
function renderScenario(args, deps) {
|
|
13042
13104
|
const { tc } = args;
|
|
13043
13105
|
const statusIcon = deps.getStatusIcon(tc.status);
|
|
13044
13106
|
const statusClass = `status-${tc.status}`;
|
|
13045
13107
|
const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
|
|
13046
13108
|
const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
|
|
13109
|
+
const tickets = (tc.story.tickets ?? []).map((t) => renderTicket(t, deps.ticketUrlTemplate, deps.escapeHtml)).join("");
|
|
13047
13110
|
const otelMeta = tc.story.meta?.otel;
|
|
13048
13111
|
let traceBadge = "";
|
|
13049
13112
|
if (otelMeta?.traceId) {
|
|
@@ -13111,7 +13174,7 @@ function renderScenario(args, deps) {
|
|
|
13111
13174
|
<span class="status-icon ${statusClass}">${statusIcon}</span>
|
|
13112
13175
|
<span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
|
|
13113
13176
|
</div>
|
|
13114
|
-
<div class="scenario-meta">${tags}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13177
|
+
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13115
13178
|
</div>
|
|
13116
13179
|
<span class="scenario-duration">${duration}</span>
|
|
13117
13180
|
</div>
|
|
@@ -13456,6 +13519,7 @@ function normalizeOptions(options = {}) {
|
|
|
13456
13519
|
mermaidEnabled: options.mermaidEnabled ?? true,
|
|
13457
13520
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
13458
13521
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
13522
|
+
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
13459
13523
|
theme: options.theme ?? "default"
|
|
13460
13524
|
};
|
|
13461
13525
|
}
|
|
@@ -13488,7 +13552,8 @@ function createHtmlFormatter(options = {}) {
|
|
|
13488
13552
|
renderAttachments: (args, d) => renderAttachments(args, d),
|
|
13489
13553
|
renderTraceView: (args, d) => renderTraceView(args, d),
|
|
13490
13554
|
embedScreenshots: opts.embedScreenshots,
|
|
13491
|
-
permalinkBaseUrl: opts.permalinkBaseUrl
|
|
13555
|
+
permalinkBaseUrl: opts.permalinkBaseUrl,
|
|
13556
|
+
ticketUrlTemplate: opts.ticketUrlTemplate
|
|
13492
13557
|
};
|
|
13493
13558
|
const featureDeps = {
|
|
13494
13559
|
escapeHtml,
|
|
@@ -14001,14 +14066,16 @@ var MarkdownFormatter = class {
|
|
|
14001
14066
|
}
|
|
14002
14067
|
if (tc.story.tickets && tc.story.tickets.length > 0) {
|
|
14003
14068
|
const ticketTemplate = this.options.ticketUrlTemplate;
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14069
|
+
const ticketLinks = tc.story.tickets.map((t) => {
|
|
14070
|
+
if (t.url) {
|
|
14071
|
+
return `[${t.id}](${t.url})`;
|
|
14072
|
+
}
|
|
14073
|
+
if (ticketTemplate) {
|
|
14074
|
+
return `[${t.id}](${ticketTemplate.replace("{ticket}", t.id)})`;
|
|
14075
|
+
}
|
|
14076
|
+
return `\`${t.id}\``;
|
|
14077
|
+
});
|
|
14078
|
+
meta.push(`Tickets: ${ticketLinks.join(", ")}`);
|
|
14012
14079
|
}
|
|
14013
14080
|
const otelMeta = tc.story.meta?.otel;
|
|
14014
14081
|
if (otelMeta?.traceId) {
|
|
@@ -14182,6 +14249,12 @@ var MarkdownFormatter = class {
|
|
|
14182
14249
|
lines.push(`${indent}`);
|
|
14183
14250
|
break;
|
|
14184
14251
|
}
|
|
14252
|
+
if (entry.children && entry.children.length > 0) {
|
|
14253
|
+
const childIndent = indent + " ";
|
|
14254
|
+
for (const child of entry.children) {
|
|
14255
|
+
this.renderDocEntry(lines, child, childIndent);
|
|
14256
|
+
}
|
|
14257
|
+
}
|
|
14185
14258
|
}
|
|
14186
14259
|
/**
|
|
14187
14260
|
* Get status icon for a status.
|
|
@@ -14254,8 +14327,8 @@ function extractFeatureName(testCases, uri) {
|
|
|
14254
14327
|
return tc.titlePath[0];
|
|
14255
14328
|
}
|
|
14256
14329
|
}
|
|
14257
|
-
const
|
|
14258
|
-
return
|
|
14330
|
+
const basename3 = uri.replace(/^.*[\\/]/, "").replace(/\.[^.]+$/, "");
|
|
14331
|
+
return basename3.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
14259
14332
|
}
|
|
14260
14333
|
function synthesizeFeature(uri, testCases) {
|
|
14261
14334
|
const featureName = extractFeatureName(testCases, uri);
|
|
@@ -14867,8 +14940,8 @@ function extractDocAttachments(step) {
|
|
|
14867
14940
|
}
|
|
14868
14941
|
return attachments;
|
|
14869
14942
|
}
|
|
14870
|
-
function guessMediaType(
|
|
14871
|
-
const lower =
|
|
14943
|
+
function guessMediaType(path6) {
|
|
14944
|
+
const lower = path6.toLowerCase();
|
|
14872
14945
|
if (lower.endsWith(".png")) return "image/png";
|
|
14873
14946
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
14874
14947
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -15009,11 +15082,11 @@ var CucumberHtmlFormatter = class {
|
|
|
15009
15082
|
for (const envelope of envelopes) {
|
|
15010
15083
|
const accepted = htmlStream.write(envelope);
|
|
15011
15084
|
if (!accepted) {
|
|
15012
|
-
await new Promise((
|
|
15085
|
+
await new Promise((resolve4) => htmlStream.once("drain", resolve4));
|
|
15013
15086
|
}
|
|
15014
15087
|
}
|
|
15015
|
-
await new Promise((
|
|
15016
|
-
collector.on("finish",
|
|
15088
|
+
await new Promise((resolve4, reject) => {
|
|
15089
|
+
collector.on("finish", resolve4);
|
|
15017
15090
|
collector.on("error", reject);
|
|
15018
15091
|
htmlStream.end();
|
|
15019
15092
|
});
|
|
@@ -15161,7 +15234,7 @@ function buildFlags(baseline, current) {
|
|
|
15161
15234
|
steps: stableJson(baseline.story.steps) !== stableJson(current.story.steps),
|
|
15162
15235
|
docs: stableJson(baselineDocs) !== stableJson(currentDocs),
|
|
15163
15236
|
tags: !compareStringArrays(baseline.tags, current.tags),
|
|
15164
|
-
tickets:
|
|
15237
|
+
tickets: stableJson(baseline.story.tickets ?? []) !== stableJson(current.story.tickets ?? []),
|
|
15165
15238
|
source: baseline.sourceFile !== current.sourceFile || baseline.sourceLine !== current.sourceLine,
|
|
15166
15239
|
duration: baseline.durationMs !== current.durationMs,
|
|
15167
15240
|
attachments: stableJson(baseline.attachments) !== stableJson(current.attachments),
|
|
@@ -15384,7 +15457,7 @@ function renderScenarioCard(scenario) {
|
|
|
15384
15457
|
<dd>${escapeHtml2(before.errorMessage ?? "") || " "}</dd>
|
|
15385
15458
|
${scenario.flags.steps ? `<dt>Steps</dt><dd>${formatSteps(before.steps)}</dd>` : ""}
|
|
15386
15459
|
${scenario.flags.docs ? `<dt>Docs</dt><dd>${formatDocs(before.docs)}</dd>` : ""}
|
|
15387
|
-
${scenario.flags.tickets ? `<dt>Tickets</dt><dd>${escapeHtml2(before.tickets.join(", ")) || " "}</dd>` : ""}
|
|
15460
|
+
${scenario.flags.tickets ? `<dt>Tickets</dt><dd>${escapeHtml2(before.tickets.map((t) => t.id).join(", ")) || " "}</dd>` : ""}
|
|
15388
15461
|
</dl>
|
|
15389
15462
|
</section>
|
|
15390
15463
|
<section>
|
|
@@ -15398,7 +15471,7 @@ function renderScenarioCard(scenario) {
|
|
|
15398
15471
|
<dd>${escapeHtml2(after.errorMessage ?? "") || " "}</dd>
|
|
15399
15472
|
${scenario.flags.steps ? `<dt>Steps</dt><dd>${formatSteps(after.steps)}</dd>` : ""}
|
|
15400
15473
|
${scenario.flags.docs ? `<dt>Docs</dt><dd>${formatDocs(after.docs)}</dd>` : ""}
|
|
15401
|
-
${scenario.flags.tickets ? `<dt>Tickets</dt><dd>${escapeHtml2(after.tickets.join(", ")) || " "}</dd>` : ""}
|
|
15474
|
+
${scenario.flags.tickets ? `<dt>Tickets</dt><dd>${escapeHtml2(after.tickets.map((t) => t.id).join(", ")) || " "}</dd>` : ""}
|
|
15402
15475
|
</dl>
|
|
15403
15476
|
</section>
|
|
15404
15477
|
</div>` : (() => {
|
|
@@ -15412,7 +15485,7 @@ function renderScenarioCard(scenario) {
|
|
|
15412
15485
|
return `<div class="snapshot-detail">
|
|
15413
15486
|
<dl>
|
|
15414
15487
|
${hasTags ? `<dt>Tags</dt><dd>${escapeHtml2(snapshot.tags.join(", "))}</dd>` : ""}
|
|
15415
|
-
${hasTickets ? `<dt>Tickets</dt><dd>${escapeHtml2(snapshot.tickets.join(", "))}</dd>` : ""}
|
|
15488
|
+
${hasTickets ? `<dt>Tickets</dt><dd>${escapeHtml2(snapshot.tickets.map((t) => t.id).join(", "))}</dd>` : ""}
|
|
15416
15489
|
${hasSteps ? `<dt>Steps</dt><dd>${formatSteps(snapshot.steps)}</dd>` : ""}
|
|
15417
15490
|
${hasDocs ? `<dt>Docs</dt><dd>${formatDocs(snapshot.docs)}</dd>` : ""}
|
|
15418
15491
|
</dl>
|
|
@@ -15727,7 +15800,7 @@ function renderScenario2(lines, scenario) {
|
|
|
15727
15800
|
lines.push(`| Docs | ${escapeCell(formatDocs2(before.docs))} | ${escapeCell(formatDocs2(after.docs))} |`);
|
|
15728
15801
|
}
|
|
15729
15802
|
if (scenario.flags.tickets) {
|
|
15730
|
-
lines.push(`| Tickets | ${escapeCell(before.tickets.join(", "))} | ${escapeCell(after.tickets.join(", "))} |`);
|
|
15803
|
+
lines.push(`| Tickets | ${escapeCell(before.tickets.map((t) => t.id).join(", "))} | ${escapeCell(after.tickets.map((t) => t.id).join(", "))} |`);
|
|
15731
15804
|
}
|
|
15732
15805
|
lines.push("");
|
|
15733
15806
|
} else {
|
|
@@ -15743,7 +15816,7 @@ function renderSnapshotDetail(lines, snapshot) {
|
|
|
15743
15816
|
lines.push("");
|
|
15744
15817
|
}
|
|
15745
15818
|
if (snapshot.tickets.length > 0) {
|
|
15746
|
-
lines.push(`**Tickets:** ${snapshot.tickets.join(", ")}`);
|
|
15819
|
+
lines.push(`**Tickets:** ${snapshot.tickets.map((t) => t.id).join(", ")}`);
|
|
15747
15820
|
lines.push("");
|
|
15748
15821
|
}
|
|
15749
15822
|
if (snapshot.steps.length > 0) {
|
|
@@ -15941,6 +16014,110 @@ function selectTestCases(args, deps) {
|
|
|
15941
16014
|
return sortTestCases(selected, sortMode);
|
|
15942
16015
|
}
|
|
15943
16016
|
|
|
16017
|
+
// src/bundler/bundle-assets.ts
|
|
16018
|
+
import * as fs3 from "fs";
|
|
16019
|
+
import * as path3 from "path";
|
|
16020
|
+
|
|
16021
|
+
// src/bundler/scan-html-assets.ts
|
|
16022
|
+
function scanHtmlAssets(html) {
|
|
16023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16024
|
+
const patterns = [
|
|
16025
|
+
/<(?:img|video)\b[^>]*?\bsrc=["']([^"']+)["']/g,
|
|
16026
|
+
/<a\b[^>]*?\bclass=["']attachment["'][^>]*?\bhref=["']([^"']+)["']/g,
|
|
16027
|
+
/<a\b[^>]*?\bhref=["']([^"']+)["'][^>]*?\bclass=["']attachment["']/g
|
|
16028
|
+
];
|
|
16029
|
+
for (const pattern of patterns) {
|
|
16030
|
+
let match;
|
|
16031
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
16032
|
+
const ref = match[1];
|
|
16033
|
+
if (isLocalAssetRef(ref) && !seen.has(ref)) {
|
|
16034
|
+
seen.add(ref);
|
|
16035
|
+
}
|
|
16036
|
+
}
|
|
16037
|
+
}
|
|
16038
|
+
return [...seen];
|
|
16039
|
+
}
|
|
16040
|
+
function isLocalAssetRef(ref) {
|
|
16041
|
+
if (!ref) return false;
|
|
16042
|
+
if (ref.startsWith("data:")) return false;
|
|
16043
|
+
if (ref.startsWith("http://") || ref.startsWith("https://")) return false;
|
|
16044
|
+
if (ref.startsWith("#")) return false;
|
|
16045
|
+
return true;
|
|
16046
|
+
}
|
|
16047
|
+
|
|
16048
|
+
// src/bundler/copy-asset.ts
|
|
16049
|
+
import * as fs2 from "fs";
|
|
16050
|
+
import * as path2 from "path";
|
|
16051
|
+
import * as crypto from "crypto";
|
|
16052
|
+
function copyAsset(sourcePath, assetsDir) {
|
|
16053
|
+
if (!fs2.existsSync(assetsDir)) {
|
|
16054
|
+
fs2.mkdirSync(assetsDir, { recursive: true });
|
|
16055
|
+
}
|
|
16056
|
+
const content = fs2.readFileSync(sourcePath);
|
|
16057
|
+
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
16058
|
+
const ext = path2.extname(sourcePath);
|
|
16059
|
+
const baseName = sanitize(path2.basename(sourcePath, ext));
|
|
16060
|
+
const destName = `${baseName}-${hash}${ext}`;
|
|
16061
|
+
const destPath = path2.join(assetsDir, destName);
|
|
16062
|
+
if (!fs2.existsSync(destPath)) {
|
|
16063
|
+
fs2.copyFileSync(sourcePath, destPath);
|
|
16064
|
+
}
|
|
16065
|
+
return `assets/${destName}`;
|
|
16066
|
+
}
|
|
16067
|
+
function sanitize(name) {
|
|
16068
|
+
return name.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
16069
|
+
}
|
|
16070
|
+
|
|
16071
|
+
// src/bundler/bundle-assets.ts
|
|
16072
|
+
function bundleAssets(htmlPath, options = {}) {
|
|
16073
|
+
const htmlDir = path3.dirname(htmlPath);
|
|
16074
|
+
const assetsDir = path3.join(htmlDir, "assets");
|
|
16075
|
+
let html = fs3.readFileSync(htmlPath, "utf8");
|
|
16076
|
+
const refs = scanHtmlAssets(html);
|
|
16077
|
+
let copiedCount = 0;
|
|
16078
|
+
const missing = [];
|
|
16079
|
+
for (const ref of refs) {
|
|
16080
|
+
const absolutePath = path3.resolve(htmlDir, ref);
|
|
16081
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
16082
|
+
missing.push(ref);
|
|
16083
|
+
continue;
|
|
16084
|
+
}
|
|
16085
|
+
const newRelPath = copyAsset(absolutePath, assetsDir);
|
|
16086
|
+
html = replaceAssetRef(html, ref, newRelPath);
|
|
16087
|
+
copiedCount++;
|
|
16088
|
+
}
|
|
16089
|
+
if (missing.length > 0 && !options.allowMissing) {
|
|
16090
|
+
throw new Error(
|
|
16091
|
+
`Missing asset${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`
|
|
16092
|
+
);
|
|
16093
|
+
}
|
|
16094
|
+
fs3.writeFileSync(htmlPath, html, "utf8");
|
|
16095
|
+
return {
|
|
16096
|
+
copiedCount,
|
|
16097
|
+
missingCount: missing.length,
|
|
16098
|
+
missing
|
|
16099
|
+
};
|
|
16100
|
+
}
|
|
16101
|
+
function replaceAssetRef(html, original, replacement) {
|
|
16102
|
+
const escaped = original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16103
|
+
const srcPattern = new RegExp(
|
|
16104
|
+
`(<(?:img|video)\\b[^>]*?\\bsrc=["'])${escaped}(["'])`,
|
|
16105
|
+
"g"
|
|
16106
|
+
);
|
|
16107
|
+
html = html.replace(srcPattern, `$1${replacement}$2`);
|
|
16108
|
+
const hrefClassFirst = new RegExp(
|
|
16109
|
+
`(<a\\b[^>]*?\\bclass=["']attachment["'][^>]*?\\bhref=["'])${escaped}(["'])`,
|
|
16110
|
+
"g"
|
|
16111
|
+
);
|
|
16112
|
+
html = html.replace(hrefClassFirst, `$1${replacement}$2`);
|
|
16113
|
+
const hrefHrefFirst = new RegExp(
|
|
16114
|
+
`(<a\\b[^>]*?\\bhref=["'])${escaped}(["'][^>]*?\\bclass=["']attachment["'])`,
|
|
16115
|
+
"g"
|
|
16116
|
+
);
|
|
16117
|
+
html = html.replace(hrefHrefFirst, `$1${replacement}$2`);
|
|
16118
|
+
return html;
|
|
16119
|
+
}
|
|
16120
|
+
|
|
15944
16121
|
// src/converters/ndjson-parser.ts
|
|
15945
16122
|
function parseNdjson(ndjson) {
|
|
15946
16123
|
const lines = ndjson.trim().split("\n").filter(Boolean);
|
|
@@ -16967,11 +17144,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
16967
17144
|
const ext = FORMAT_EXTENSIONS[format];
|
|
16968
17145
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
16969
17146
|
if (mode === "aggregated") {
|
|
16970
|
-
return toPosix(
|
|
17147
|
+
return toPosix(path4.join(baseOutputDir, `${effectiveName}${ext}`));
|
|
16971
17148
|
}
|
|
16972
17149
|
const normalizedSource = toPosix(sourceFile);
|
|
16973
|
-
const dirOfSource =
|
|
16974
|
-
let baseName =
|
|
17150
|
+
const dirOfSource = path4.posix.dirname(normalizedSource);
|
|
17151
|
+
let baseName = path4.posix.basename(normalizedSource);
|
|
16975
17152
|
for (const testExt of TEST_EXTENSIONS) {
|
|
16976
17153
|
if (baseName.endsWith(testExt)) {
|
|
16977
17154
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -16980,9 +17157,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
16980
17157
|
}
|
|
16981
17158
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
16982
17159
|
if (colocatedStyle === "adjacent") {
|
|
16983
|
-
return toPosix(
|
|
17160
|
+
return toPosix(path4.posix.join(dirOfSource, fileName));
|
|
16984
17161
|
}
|
|
16985
|
-
return toPosix(
|
|
17162
|
+
return toPosix(path4.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
16986
17163
|
}
|
|
16987
17164
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
16988
17165
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -17081,6 +17258,7 @@ var ReportGenerator = class {
|
|
|
17081
17258
|
mermaidEnabled: options.html?.mermaidEnabled ?? true,
|
|
17082
17259
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17083
17260
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17261
|
+
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17084
17262
|
theme: options.html?.theme ?? "default"
|
|
17085
17263
|
},
|
|
17086
17264
|
junit: {
|
|
@@ -17104,7 +17282,9 @@ var ReportGenerator = class {
|
|
|
17104
17282
|
traceUrlTemplate: options.markdown?.traceUrlTemplate,
|
|
17105
17283
|
includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
|
|
17106
17284
|
customRenderers: options.markdown?.customRenderers
|
|
17107
|
-
}
|
|
17285
|
+
},
|
|
17286
|
+
assetMode: options.assetMode ?? "none",
|
|
17287
|
+
allowMissingAssets: options.allowMissingAssets ?? false
|
|
17108
17288
|
};
|
|
17109
17289
|
}
|
|
17110
17290
|
/**
|
|
@@ -17131,6 +17311,16 @@ var ReportGenerator = class {
|
|
|
17131
17311
|
const paths = await this.generateFormat(filteredRun, format);
|
|
17132
17312
|
results.set(format, paths);
|
|
17133
17313
|
}
|
|
17314
|
+
if (this.options.assetMode === "copy") {
|
|
17315
|
+
const htmlPaths = results.get("html");
|
|
17316
|
+
if (htmlPaths) {
|
|
17317
|
+
for (const htmlPath of htmlPaths) {
|
|
17318
|
+
bundleAssets(htmlPath, {
|
|
17319
|
+
allowMissing: this.options.allowMissingAssets
|
|
17320
|
+
});
|
|
17321
|
+
}
|
|
17322
|
+
}
|
|
17323
|
+
}
|
|
17134
17324
|
return results;
|
|
17135
17325
|
}
|
|
17136
17326
|
/**
|
|
@@ -17148,9 +17338,9 @@ var ReportGenerator = class {
|
|
|
17148
17338
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
17149
17339
|
const ext = FORMAT_EXTENSIONS[format];
|
|
17150
17340
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
17151
|
-
const outputPath = toPosix(
|
|
17341
|
+
const outputPath = toPosix(path4.join(this.options.outputDir, `${effectiveName}${ext}`));
|
|
17152
17342
|
const content = await this.formatContent(run, format);
|
|
17153
|
-
const dir =
|
|
17343
|
+
const dir = path4.dirname(outputPath);
|
|
17154
17344
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
17155
17345
|
await this.deps.writeFile(outputPath, content);
|
|
17156
17346
|
return [outputPath];
|
|
@@ -17162,7 +17352,7 @@ var ReportGenerator = class {
|
|
|
17162
17352
|
testCases
|
|
17163
17353
|
};
|
|
17164
17354
|
const content = await this.formatContent(groupRun, format);
|
|
17165
|
-
const dir =
|
|
17355
|
+
const dir = path4.dirname(outputPath);
|
|
17166
17356
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
17167
17357
|
await this.deps.writeFile(outputPath, content);
|
|
17168
17358
|
writtenPaths.push(outputPath);
|
|
@@ -17191,7 +17381,8 @@ var ReportGenerator = class {
|
|
|
17191
17381
|
syntaxHighlighting: this.options.html.syntaxHighlighting,
|
|
17192
17382
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17193
17383
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17194
|
-
permalinkBaseUrl: this.options.html.permalinkBaseUrl
|
|
17384
|
+
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17385
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
17195
17386
|
});
|
|
17196
17387
|
return formatter.format(run);
|
|
17197
17388
|
}
|
|
@@ -17256,7 +17447,7 @@ async function generateRunComparison(args) {
|
|
|
17256
17447
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
17257
17448
|
for (const format of args.formats) {
|
|
17258
17449
|
const ext = format === "html" ? ".html" : ".md";
|
|
17259
|
-
const outputPath = toPosix(
|
|
17450
|
+
const outputPath = toPosix(path4.join(outputDir, `${outputName}${ext}`));
|
|
17260
17451
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
17261
17452
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
17262
17453
|
files.push(outputPath);
|
|
@@ -17312,6 +17503,9 @@ OPTIONS
|
|
|
17312
17503
|
--html-no-mermaid Disable mermaid diagrams in HTML (enabled by default)
|
|
17313
17504
|
--html-no-markdown Disable markdown parsing in HTML (enabled by default)
|
|
17314
17505
|
--html-permalink-base-url <url> Base URL for source permalinks in HTML (e.g. "https://github.com/org/repo/blob/main")
|
|
17506
|
+
--html-ticket-url-template <url> URL template for ticket links in HTML (use {ticket} as placeholder)
|
|
17507
|
+
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
17508
|
+
--allow-missing-assets Warn on missing assets instead of failing
|
|
17315
17509
|
--stdin Read JSON from stdin instead of file
|
|
17316
17510
|
--json-summary Print machine-parsable JSON summary
|
|
17317
17511
|
--baseline <path|auto> Compare baseline file, or auto-pick a prior run for compare
|
|
@@ -17391,6 +17585,7 @@ function parseCliArgs(argv) {
|
|
|
17391
17585
|
"html-no-mermaid": { type: "boolean", default: false },
|
|
17392
17586
|
"html-no-markdown": { type: "boolean", default: false },
|
|
17393
17587
|
"html-permalink-base-url": { type: "string" },
|
|
17588
|
+
"html-ticket-url-template": { type: "string" },
|
|
17394
17589
|
stdin: { type: "boolean", default: false },
|
|
17395
17590
|
"json-summary": { type: "boolean", default: false },
|
|
17396
17591
|
"emit-canonical": { type: "string" },
|
|
@@ -17407,6 +17602,8 @@ function parseCliArgs(argv) {
|
|
|
17407
17602
|
"webhook-hmac-secret": { type: "string" },
|
|
17408
17603
|
"webhook-hmac-header": { type: "string" },
|
|
17409
17604
|
"webhook-hmac-timestamp": { type: "boolean", default: false },
|
|
17605
|
+
"asset-mode": { type: "string", default: "none" },
|
|
17606
|
+
"allow-missing-assets": { type: "boolean", default: false },
|
|
17410
17607
|
"pr-summary": { type: "boolean", default: false },
|
|
17411
17608
|
"pr-summary-file": { type: "string" },
|
|
17412
17609
|
help: { type: "boolean", default: false }
|
|
@@ -17516,6 +17713,12 @@ function parseCliArgs(argv) {
|
|
|
17516
17713
|
console.error(`Error: --sort-test-cases must be id, source, or none, got "${sortTestCasesRaw}".`);
|
|
17517
17714
|
process.exit(EXIT_USAGE);
|
|
17518
17715
|
}
|
|
17716
|
+
const assetModeRaw = values["asset-mode"];
|
|
17717
|
+
const validAssetModes = /* @__PURE__ */ new Set(["none", "copy"]);
|
|
17718
|
+
if (!validAssetModes.has(assetModeRaw)) {
|
|
17719
|
+
console.error(`Error: --asset-mode must be "none" or "copy", got "${assetModeRaw}".`);
|
|
17720
|
+
process.exit(EXIT_USAGE);
|
|
17721
|
+
}
|
|
17519
17722
|
return {
|
|
17520
17723
|
subcommand,
|
|
17521
17724
|
inputFile,
|
|
@@ -17541,6 +17744,7 @@ function parseCliArgs(argv) {
|
|
|
17541
17744
|
htmlNoMermaid: values["html-no-mermaid"],
|
|
17542
17745
|
htmlNoMarkdown: values["html-no-markdown"],
|
|
17543
17746
|
htmlPermalinkBaseUrl: values["html-permalink-base-url"],
|
|
17747
|
+
htmlTicketUrlTemplate: values["html-ticket-url-template"],
|
|
17544
17748
|
jsonSummary: values["json-summary"],
|
|
17545
17749
|
emitCanonical: values["emit-canonical"],
|
|
17546
17750
|
slackWebhook,
|
|
@@ -17556,6 +17760,8 @@ function parseCliArgs(argv) {
|
|
|
17556
17760
|
webhookHmacSecret: values["webhook-hmac-secret"],
|
|
17557
17761
|
webhookHmacHeader: values["webhook-hmac-header"] ?? "X-Signature",
|
|
17558
17762
|
webhookHmacTimestamp: values["webhook-hmac-timestamp"],
|
|
17763
|
+
assetMode: assetModeRaw,
|
|
17764
|
+
allowMissingAssets: values["allow-missing-assets"],
|
|
17559
17765
|
prSummary: values["pr-summary"],
|
|
17560
17766
|
prSummaryFile: values["pr-summary-file"]
|
|
17561
17767
|
};
|
|
@@ -17564,27 +17770,27 @@ async function readInput(args) {
|
|
|
17564
17770
|
if (args.stdin) {
|
|
17565
17771
|
return readStdin();
|
|
17566
17772
|
}
|
|
17567
|
-
const filePath =
|
|
17568
|
-
if (!
|
|
17773
|
+
const filePath = path5.resolve(args.inputFile);
|
|
17774
|
+
if (!fs5.existsSync(filePath)) {
|
|
17569
17775
|
console.error(`Error: File not found: ${filePath}`);
|
|
17570
17776
|
process.exit(EXIT_USAGE);
|
|
17571
17777
|
}
|
|
17572
|
-
return
|
|
17778
|
+
return fs5.readFileSync(filePath, "utf8");
|
|
17573
17779
|
}
|
|
17574
17780
|
function readFileInput(filePath) {
|
|
17575
|
-
const resolved =
|
|
17576
|
-
if (!
|
|
17781
|
+
const resolved = path5.resolve(filePath);
|
|
17782
|
+
if (!fs5.existsSync(resolved)) {
|
|
17577
17783
|
console.error(`Error: File not found: ${resolved}`);
|
|
17578
17784
|
process.exit(EXIT_USAGE);
|
|
17579
17785
|
}
|
|
17580
|
-
return
|
|
17786
|
+
return fs5.readFileSync(resolved, "utf8");
|
|
17581
17787
|
}
|
|
17582
17788
|
function readStdin() {
|
|
17583
|
-
return new Promise((
|
|
17789
|
+
return new Promise((resolve4, reject) => {
|
|
17584
17790
|
const chunks = [];
|
|
17585
17791
|
process.stdin.setEncoding("utf8");
|
|
17586
17792
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17587
|
-
process.stdin.on("end", () =>
|
|
17793
|
+
process.stdin.on("end", () => resolve4(chunks.join("")));
|
|
17588
17794
|
process.stdin.on("error", reject);
|
|
17589
17795
|
});
|
|
17590
17796
|
}
|
|
@@ -17710,14 +17916,14 @@ function tryNormalizeRunFromText(text, args) {
|
|
|
17710
17916
|
}
|
|
17711
17917
|
}
|
|
17712
17918
|
function listBaselineCandidates(currentFile, args) {
|
|
17713
|
-
const baselineDir =
|
|
17714
|
-
const currentResolved =
|
|
17715
|
-
if (!
|
|
17919
|
+
const baselineDir = path5.resolve(args.baselineDir ?? path5.dirname(currentFile));
|
|
17920
|
+
const currentResolved = path5.resolve(currentFile);
|
|
17921
|
+
if (!fs5.existsSync(baselineDir)) {
|
|
17716
17922
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
17717
17923
|
process.exit(EXIT_USAGE);
|
|
17718
17924
|
}
|
|
17719
|
-
const entries =
|
|
17720
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
17925
|
+
const entries = fs5.readdirSync(baselineDir, { withFileTypes: true });
|
|
17926
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path5.join(baselineDir, entry.name)).filter((candidate) => path5.resolve(candidate) !== currentResolved).filter(
|
|
17721
17927
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
17722
17928
|
);
|
|
17723
17929
|
}
|
|
@@ -17725,14 +17931,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
17725
17931
|
const candidates = listBaselineCandidates(currentFile, args);
|
|
17726
17932
|
const comparable = [];
|
|
17727
17933
|
for (const candidate of candidates) {
|
|
17728
|
-
const run = tryNormalizeRunFromText(
|
|
17934
|
+
const run = tryNormalizeRunFromText(fs5.readFileSync(candidate, "utf8"), args);
|
|
17729
17935
|
if (run) {
|
|
17730
17936
|
comparable.push({ file: candidate, run });
|
|
17731
17937
|
}
|
|
17732
17938
|
}
|
|
17733
17939
|
if (comparable.length === 0) {
|
|
17734
17940
|
console.error(
|
|
17735
|
-
`Error: no compatible baseline files found in ${
|
|
17941
|
+
`Error: no compatible baseline files found in ${path5.resolve(args.baselineDir ?? path5.dirname(currentFile))}.`
|
|
17736
17942
|
);
|
|
17737
17943
|
process.exit(EXIT_USAGE);
|
|
17738
17944
|
}
|
|
@@ -17813,9 +18019,9 @@ async function main() {
|
|
|
17813
18019
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
17814
18020
|
}
|
|
17815
18021
|
if (args.emitCanonical) {
|
|
17816
|
-
const outPath =
|
|
17817
|
-
|
|
17818
|
-
|
|
18022
|
+
const outPath = path5.resolve(args.emitCanonical);
|
|
18023
|
+
fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
|
|
18024
|
+
fs5.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
17819
18025
|
}
|
|
17820
18026
|
try {
|
|
17821
18027
|
const result = await generateReports(run, args);
|
|
@@ -17871,9 +18077,9 @@ ${msg}`);
|
|
|
17871
18077
|
}
|
|
17872
18078
|
const run = data;
|
|
17873
18079
|
if (args.emitCanonical) {
|
|
17874
|
-
const outPath =
|
|
17875
|
-
|
|
17876
|
-
|
|
18080
|
+
const outPath = path5.resolve(args.emitCanonical);
|
|
18081
|
+
fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
|
|
18082
|
+
fs5.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
17877
18083
|
}
|
|
17878
18084
|
try {
|
|
17879
18085
|
const result = await generateReports(run, args);
|
|
@@ -17928,9 +18134,9 @@ ${msg}`);
|
|
|
17928
18134
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
17929
18135
|
}
|
|
17930
18136
|
if (args.emitCanonical) {
|
|
17931
|
-
const outPath =
|
|
17932
|
-
|
|
17933
|
-
|
|
18137
|
+
const outPath = path5.resolve(args.emitCanonical);
|
|
18138
|
+
fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
|
|
18139
|
+
fs5.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
17934
18140
|
}
|
|
17935
18141
|
try {
|
|
17936
18142
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
@@ -17987,13 +18193,13 @@ async function dispatchNotifications(run, args) {
|
|
|
17987
18193
|
}
|
|
17988
18194
|
function runHistoryPipeline(run, args) {
|
|
17989
18195
|
if (!args.historyFile) return;
|
|
17990
|
-
const historyPath =
|
|
18196
|
+
const historyPath = path5.resolve(args.historyFile);
|
|
17991
18197
|
const store = loadHistory(
|
|
17992
18198
|
{ filePath: historyPath },
|
|
17993
18199
|
{
|
|
17994
18200
|
readFile: (p) => {
|
|
17995
18201
|
try {
|
|
17996
|
-
return
|
|
18202
|
+
return fs5.readFileSync(p, "utf8");
|
|
17997
18203
|
} catch {
|
|
17998
18204
|
return void 0;
|
|
17999
18205
|
}
|
|
@@ -18006,11 +18212,11 @@ function runHistoryPipeline(run, args) {
|
|
|
18006
18212
|
run,
|
|
18007
18213
|
maxRuns: args.maxHistoryRuns
|
|
18008
18214
|
});
|
|
18009
|
-
const dir =
|
|
18010
|
-
|
|
18215
|
+
const dir = path5.dirname(historyPath);
|
|
18216
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
18011
18217
|
saveHistory(
|
|
18012
18218
|
{ filePath: historyPath, store: updated },
|
|
18013
|
-
{ writeFile: (p, content) =>
|
|
18219
|
+
{ writeFile: (p, content) => fs5.writeFileSync(p, content, "utf8") }
|
|
18014
18220
|
);
|
|
18015
18221
|
let metricsCount = 0;
|
|
18016
18222
|
for (const testId of Object.keys(updated.tests)) {
|
|
@@ -18040,8 +18246,11 @@ async function generateReports(run, args, _droppedMissingStory = 0) {
|
|
|
18040
18246
|
syntaxHighlighting: !args.htmlNoSyntaxHighlighting,
|
|
18041
18247
|
mermaidEnabled: !args.htmlNoMermaid,
|
|
18042
18248
|
markdownEnabled: !args.htmlNoMarkdown,
|
|
18043
|
-
permalinkBaseUrl: args.htmlPermalinkBaseUrl
|
|
18044
|
-
|
|
18249
|
+
permalinkBaseUrl: args.htmlPermalinkBaseUrl,
|
|
18250
|
+
ticketUrlTemplate: args.htmlTicketUrlTemplate
|
|
18251
|
+
},
|
|
18252
|
+
assetMode: args.assetMode,
|
|
18253
|
+
allowMissingAssets: args.allowMissingAssets
|
|
18045
18254
|
});
|
|
18046
18255
|
const resultMap = await generator.generate(run);
|
|
18047
18256
|
const files = [];
|
|
@@ -18103,9 +18312,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
18103
18312
|
function printCompareResult(result, args, startMs) {
|
|
18104
18313
|
const durationMs = Date.now() - startMs;
|
|
18105
18314
|
if (result.prSummary && args.prSummaryFile) {
|
|
18106
|
-
const outputPath =
|
|
18107
|
-
|
|
18108
|
-
|
|
18315
|
+
const outputPath = path5.resolve(args.prSummaryFile);
|
|
18316
|
+
fs5.mkdirSync(path5.dirname(outputPath), { recursive: true });
|
|
18317
|
+
fs5.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
18109
18318
|
}
|
|
18110
18319
|
if (args.jsonSummary) {
|
|
18111
18320
|
console.log(
|