executable-stories-formatters 0.7.14 → 0.7.15

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.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "util";
5
5
  import * as fs8 from "fs";
6
- import * as path8 from "path";
6
+ import * as path9 from "path";
7
7
 
8
8
  // src/validation/schema-validator.ts
9
9
  import Ajv from "ajv/dist/2020.js";
@@ -492,17 +492,17 @@ function validateRawRun(data) {
492
492
  return { valid: true, errors: [] };
493
493
  }
494
494
  const errors = (validate.errors ?? []).map((err) => {
495
- const path9 = err.instancePath || "/";
495
+ const path10 = err.instancePath || "/";
496
496
  const message = err.message ?? "unknown error";
497
497
  if (err.keyword === "additionalProperties") {
498
498
  const extra = err.params.additionalProperty;
499
- return `${path9}: ${message} \u2014 '${extra}'`;
499
+ return `${path10}: ${message} \u2014 '${extra}'`;
500
500
  }
501
501
  if (err.keyword === "enum") {
502
502
  const allowed = err.params.allowedValues;
503
- return `${path9}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
503
+ return `${path10}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
504
504
  }
505
- return `${path9}: ${message}`;
505
+ return `${path10}: ${message}`;
506
506
  });
507
507
  return { valid: false, errors };
508
508
  }
@@ -976,7 +976,7 @@ ${result.errors.join("\n")}`);
976
976
 
977
977
  // src/index.ts
978
978
  import "fs";
979
- import * as path6 from "path";
979
+ import * as path7 from "path";
980
980
  import * as fsPromises from "fs/promises";
981
981
 
982
982
  // src/converters/acl/lines.ts
@@ -1355,9 +1355,298 @@ ${doc.markdown}`,
1355
1355
  }
1356
1356
  };
1357
1357
 
1358
+ // src/converters/story-report.ts
1359
+ import { posix as path2 } from "path";
1360
+
1361
+ // src/types/story-report.ts
1362
+ var STORY_REPORT_SCHEMA_VERSION = "1.0";
1363
+
1364
+ // src/converters/story-report.ts
1365
+ function reportSlug(text2) {
1366
+ return text2.toLowerCase().replace(/[/\\.]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1367
+ }
1368
+ function toRelativeSourceFile(sourceFile, projectRoot) {
1369
+ if (!sourceFile) return sourceFile;
1370
+ const normalized = sourceFile.split(path2.sep).join("/");
1371
+ const root = projectRoot.split(path2.sep).join("/").replace(/\/$/, "");
1372
+ if (root && normalized.startsWith(root + "/")) return normalized.slice(root.length + 1);
1373
+ return normalized;
1374
+ }
1375
+ function fileBasenameTitle(sourceFile) {
1376
+ const base = sourceFile.split("/").pop() ?? sourceFile;
1377
+ return base.replace(/\.(story\.)?(test|spec)\.[tj]sx?$/, "").replace(/\.[tj]sx?$/, "");
1378
+ }
1379
+ function emptySummary() {
1380
+ return { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0, durationMs: 0 };
1381
+ }
1382
+ function addToSummary(summary, status, durationMs) {
1383
+ summary.total += 1;
1384
+ summary[status] += 1;
1385
+ summary.durationMs += durationMs;
1386
+ }
1387
+ function isKeyword(value) {
1388
+ return value === "Given" || value === "When" || value === "Then" || value === "And" || value === "But";
1389
+ }
1390
+ function copyDocEntries(entries) {
1391
+ if (!entries || entries.length === 0) return [];
1392
+ return entries.map(copyDocEntry);
1393
+ }
1394
+ function copyDocEntry(entry) {
1395
+ const children = entry.children ? { children: copyDocEntries(entry.children) } : {};
1396
+ switch (entry.kind) {
1397
+ case "note":
1398
+ return { kind: "note", text: entry.text, phase: entry.phase, ...children };
1399
+ case "tag":
1400
+ return { kind: "tag", names: [...entry.names], phase: entry.phase, ...children };
1401
+ case "kv":
1402
+ return { kind: "kv", label: entry.label, value: entry.value, phase: entry.phase, ...children };
1403
+ case "code":
1404
+ return {
1405
+ kind: "code",
1406
+ label: entry.label,
1407
+ content: entry.content,
1408
+ ...entry.lang ? { lang: entry.lang } : {},
1409
+ phase: entry.phase,
1410
+ ...children
1411
+ };
1412
+ case "table":
1413
+ return {
1414
+ kind: "table",
1415
+ label: entry.label,
1416
+ columns: [...entry.columns],
1417
+ rows: entry.rows.map((r) => [...r]),
1418
+ phase: entry.phase,
1419
+ ...children
1420
+ };
1421
+ case "link":
1422
+ return { kind: "link", label: entry.label, url: entry.url, phase: entry.phase, ...children };
1423
+ case "section":
1424
+ return { kind: "section", title: entry.title, markdown: entry.markdown, phase: entry.phase, ...children };
1425
+ case "mermaid":
1426
+ return {
1427
+ kind: "mermaid",
1428
+ code: entry.code,
1429
+ ...entry.title ? { title: entry.title } : {},
1430
+ phase: entry.phase,
1431
+ ...children
1432
+ };
1433
+ case "screenshot":
1434
+ return {
1435
+ kind: "screenshot",
1436
+ path: entry.path,
1437
+ ...entry.alt ? { alt: entry.alt } : {},
1438
+ phase: entry.phase,
1439
+ ...children
1440
+ };
1441
+ case "custom":
1442
+ return {
1443
+ kind: "custom",
1444
+ type: entry.type,
1445
+ data: entry.data,
1446
+ phase: entry.phase,
1447
+ ...children
1448
+ };
1449
+ }
1450
+ }
1451
+ function buildStep(args) {
1452
+ const step = {
1453
+ id: `${args.scenarioId}--step-${args.index}`,
1454
+ index: args.index,
1455
+ keyword: args.keyword,
1456
+ text: args.text,
1457
+ status: args.status,
1458
+ durationMs: args.durationMs,
1459
+ docEntries: args.docEntries
1460
+ };
1461
+ if (args.errorMessage !== void 0) step.errorMessage = args.errorMessage;
1462
+ if (args.mode !== void 0) step.mode = args.mode;
1463
+ return step;
1464
+ }
1465
+ function buildSteps(scenarioId, tc) {
1466
+ const declared = tc.story.steps ?? [];
1467
+ const results = tc.stepResults;
1468
+ const max = Math.max(declared.length, results.length);
1469
+ const steps = [];
1470
+ for (let i = 0; i < max; i++) {
1471
+ const decl = declared[i];
1472
+ const res = results[i];
1473
+ const keywordSource = decl?.keyword;
1474
+ const keyword = keywordSource && isKeyword(keywordSource) ? keywordSource : "Given";
1475
+ const text2 = decl?.text ?? "";
1476
+ const status = res?.status ?? "pending";
1477
+ const durationMs = res?.durationMs ?? decl?.durationMs ?? 0;
1478
+ const docEntries = copyDocEntries(decl?.docs);
1479
+ steps.push(buildStep({
1480
+ scenarioId,
1481
+ index: i,
1482
+ keyword,
1483
+ text: text2,
1484
+ status,
1485
+ durationMs,
1486
+ ...res?.errorMessage !== void 0 ? { errorMessage: res.errorMessage } : {},
1487
+ ...decl?.mode !== void 0 ? { mode: decl.mode } : {},
1488
+ docEntries
1489
+ }));
1490
+ }
1491
+ return steps;
1492
+ }
1493
+ function buildAttachments(tc) {
1494
+ return tc.attachments.map((a) => ({
1495
+ name: a.name,
1496
+ mediaType: a.mediaType,
1497
+ body: a.body,
1498
+ contentEncoding: a.contentEncoding
1499
+ }));
1500
+ }
1501
+ function buildScenario(tc, featureId) {
1502
+ const titleRaw = tc.story.scenario?.trim() || "(untitled scenario)";
1503
+ const id = `${featureId}--${reportSlug(titleRaw) || `case-${tc.id}`}`;
1504
+ const steps = buildSteps(id, tc);
1505
+ const scenario = {
1506
+ id,
1507
+ title: titleRaw,
1508
+ status: tc.status,
1509
+ durationMs: tc.durationMs,
1510
+ tags: [...tc.tags],
1511
+ retry: tc.retry,
1512
+ retries: tc.retries,
1513
+ docEntries: copyDocEntries(tc.story.docs),
1514
+ steps,
1515
+ attachments: buildAttachments(tc)
1516
+ };
1517
+ if (tc.sourceLine && tc.sourceLine > 0) scenario.sourceLine = tc.sourceLine;
1518
+ if (tc.errorMessage !== void 0) scenario.errorMessage = tc.errorMessage;
1519
+ if (tc.errorStack !== void 0) scenario.errorStack = tc.errorStack;
1520
+ const tickets = tc.story.tickets;
1521
+ if (tickets && tickets.length > 0) {
1522
+ scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
1523
+ }
1524
+ return scenario;
1525
+ }
1526
+ function deriveFeatureTitle(group, relSourceFile) {
1527
+ for (const tc of group) {
1528
+ const head = tc.titlePath?.[0];
1529
+ if (head && head.trim()) return head.trim();
1530
+ }
1531
+ return fileBasenameTitle(relSourceFile);
1532
+ }
1533
+ function compareScenarios(a, b) {
1534
+ const aLine = a.sourceLine ?? Number.POSITIVE_INFINITY;
1535
+ const bLine = b.sourceLine ?? Number.POSITIVE_INFINITY;
1536
+ if (aLine !== bLine) return aLine - bLine;
1537
+ return a.title.localeCompare(b.title);
1538
+ }
1539
+ function buildFeature(relSourceFile, group) {
1540
+ const id = `feature-${reportSlug(relSourceFile.replace(/\.[^.]+$/, "")) || "untitled"}`;
1541
+ const title = deriveFeatureTitle(group, relSourceFile);
1542
+ const summary = emptySummary();
1543
+ const scenarios = [];
1544
+ for (const tc of group) {
1545
+ const scenario = buildScenario(tc, id);
1546
+ scenarios.push(scenario);
1547
+ addToSummary(summary, scenario.status, scenario.durationMs);
1548
+ }
1549
+ scenarios.sort(compareScenarios);
1550
+ return { id, title, sourceFile: relSourceFile, summary, scenarios };
1551
+ }
1552
+ function ensureUniqueFeatureIds(features) {
1553
+ const seen = /* @__PURE__ */ new Map();
1554
+ for (const f of features) {
1555
+ const count = seen.get(f.id) ?? 0;
1556
+ if (count > 0) f.id = `${f.id}-${count + 1}`;
1557
+ seen.set(f.id, count + 1);
1558
+ }
1559
+ }
1560
+ function ensureUniqueScenarioIds(feature) {
1561
+ const seen = /* @__PURE__ */ new Map();
1562
+ for (const s of feature.scenarios) {
1563
+ const count = seen.get(s.id) ?? 0;
1564
+ if (count > 0) {
1565
+ const newId = `${s.id}-${count + 1}`;
1566
+ for (const step of s.steps) {
1567
+ step.id = step.id.replace(s.id, newId);
1568
+ }
1569
+ s.id = newId;
1570
+ }
1571
+ seen.set(s.id, count + 1);
1572
+ }
1573
+ }
1574
+ function toStoryReport(run) {
1575
+ const groups = /* @__PURE__ */ new Map();
1576
+ for (const tc of run.testCases) {
1577
+ const rel = toRelativeSourceFile(tc.sourceFile, run.projectRoot);
1578
+ const existing = groups.get(rel);
1579
+ if (existing) existing.push(tc);
1580
+ else groups.set(rel, [tc]);
1581
+ }
1582
+ const features = [];
1583
+ for (const [rel, group] of groups) {
1584
+ features.push(buildFeature(rel, group));
1585
+ }
1586
+ features.sort((a, b) => a.title.localeCompare(b.title));
1587
+ ensureUniqueFeatureIds(features);
1588
+ for (const f of features) ensureUniqueScenarioIds(f);
1589
+ const summary = emptySummary();
1590
+ for (const f of features) {
1591
+ summary.total += f.summary.total;
1592
+ summary.passed += f.summary.passed;
1593
+ summary.failed += f.summary.failed;
1594
+ summary.skipped += f.summary.skipped;
1595
+ summary.pending += f.summary.pending;
1596
+ summary.durationMs += f.summary.durationMs;
1597
+ }
1598
+ const report = {
1599
+ schemaVersion: STORY_REPORT_SCHEMA_VERSION,
1600
+ runId: run.runId,
1601
+ startedAtMs: run.startedAtMs,
1602
+ finishedAtMs: run.finishedAtMs,
1603
+ durationMs: run.durationMs,
1604
+ projectRoot: run.projectRoot,
1605
+ summary,
1606
+ features
1607
+ };
1608
+ if (run.packageVersion) report.packageVersion = run.packageVersion;
1609
+ if (run.gitSha) report.gitSha = run.gitSha;
1610
+ if (run.ci) {
1611
+ const ci = { name: run.ci.name };
1612
+ if (run.ci.url) ci.url = run.ci.url;
1613
+ if (run.ci.buildNumber) ci.buildNumber = run.ci.buildNumber;
1614
+ if (run.ci.branch) ci.branch = run.ci.branch;
1615
+ if (run.ci.commitSha) ci.commitSha = run.ci.commitSha;
1616
+ if (run.ci.prNumber) ci.prNumber = run.ci.prNumber;
1617
+ report.ci = ci;
1618
+ }
1619
+ if (run.coverage) {
1620
+ const cov = {};
1621
+ if (run.coverage.linesPct !== void 0) cov.linesPct = run.coverage.linesPct;
1622
+ if (run.coverage.branchesPct !== void 0) cov.branchesPct = run.coverage.branchesPct;
1623
+ if (run.coverage.functionsPct !== void 0) cov.functionsPct = run.coverage.functionsPct;
1624
+ if (run.coverage.statementsPct !== void 0) cov.statementsPct = run.coverage.statementsPct;
1625
+ report.coverage = cov;
1626
+ }
1627
+ return report;
1628
+ }
1629
+
1630
+ // src/formatters/story-report-json.ts
1631
+ var StoryReportJsonFormatter = class {
1632
+ options;
1633
+ constructor(options = {}) {
1634
+ this.options = {
1635
+ pretty: options.pretty ?? true
1636
+ };
1637
+ }
1638
+ toReport(run) {
1639
+ return toStoryReport(run);
1640
+ }
1641
+ format(run) {
1642
+ const report = toStoryReport(run);
1643
+ return this.options.pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
1644
+ }
1645
+ };
1646
+
1358
1647
  // src/formatters/html/renderers/index.ts
1359
1648
  import * as fs2 from "fs";
1360
- import * as path2 from "path";
1649
+ import * as path3 from "path";
1361
1650
 
1362
1651
  // src/formatters/html/template.ts
1363
1652
  var JS_THEME = `
@@ -2166,6 +2455,80 @@ function escapeHtml(str) {
2166
2455
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
2167
2456
  }
2168
2457
 
2458
+ // src/theme/tokens.ts
2459
+ var ES_THEME_TOKENS_CSS = `
2460
+ :root,
2461
+ [data-theme="light"] {
2462
+ --es-color-bg: #ffffff;
2463
+ --es-color-fg: #111827;
2464
+ --es-color-muted: #6b7280;
2465
+ --es-color-border: #e5e7eb;
2466
+ --es-color-surface: #f9fafb;
2467
+ --es-color-link: #2563eb;
2468
+ --es-color-passed: #16a34a;
2469
+ --es-color-failed: #dc2626;
2470
+ --es-color-skipped: #9ca3af;
2471
+ --es-color-pending: #d97706;
2472
+ --es-color-passed-bg: #f0fdf4;
2473
+ --es-color-failed-bg: #fef2f2;
2474
+ --es-color-skipped-bg: #f3f4f6;
2475
+ --es-color-pending-bg: #fffbeb;
2476
+ --es-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2477
+ --es-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
2478
+ --es-size-base: 1rem;
2479
+ --es-size-sm: 0.875rem;
2480
+ --es-size-xs: 0.75rem;
2481
+ --es-size-h1: 1.875rem;
2482
+ --es-size-h2: 1.5rem;
2483
+ --es-size-h3: 1.25rem;
2484
+ --es-space-1: 0.25rem;
2485
+ --es-space-2: 0.5rem;
2486
+ --es-space-3: 0.75rem;
2487
+ --es-space-4: 1rem;
2488
+ --es-space-6: 1.5rem;
2489
+ --es-space-8: 2rem;
2490
+ --es-radius: 0.5rem;
2491
+ --es-line: 1.6;
2492
+ --es-measure: 72ch;
2493
+ }
2494
+
2495
+ @media (prefers-color-scheme: dark) {
2496
+ :root {
2497
+ --es-color-bg: #0b0f17;
2498
+ --es-color-fg: #e5e7eb;
2499
+ --es-color-muted: #9ca3af;
2500
+ --es-color-border: #1f2937;
2501
+ --es-color-surface: #111827;
2502
+ --es-color-link: #60a5fa;
2503
+ --es-color-passed: #4ade80;
2504
+ --es-color-failed: #f87171;
2505
+ --es-color-skipped: #6b7280;
2506
+ --es-color-pending: #fbbf24;
2507
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
2508
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
2509
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
2510
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
2511
+ }
2512
+ }
2513
+
2514
+ [data-theme="dark"] {
2515
+ --es-color-bg: #0b0f17;
2516
+ --es-color-fg: #e5e7eb;
2517
+ --es-color-muted: #9ca3af;
2518
+ --es-color-border: #1f2937;
2519
+ --es-color-surface: #111827;
2520
+ --es-color-link: #60a5fa;
2521
+ --es-color-passed: #4ade80;
2522
+ --es-color-failed: #f87171;
2523
+ --es-color-skipped: #6b7280;
2524
+ --es-color-pending: #fbbf24;
2525
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
2526
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
2527
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
2528
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
2529
+ }
2530
+ `.trim();
2531
+
2169
2532
  // src/formatters/html/styles.ts
2170
2533
  var CSS_STYLES = `
2171
2534
  /* ============================================================================
@@ -2173,6 +2536,14 @@ var CSS_STYLES = `
2173
2536
  ============================================================================ */
2174
2537
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
2175
2538
 
2539
+ /* ============================================================================
2540
+ executable-stories canonical tokens (--es-*).
2541
+ Shared with executable-stories-react. Override on :root or any ancestor of
2542
+ the report to re-color both the standalone HTML and the React component.
2543
+ ============================================================================ */
2544
+ ${ES_THEME_TOKENS_CSS}
2545
+
2546
+
2176
2547
  /* ============================================================================
2177
2548
  CSS Custom Properties - Light Mode (Default)
2178
2549
  Cucumber-branded shadcn/ui base theme
@@ -14415,7 +14786,7 @@ var SCREENSHOT_MIME_BY_EXT = {
14415
14786
  };
14416
14787
  function readScreenshotAsDataUri(filePath) {
14417
14788
  try {
14418
- const ext = path2.extname(filePath).slice(1).toLowerCase();
14789
+ const ext = path3.extname(filePath).slice(1).toLowerCase();
14419
14790
  const mime = SCREENSHOT_MIME_BY_EXT[ext];
14420
14791
  if (!mime) return void 0;
14421
14792
  if (!fs2.existsSync(filePath)) return void 0;
@@ -15892,8 +16263,8 @@ function extractDocAttachments(step) {
15892
16263
  }
15893
16264
  return attachments;
15894
16265
  }
15895
- function guessMediaType(path9) {
15896
- const lower = path9.toLowerCase();
16266
+ function guessMediaType(path10) {
16267
+ const lower = path10.toLowerCase();
15897
16268
  if (lower.endsWith(".png")) return "image/png";
15898
16269
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15899
16270
  if (lower.endsWith(".gif")) return "image/gif";
@@ -16968,7 +17339,7 @@ function selectTestCases(args, deps) {
16968
17339
 
16969
17340
  // src/bundler/bundle-assets.ts
16970
17341
  import * as fs4 from "fs";
16971
- import * as path4 from "path";
17342
+ import * as path5 from "path";
16972
17343
 
16973
17344
  // src/bundler/scan-html-assets.ts
16974
17345
  function scanHtmlAssets(html) {
@@ -16999,7 +17370,7 @@ function isLocalAssetRef(ref) {
16999
17370
 
17000
17371
  // src/bundler/copy-asset.ts
17001
17372
  import * as fs3 from "fs";
17002
- import * as path3 from "path";
17373
+ import * as path4 from "path";
17003
17374
  import * as crypto from "crypto";
17004
17375
  function copyAsset(sourcePath, assetsDir) {
17005
17376
  if (!fs3.existsSync(assetsDir)) {
@@ -17007,10 +17378,10 @@ function copyAsset(sourcePath, assetsDir) {
17007
17378
  }
17008
17379
  const content = fs3.readFileSync(sourcePath);
17009
17380
  const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
17010
- const ext = path3.extname(sourcePath);
17011
- const baseName = sanitize(path3.basename(sourcePath, ext));
17381
+ const ext = path4.extname(sourcePath);
17382
+ const baseName = sanitize(path4.basename(sourcePath, ext));
17012
17383
  const destName = `${baseName}-${hash}${ext}`;
17013
- const destPath = path3.join(assetsDir, destName);
17384
+ const destPath = path4.join(assetsDir, destName);
17014
17385
  if (!fs3.existsSync(destPath)) {
17015
17386
  fs3.copyFileSync(sourcePath, destPath);
17016
17387
  }
@@ -17022,14 +17393,14 @@ function sanitize(name) {
17022
17393
 
17023
17394
  // src/bundler/bundle-assets.ts
17024
17395
  function bundleAssets(htmlPath, options = {}) {
17025
- const htmlDir = path4.dirname(htmlPath);
17026
- const assetsDir = path4.join(htmlDir, "assets");
17396
+ const htmlDir = path5.dirname(htmlPath);
17397
+ const assetsDir = path5.join(htmlDir, "assets");
17027
17398
  let html = fs4.readFileSync(htmlPath, "utf8");
17028
17399
  const refs = scanHtmlAssets(html);
17029
17400
  let copiedCount = 0;
17030
17401
  const missing = [];
17031
17402
  for (const ref of refs) {
17032
- const absolutePath = path4.resolve(htmlDir, ref);
17403
+ const absolutePath = path5.resolve(htmlDir, ref);
17033
17404
  if (!fs4.existsSync(absolutePath)) {
17034
17405
  missing.push(ref);
17035
17406
  continue;
@@ -17527,14 +17898,14 @@ function groupBy7(items, keyFn) {
17527
17898
 
17528
17899
  // src/formatters/astro-assets.ts
17529
17900
  import * as fs5 from "fs";
17530
- import * as path5 from "path";
17901
+ import * as path6 from "path";
17531
17902
  var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
17532
17903
  function isLocalPath(src) {
17533
17904
  const trimmed = src.trim();
17534
17905
  if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
17535
17906
  return false;
17536
17907
  }
17537
- return !path5.posix.isAbsolute(trimmed) && !path5.win32.isAbsolute(trimmed);
17908
+ return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
17538
17909
  }
17539
17910
  function stripCodeContent(markdown) {
17540
17911
  let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
@@ -17628,7 +17999,7 @@ function copyMarkdownAssets(options) {
17628
17999
  const pathMap = /* @__PURE__ */ new Map();
17629
18000
  const missing = [];
17630
18001
  for (const ref of refs) {
17631
- const absPath = path5.resolve(markdownDir, ref);
18002
+ const absPath = path6.resolve(markdownDir, ref);
17632
18003
  if (!fs5.existsSync(absPath)) {
17633
18004
  if (!allowMissing) {
17634
18005
  throw new Error(`Asset not found: ${absPath}`);
@@ -18905,7 +19276,8 @@ var FORMAT_EXTENSIONS = {
18905
19276
  junit: ".junit.xml",
18906
19277
  "cucumber-json": ".cucumber.json",
18907
19278
  "cucumber-messages": ".ndjson",
18908
- confluence: ".adf.json"
19279
+ confluence: ".adf.json",
19280
+ "story-report-json": ".story-report.json"
18909
19281
  };
18910
19282
  var TEST_EXTENSIONS = [
18911
19283
  ".test.ts",
@@ -18932,11 +19304,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
18932
19304
  const ext = FORMAT_EXTENSIONS[format];
18933
19305
  const effectiveName = outputName + (outputNameSuffix ?? "");
18934
19306
  if (mode === "aggregated") {
18935
- return toPosix(path6.join(baseOutputDir, `${effectiveName}${ext}`));
19307
+ return toPosix(path7.join(baseOutputDir, `${effectiveName}${ext}`));
18936
19308
  }
18937
19309
  const normalizedSource = toPosix(sourceFile);
18938
- const dirOfSource = path6.posix.dirname(normalizedSource);
18939
- let baseName = path6.posix.basename(normalizedSource);
19310
+ const dirOfSource = path7.posix.dirname(normalizedSource);
19311
+ let baseName = path7.posix.basename(normalizedSource);
18940
19312
  for (const testExt of TEST_EXTENSIONS) {
18941
19313
  if (baseName.endsWith(testExt)) {
18942
19314
  baseName = baseName.slice(0, -testExt.length);
@@ -18945,9 +19317,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
18945
19317
  }
18946
19318
  const fileName = `${baseName}.${effectiveName}${ext}`;
18947
19319
  if (colocatedStyle === "adjacent") {
18948
- return toPosix(path6.posix.join(dirOfSource, fileName));
19320
+ return toPosix(path7.posix.join(dirOfSource, fileName));
18949
19321
  }
18950
- return toPosix(path6.posix.join(baseOutputDir, dirOfSource, fileName));
19322
+ return toPosix(path7.posix.join(baseOutputDir, dirOfSource, fileName));
18951
19323
  }
18952
19324
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
18953
19325
  const groups = /* @__PURE__ */ new Map();
@@ -19030,6 +19402,9 @@ var ReportGenerator = class {
19030
19402
  cucumberJson: {
19031
19403
  pretty: options.cucumberJson?.pretty ?? false
19032
19404
  },
19405
+ storyReportJson: {
19406
+ pretty: options.storyReportJson?.pretty ?? true
19407
+ },
19033
19408
  cucumberMessages: {
19034
19409
  uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
19035
19410
  includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
@@ -19145,8 +19520,8 @@ var ReportGenerator = class {
19145
19520
  if (astroPaths) {
19146
19521
  for (const mdPath of astroPaths) {
19147
19522
  const content = await fsPromises.readFile(mdPath, "utf8");
19148
- const mdDir = path6.dirname(mdPath);
19149
- const assetsDir = path6.resolve(this.options.astro.assetsDir);
19523
+ const mdDir = path7.dirname(mdPath);
19524
+ const assetsDir = path7.resolve(this.options.astro.assetsDir);
19150
19525
  const result = copyMarkdownAssets({
19151
19526
  markdown: content,
19152
19527
  markdownDir: mdDir,
@@ -19177,9 +19552,9 @@ var ReportGenerator = class {
19177
19552
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
19178
19553
  const ext = FORMAT_EXTENSIONS[format];
19179
19554
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
19180
- const outputPath = toPosix(path6.join(this.options.outputDir, `${effectiveName}${ext}`));
19555
+ const outputPath = toPosix(path7.join(this.options.outputDir, `${effectiveName}${ext}`));
19181
19556
  const content = await this.formatContent(run, format);
19182
- const dir = path6.dirname(outputPath);
19557
+ const dir = path7.dirname(outputPath);
19183
19558
  await fsPromises.mkdir(dir, { recursive: true });
19184
19559
  await this.deps.writeFile(outputPath, content);
19185
19560
  return [outputPath];
@@ -19191,7 +19566,7 @@ var ReportGenerator = class {
19191
19566
  testCases
19192
19567
  };
19193
19568
  const content = await this.formatContent(groupRun, format);
19194
- const dir = path6.dirname(outputPath);
19569
+ const dir = path7.dirname(outputPath);
19195
19570
  await fsPromises.mkdir(dir, { recursive: true });
19196
19571
  await this.deps.writeFile(outputPath, content);
19197
19572
  writtenPaths.push(outputPath);
@@ -19298,6 +19673,12 @@ var ReportGenerator = class {
19298
19673
  });
19299
19674
  return formatter.format(run);
19300
19675
  }
19676
+ case "story-report-json": {
19677
+ const formatter = new StoryReportJsonFormatter({
19678
+ pretty: this.options.storyReportJson.pretty
19679
+ });
19680
+ return formatter.format(run);
19681
+ }
19301
19682
  default:
19302
19683
  throw new Error(`Unknown format: ${format}`);
19303
19684
  }
@@ -19311,7 +19692,7 @@ async function generateRunComparison(args) {
19311
19692
  await fsPromises.mkdir(outputDir, { recursive: true });
19312
19693
  for (const format of args.formats) {
19313
19694
  const ext = format === "html" ? ".html" : ".md";
19314
- const outputPath = toPosix(path6.join(outputDir, `${outputName}${ext}`));
19695
+ const outputPath = toPosix(path7.join(outputDir, `${outputName}${ext}`));
19315
19696
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
19316
19697
  await fsPromises.writeFile(outputPath, content, "utf8");
19317
19698
  files.push(outputPath);
@@ -19321,9 +19702,9 @@ async function generateRunComparison(args) {
19321
19702
 
19322
19703
  // src/init-astro.ts
19323
19704
  import * as fs7 from "fs";
19324
- import * as path7 from "path";
19705
+ import * as path8 from "path";
19325
19706
  import { fileURLToPath } from "url";
19326
- var __dirname = path7.dirname(fileURLToPath(import.meta.url));
19707
+ var __dirname = path8.dirname(fileURLToPath(import.meta.url));
19327
19708
  function initAstro(options = {}) {
19328
19709
  const targetDir = options.targetDir ?? "./story-docs";
19329
19710
  const force = options.force ?? false;
@@ -19335,7 +19716,7 @@ function initAstro(options = {}) {
19335
19716
  );
19336
19717
  }
19337
19718
  }
19338
- const templateDir = path7.resolve(__dirname, "..", "templates", "astro-starlight");
19719
+ const templateDir = path8.resolve(__dirname, "..", "templates", "astro-starlight");
19339
19720
  if (!fs7.existsSync(templateDir)) {
19340
19721
  throw new Error(
19341
19722
  `Template directory not found at ${templateDir}. Ensure the package is installed correctly.`
@@ -19348,8 +19729,8 @@ function copyDirRecursive(src, dest) {
19348
19729
  fs7.mkdirSync(dest, { recursive: true });
19349
19730
  const entries = fs7.readdirSync(src, { withFileTypes: true });
19350
19731
  for (const entry of entries) {
19351
- const srcPath = path7.join(src, entry.name);
19352
- const destPath = path7.join(dest, entry.name);
19732
+ const srcPath = path8.join(src, entry.name);
19733
+ const destPath = path8.join(dest, entry.name);
19353
19734
  if (entry.isDirectory()) {
19354
19735
  copyDirRecursive(srcPath, destPath);
19355
19736
  } else {
@@ -19380,6 +19761,7 @@ var EXIT_SCHEMA_VALIDATION = 1;
19380
19761
  var EXIT_CANONICAL_VALIDATION = 2;
19381
19762
  var EXIT_GENERATION = 3;
19382
19763
  var EXIT_USAGE = 4;
19764
+ var EXIT_COMPARE_GATE = 5;
19383
19765
  var HELP_TEXT = `
19384
19766
  executable-stories \u2014 Generate reports from test results JSON.
19385
19767
 
@@ -19404,7 +19786,7 @@ SUBCOMMANDS
19404
19786
  publish-jira Publish an ADF JSON file to a Jira issue (as comment or description)
19405
19787
 
19406
19788
  OPTIONS
19407
- --format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, or custom names from config (default: html)
19789
+ --format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, story-report-json, or custom names from config (default: html)
19408
19790
  astro Themed Markdown (for Astro docs sites with matching CSS)
19409
19791
  confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
19410
19792
  html Custom HTML report (accessible, dark mode, mermaid)
@@ -19413,6 +19795,7 @@ OPTIONS
19413
19795
  junit JUnit XML
19414
19796
  cucumber-json Cucumber JSON
19415
19797
  cucumber-messages Raw NDJSON (Cucumber Messages)
19798
+ story-report-json StoryReport v1 JSON (consumed by executable-stories-react and other UI renderers)
19416
19799
  --config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
19417
19800
  --input-type <type> Input type: raw, canonical, or ndjson (default: raw)
19418
19801
  --output-dir <dir> Output directory (default: reports)
@@ -19443,6 +19826,9 @@ OPTIONS
19443
19826
  --baseline-dir <dir> Directory to scan when --baseline auto is used
19444
19827
  --pr-summary Print a PR-friendly markdown summary after compare
19445
19828
  --pr-summary-file <path> Write the PR-friendly markdown summary to a file
19829
+ --fail-on-regression Exit non-zero when any regression is detected in compare
19830
+ --fail-on-added-failures Exit non-zero when newly added scenarios are failing
19831
+ --max-regressions <n> Exit non-zero when regressions exceed threshold
19446
19832
  --emit-canonical <path> Write canonical JSON to given path
19447
19833
  --help Show this help message
19448
19834
 
@@ -19506,6 +19892,7 @@ EXIT CODES
19506
19892
  2 Canonical validation failure
19507
19893
  3 Formatter/generation failure
19508
19894
  4 Bad arguments / usage error
19895
+ 5 Compare gate failed
19509
19896
  `.trim();
19510
19897
  async function parseCliArgs(argv) {
19511
19898
  const args = argv.slice(2);
@@ -19607,6 +19994,9 @@ async function parseCliArgs(argv) {
19607
19994
  "allow-missing-assets": { type: "boolean", default: false },
19608
19995
  "pr-summary": { type: "boolean", default: false },
19609
19996
  "pr-summary-file": { type: "string" },
19997
+ "fail-on-regression": { type: "boolean", default: false },
19998
+ "fail-on-added-failures": { type: "boolean", default: false },
19999
+ "max-regressions": { type: "string" },
19610
20000
  "config": { type: "string" },
19611
20001
  help: { type: "boolean", default: false }
19612
20002
  },
@@ -19647,7 +20037,7 @@ async function parseCliArgs(argv) {
19647
20037
  }
19648
20038
  const pluginConfig = await loadConfig(values["config"]);
19649
20039
  const customFormatterNames = new Set(Object.keys(pluginConfig.formatters ?? {}));
19650
- const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
20040
+ const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html", "story-report-json"]);
19651
20041
  const formatStr = values.format;
19652
20042
  const allRequestedFormats = formatStr.split(",").map((f) => f.trim());
19653
20043
  const builtInRequested = allRequestedFormats.filter((f) => builtInFormats.has(f));
@@ -19655,7 +20045,7 @@ async function parseCliArgs(argv) {
19655
20045
  const unknownFormats = allRequestedFormats.filter((f) => !builtInFormats.has(f) && !customFormatterNames.has(f));
19656
20046
  if (unknownFormats.length > 0) {
19657
20047
  const knownCustom = customFormatterNames.size > 0 ? `, ${[...customFormatterNames].join(", ")}` : "";
19658
- console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html${knownCustom}.`);
20048
+ console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, story-report-json${knownCustom}.`);
19659
20049
  process.exit(EXIT_USAGE);
19660
20050
  }
19661
20051
  const formats = builtInRequested;
@@ -19714,6 +20104,12 @@ async function parseCliArgs(argv) {
19714
20104
  console.error(`Error: --max-history-runs must be a positive integer, got "${maxHistoryRunsStr}".`);
19715
20105
  process.exit(EXIT_USAGE);
19716
20106
  }
20107
+ const maxRegressionsStr = values["max-regressions"];
20108
+ const maxRegressions = maxRegressionsStr !== void 0 ? parseInt(maxRegressionsStr, 10) : void 0;
20109
+ if (maxRegressionsStr !== void 0 && (isNaN(maxRegressions) || maxRegressions < 0)) {
20110
+ console.error(`Error: --max-regressions must be a non-negative integer, got "${maxRegressionsStr}".`);
20111
+ process.exit(EXIT_USAGE);
20112
+ }
19717
20113
  const sortTestCasesRaw = values["sort-test-cases"];
19718
20114
  const validSortModes = /* @__PURE__ */ new Set(["id", "source", "none"]);
19719
20115
  if (!validSortModes.has(sortTestCasesRaw)) {
@@ -19774,6 +20170,9 @@ async function parseCliArgs(argv) {
19774
20170
  allowMissingAssets: values["allow-missing-assets"],
19775
20171
  prSummary: values["pr-summary"],
19776
20172
  prSummaryFile: values["pr-summary-file"],
20173
+ failOnRegression: values["fail-on-regression"],
20174
+ failOnAddedFailures: values["fail-on-added-failures"],
20175
+ maxRegressions,
19777
20176
  config: values["config"]
19778
20177
  };
19779
20178
  return { args: cliArgs, pluginConfig, customRequested };
@@ -19782,7 +20181,7 @@ async function readInput(args) {
19782
20181
  if (args.stdin) {
19783
20182
  return readStdin();
19784
20183
  }
19785
- const filePath = path8.resolve(args.inputFile);
20184
+ const filePath = path9.resolve(args.inputFile);
19786
20185
  if (!fs8.existsSync(filePath)) {
19787
20186
  console.error(`Error: File not found: ${filePath}`);
19788
20187
  process.exit(EXIT_USAGE);
@@ -19790,7 +20189,7 @@ async function readInput(args) {
19790
20189
  return fs8.readFileSync(filePath, "utf8");
19791
20190
  }
19792
20191
  function readFileInput(filePath) {
19793
- const resolved = path8.resolve(filePath);
20192
+ const resolved = path9.resolve(filePath);
19794
20193
  if (!fs8.existsSync(resolved)) {
19795
20194
  console.error(`Error: File not found: ${resolved}`);
19796
20195
  process.exit(EXIT_USAGE);
@@ -19928,14 +20327,14 @@ function tryNormalizeRunFromText(text2, args) {
19928
20327
  }
19929
20328
  }
19930
20329
  function listBaselineCandidates(currentFile, args) {
19931
- const baselineDir = path8.resolve(args.baselineDir ?? path8.dirname(currentFile));
19932
- const currentResolved = path8.resolve(currentFile);
20330
+ const baselineDir = path9.resolve(args.baselineDir ?? path9.dirname(currentFile));
20331
+ const currentResolved = path9.resolve(currentFile);
19933
20332
  if (!fs8.existsSync(baselineDir)) {
19934
20333
  console.error(`Error: baseline directory not found: ${baselineDir}`);
19935
20334
  process.exit(EXIT_USAGE);
19936
20335
  }
19937
20336
  const entries = fs8.readdirSync(baselineDir, { withFileTypes: true });
19938
- return entries.filter((entry) => entry.isFile()).map((entry) => path8.join(baselineDir, entry.name)).filter((candidate) => path8.resolve(candidate) !== currentResolved).filter(
20337
+ return entries.filter((entry) => entry.isFile()).map((entry) => path9.join(baselineDir, entry.name)).filter((candidate) => path9.resolve(candidate) !== currentResolved).filter(
19939
20338
  (candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
19940
20339
  );
19941
20340
  }
@@ -19950,7 +20349,7 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
19950
20349
  }
19951
20350
  if (comparable.length === 0) {
19952
20351
  console.error(
19953
- `Error: no compatible baseline files found in ${path8.resolve(args.baselineDir ?? path8.dirname(currentFile))}.`
20352
+ `Error: no compatible baseline files found in ${path9.resolve(args.baselineDir ?? path9.dirname(currentFile))}.`
19954
20353
  );
19955
20354
  process.exit(EXIT_USAGE);
19956
20355
  }
@@ -19973,6 +20372,13 @@ async function main() {
19973
20372
  try {
19974
20373
  const result = await generateCompareReports(baseline, current, baselineFile, args);
19975
20374
  printCompareResult(result, args, startMs);
20375
+ const gateFailures = evaluateCompareGate(result, args);
20376
+ if (gateFailures.length > 0) {
20377
+ for (const failure of gateFailures) {
20378
+ console.error(`Compare gate failed: ${failure}`);
20379
+ }
20380
+ process.exit(EXIT_COMPARE_GATE);
20381
+ }
19976
20382
  process.exit(EXIT_SUCCESS);
19977
20383
  } catch (err) {
19978
20384
  const msg = err instanceof Error ? err.message : String(err);
@@ -20039,8 +20445,8 @@ async function main() {
20039
20445
  process.exit(EXIT_SCHEMA_VALIDATION);
20040
20446
  }
20041
20447
  if (args.emitCanonical) {
20042
- const outPath = path8.resolve(args.emitCanonical);
20043
- fs8.mkdirSync(path8.dirname(outPath), { recursive: true });
20448
+ const outPath = path9.resolve(args.emitCanonical);
20449
+ fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
20044
20450
  fs8.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
20045
20451
  }
20046
20452
  try {
@@ -20098,8 +20504,8 @@ ${msg}`);
20098
20504
  }
20099
20505
  const run = data;
20100
20506
  if (args.emitCanonical) {
20101
- const outPath = path8.resolve(args.emitCanonical);
20102
- fs8.mkdirSync(path8.dirname(outPath), { recursive: true });
20507
+ const outPath = path9.resolve(args.emitCanonical);
20508
+ fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
20103
20509
  fs8.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
20104
20510
  }
20105
20511
  try {
@@ -20156,8 +20562,8 @@ ${msg}`);
20156
20562
  process.exit(EXIT_CANONICAL_VALIDATION);
20157
20563
  }
20158
20564
  if (args.emitCanonical) {
20159
- const outPath = path8.resolve(args.emitCanonical);
20160
- fs8.mkdirSync(path8.dirname(outPath), { recursive: true });
20565
+ const outPath = path9.resolve(args.emitCanonical);
20566
+ fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
20161
20567
  fs8.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
20162
20568
  }
20163
20569
  try {
@@ -20183,7 +20589,7 @@ function runCustomFormatters(run, customRequested, formatters, args) {
20183
20589
  const ext = formatter.fileExtension ?? formatName;
20184
20590
  const baseName = args.outputName ?? "report";
20185
20591
  const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
20186
- const filepath = path8.join(outputDir, filename);
20592
+ const filepath = path9.join(outputDir, filename);
20187
20593
  fs8.mkdirSync(outputDir, { recursive: true });
20188
20594
  fs8.writeFileSync(filepath, content, "utf8");
20189
20595
  console.log(`Generated: ${filepath}`);
@@ -20235,7 +20641,7 @@ async function dispatchNotifications(run, args) {
20235
20641
  }
20236
20642
  function runHistoryPipeline(run, args) {
20237
20643
  if (!args.historyFile) return;
20238
- const historyPath = path8.resolve(args.historyFile);
20644
+ const historyPath = path9.resolve(args.historyFile);
20239
20645
  const store = loadHistory(
20240
20646
  { filePath: historyPath },
20241
20647
  {
@@ -20254,7 +20660,7 @@ function runHistoryPipeline(run, args) {
20254
20660
  run,
20255
20661
  maxRuns: args.maxHistoryRuns
20256
20662
  });
20257
- const dir = path8.dirname(historyPath);
20663
+ const dir = path9.dirname(historyPath);
20258
20664
  fs8.mkdirSync(dir, { recursive: true });
20259
20665
  saveHistory(
20260
20666
  { filePath: historyPath, store: updated },
@@ -20331,6 +20737,9 @@ async function generateCompareReports(baseline, current, baselineFile, args) {
20331
20737
  return {
20332
20738
  files: result.files,
20333
20739
  baselineFile,
20740
+ addedFailures: result.diff.scenarios.filter(
20741
+ (scenario) => scenario.kind === "added" && scenario.current?.status === "failed"
20742
+ ).length,
20334
20743
  summary: result.diff.summary,
20335
20744
  prSummary: args.prSummary || args.prSummaryFile ? createPrCommentSummary(result.diff) : void 0
20336
20745
  };
@@ -20356,8 +20765,8 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
20356
20765
  function printCompareResult(result, args, startMs) {
20357
20766
  const durationMs = Date.now() - startMs;
20358
20767
  if (result.prSummary && args.prSummaryFile) {
20359
- const outputPath = path8.resolve(args.prSummaryFile);
20360
- fs8.mkdirSync(path8.dirname(outputPath), { recursive: true });
20768
+ const outputPath = path9.resolve(args.prSummaryFile);
20769
+ fs8.mkdirSync(path9.dirname(outputPath), { recursive: true });
20361
20770
  fs8.writeFileSync(outputPath, result.prSummary, "utf8");
20362
20771
  }
20363
20772
  if (args.jsonSummary) {
@@ -20366,6 +20775,7 @@ function printCompareResult(result, args, startMs) {
20366
20775
  {
20367
20776
  files: result.files,
20368
20777
  baselineFile: result.baselineFile,
20778
+ addedFailures: result.addedFailures,
20369
20779
  diff: result.summary,
20370
20780
  prSummary: result.prSummary,
20371
20781
  durationMs
@@ -20385,6 +20795,25 @@ function printCompareResult(result, args, startMs) {
20385
20795
  console.log(result.prSummary);
20386
20796
  }
20387
20797
  }
20798
+ function evaluateCompareGate(result, args) {
20799
+ const failures = [];
20800
+ if (args.failOnRegression && result.summary.regressed > 0) {
20801
+ failures.push(
20802
+ `regressions detected (${result.summary.regressed}) with --fail-on-regression`
20803
+ );
20804
+ }
20805
+ if (args.failOnAddedFailures && result.addedFailures > 0) {
20806
+ failures.push(
20807
+ `new failing scenarios detected (${result.addedFailures}) with --fail-on-added-failures`
20808
+ );
20809
+ }
20810
+ if (args.maxRegressions !== void 0 && result.summary.regressed > args.maxRegressions) {
20811
+ failures.push(
20812
+ `regressions ${result.summary.regressed} exceed --max-regressions ${args.maxRegressions}`
20813
+ );
20814
+ }
20815
+ return failures;
20816
+ }
20388
20817
  async function runPublishConfluence(rawArgs) {
20389
20818
  const { values, positionals } = parseArgs({
20390
20819
  args: rawArgs,
@@ -20457,7 +20886,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
20457
20886
  console.error("Error: --title is required when creating a new page");
20458
20887
  process.exit(EXIT_USAGE);
20459
20888
  }
20460
- const adf = fs8.readFileSync(path8.resolve(inputFile), "utf8");
20889
+ const adf = fs8.readFileSync(path9.resolve(inputFile), "utf8");
20461
20890
  if (dryRun) {
20462
20891
  console.log(
20463
20892
  JSON.stringify(
@@ -20563,7 +20992,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
20563
20992
  process.exit(EXIT_USAGE);
20564
20993
  }
20565
20994
  const mode = modeRaw;
20566
- const adf = fs8.readFileSync(path8.resolve(inputFile), "utf8");
20995
+ const adf = fs8.readFileSync(path9.resolve(inputFile), "utf8");
20567
20996
  if (dryRun) {
20568
20997
  console.log(
20569
20998
  JSON.stringify(