executable-stories-formatters 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -693,50 +693,162 @@ function initTheme() {
693
693
  }
694
694
  `;
695
695
  var JS_CORE = `
696
+ // Filter state
697
+ var activeTags = new Set();
698
+ var activeStatus = null;
699
+
696
700
  // Search functionality
697
701
  function initSearch() {
698
- const input = document.querySelector('.search-input');
702
+ var input = document.querySelector('.search-input');
699
703
  if (!input) return;
700
704
 
701
- let debounceTimer;
702
- input.addEventListener('input', (e) => {
705
+ var debounceTimer;
706
+ input.addEventListener('input', function() {
703
707
  clearTimeout(debounceTimer);
704
- debounceTimer = setTimeout(() => {
705
- filterScenarios(e.target.value.toLowerCase().trim());
708
+ debounceTimer = setTimeout(function() {
709
+ applyAllFilters();
706
710
  }, 150);
707
711
  });
708
712
 
709
713
  // Clear search on Escape
710
- input.addEventListener('keydown', (e) => {
714
+ input.addEventListener('keydown', function(e) {
711
715
  if (e.key === 'Escape') {
712
716
  e.target.value = '';
713
- filterScenarios('');
717
+ applyAllFilters();
714
718
  }
715
719
  });
716
720
  }
717
721
 
718
- function filterScenarios(query) {
719
- const features = document.querySelectorAll('.feature');
722
+ // Tag filter
723
+ function initTagFilter() {
724
+ document.querySelectorAll('.tag-pill').forEach(function(pill) {
725
+ pill.addEventListener('click', function() {
726
+ var tag = pill.dataset.tag;
727
+ if (activeTags.has(tag)) {
728
+ activeTags.delete(tag);
729
+ pill.classList.remove('active');
730
+ } else {
731
+ activeTags.add(tag);
732
+ pill.classList.add('active');
733
+ }
734
+ updateClearButton();
735
+ applyAllFilters();
736
+ });
737
+ });
720
738
 
721
- features.forEach(feature => {
722
- const scenarios = feature.querySelectorAll('.scenario');
723
- let visibleCount = 0;
739
+ var clearBtn = document.querySelector('.tag-bar-clear');
740
+ if (clearBtn) {
741
+ clearBtn.addEventListener('click', function() {
742
+ activeTags.clear();
743
+ document.querySelectorAll('.tag-pill.active').forEach(function(p) { p.classList.remove('active'); });
744
+ updateClearButton();
745
+ applyAllFilters();
746
+ });
747
+ }
748
+ }
724
749
 
725
- scenarios.forEach(scenario => {
726
- const title = scenario.querySelector('.scenario-title')?.textContent?.toLowerCase() || '';
727
- const tags = Array.from(scenario.querySelectorAll('.tag')).map(t => t.textContent.toLowerCase());
728
- const steps = Array.from(scenario.querySelectorAll('.step-text')).map(s => s.textContent.toLowerCase());
750
+ function updateClearButton() {
751
+ var clearBtn = document.querySelector('.tag-bar-clear');
752
+ if (clearBtn) {
753
+ clearBtn.style.display = activeTags.size > 0 ? '' : 'none';
754
+ }
755
+ }
729
756
 
730
- const matches = !query ||
731
- title.includes(query) ||
732
- tags.some(t => t.includes(query)) ||
733
- steps.some(s => s.includes(query));
757
+ // Status filter (clickable summary cards)
758
+ function initStatusFilter() {
759
+ document.querySelectorAll('.summary-card').forEach(function(card) {
760
+ card.style.cursor = 'pointer';
761
+ if (!card.classList.contains('passed') && !card.classList.contains('failed') && !card.classList.contains('skipped')) {
762
+ card.addEventListener('click', function() {
763
+ activeStatus = null;
764
+ document.querySelectorAll('.summary-card').forEach(function(c) { c.classList.remove('status-active'); });
765
+ applyAllFilters();
766
+ });
767
+ return;
768
+ }
769
+ card.addEventListener('click', function() {
770
+ var status = card.classList.contains('passed') ? 'passed' :
771
+ card.classList.contains('failed') ? 'failed' : 'skipped';
772
+ if (activeStatus === status) {
773
+ activeStatus = null;
774
+ card.classList.remove('status-active');
775
+ } else {
776
+ activeStatus = status;
777
+ document.querySelectorAll('.summary-card').forEach(function(c) { c.classList.remove('status-active'); });
778
+ card.classList.add('status-active');
779
+ }
780
+ applyAllFilters();
781
+ });
782
+ });
783
+ }
734
784
 
735
- scenario.style.display = matches ? '' : 'none';
736
- if (matches) visibleCount++;
785
+ // Unified filter: composes search + tags + status
786
+ function applyAllFilters() {
787
+ var searchInput = document.querySelector('.search-input');
788
+ var searchQuery = searchInput ? searchInput.value.toLowerCase().trim() : '';
789
+ var features = document.querySelectorAll('.feature');
790
+ var visibleCount = 0;
791
+ var totalCount = 0;
792
+
793
+ features.forEach(function(feature) {
794
+ var scenarios = feature.querySelectorAll('.scenario');
795
+ var featureVisible = 0;
796
+
797
+ scenarios.forEach(function(scenario) {
798
+ totalCount++;
799
+ var title = (scenario.querySelector('.scenario-title') || {}).textContent || '';
800
+ title = title.toLowerCase();
801
+ var tags = Array.from(scenario.querySelectorAll('.scenario-meta .tag')).map(function(t) { return t.textContent.toLowerCase(); });
802
+ var steps = Array.from(scenario.querySelectorAll('.step-text')).map(function(s) { return s.textContent.toLowerCase(); });
803
+ var statusEl = scenario.querySelector('.status-icon');
804
+ var status = statusEl && statusEl.classList.contains('status-passed') ? 'passed' :
805
+ statusEl && statusEl.classList.contains('status-failed') ? 'failed' :
806
+ statusEl && statusEl.classList.contains('status-skipped') ? 'skipped' : 'pending';
807
+
808
+ var matchesSearch = !searchQuery ||
809
+ title.includes(searchQuery) ||
810
+ tags.some(function(t) { return t.includes(searchQuery); }) ||
811
+ steps.some(function(s) { return s.includes(searchQuery); });
812
+
813
+ var matchesTags = activeTags.size === 0 ||
814
+ tags.some(function(t) { return activeTags.has(t); });
815
+
816
+ var matchesStatus = !activeStatus ||
817
+ status === activeStatus ||
818
+ (activeStatus === 'skipped' && status === 'pending');
819
+
820
+ var visible = matchesSearch && matchesTags && matchesStatus;
821
+ scenario.style.display = visible ? '' : 'none';
822
+ if (visible) { visibleCount++; featureVisible++; }
737
823
  });
738
824
 
739
- feature.style.display = visibleCount > 0 ? '' : 'none';
825
+ feature.style.display = featureVisible > 0 ? '' : 'none';
826
+ });
827
+
828
+ updateFilterResults(visibleCount, totalCount);
829
+ }
830
+
831
+ function updateFilterResults(visible, total) {
832
+ var el = document.querySelector('.filter-results');
833
+ if (!el) return;
834
+ var searchInput = document.querySelector('.search-input');
835
+ var isFiltering = activeTags.size > 0 || activeStatus ||
836
+ (searchInput && searchInput.value.trim().length > 0);
837
+ el.style.display = isFiltering ? '' : 'none';
838
+ var vc = el.querySelector('.visible-count');
839
+ var tc = el.querySelector('.total-count');
840
+ if (vc) vc.textContent = visible;
841
+ if (tc) tc.textContent = total;
842
+ }
843
+
844
+ // Keyboard shortcuts
845
+ function initKeyboardShortcuts() {
846
+ document.addEventListener('keydown', function(e) {
847
+ if (e.key === '/' && !e.ctrlKey && !e.metaKey && e.target.tagName !== 'INPUT') {
848
+ e.preventDefault();
849
+ var input = document.querySelector('.search-input');
850
+ if (input) input.focus();
851
+ }
740
852
  });
741
853
  }
742
854
 
@@ -771,6 +883,18 @@ function initCollapse() {
771
883
  }
772
884
  });
773
885
  });
886
+
887
+ document.querySelectorAll('.trace-view-header').forEach(header => {
888
+ header.addEventListener('click', () => {
889
+ toggleCollapse(header, header.closest('.trace-view'));
890
+ });
891
+ header.addEventListener('keydown', (e) => {
892
+ if (e.key === 'Enter' || e.key === ' ') {
893
+ e.preventDefault();
894
+ toggleCollapse(header, header.closest('.trace-view'));
895
+ }
896
+ });
897
+ });
774
898
  }
775
899
 
776
900
  function expandAll() {
@@ -821,6 +945,9 @@ function generateScript(options) {
821
945
  initCalls.push("initTheme();");
822
946
  }
823
947
  initCalls.push("initSearch();");
948
+ initCalls.push("initTagFilter();");
949
+ initCalls.push("initStatusFilter();");
950
+ initCalls.push("initKeyboardShortcuts();");
824
951
  initCalls.push("initCollapse();");
825
952
  const initScript = `
826
953
  // Initialize on load
@@ -971,6 +1098,7 @@ var CSS_STYLES = `
971
1098
  --tag-bg: hsl(145 55% 95%);
972
1099
  --tag-color: hsl(145 63% 30%);
973
1100
  --tag-border: hsl(145 55% 85%);
1101
+ --step-param-color: hsl(220 70% 50%);
974
1102
 
975
1103
  /* Accordion/Collapsible styling */
976
1104
  --accordion-header-hover: hsl(0 0% 98%);
@@ -1029,6 +1157,7 @@ var CSS_STYLES = `
1029
1157
  --tag-bg: hsl(145 35% 14%);
1030
1158
  --tag-color: hsl(145 63% 60%);
1031
1159
  --tag-border: hsl(145 35% 22%);
1160
+ --step-param-color: hsl(220 70% 70%);
1032
1161
 
1033
1162
  /* Accordion/Collapsible styling */
1034
1163
  --accordion-header-hover: hsl(0 0% 11%);
@@ -1077,6 +1206,7 @@ var CSS_STYLES = `
1077
1206
  --tag-bg: hsl(145 35% 14%);
1078
1207
  --tag-color: hsl(145 63% 60%);
1079
1208
  --tag-border: hsl(145 35% 22%);
1209
+ --step-param-color: hsl(220 70% 70%);
1080
1210
  --accordion-header-hover: hsl(0 0% 11%);
1081
1211
  --accordion-content-bg: hsl(0 0% 7%);
1082
1212
  }
@@ -1304,6 +1434,98 @@ body {
1304
1434
  }
1305
1435
  .summary-card.pending .value { color: var(--pending); }
1306
1436
 
1437
+ /* ============================================================================
1438
+ Tag Filter Bar
1439
+ ============================================================================ */
1440
+ .tag-bar {
1441
+ margin-bottom: 1rem;
1442
+ padding: 0.75rem 1rem;
1443
+ background: var(--card);
1444
+ border: 1px solid var(--border);
1445
+ border-radius: var(--radius);
1446
+ position: sticky;
1447
+ top: 0;
1448
+ z-index: 10;
1449
+ }
1450
+
1451
+ .tag-bar-header {
1452
+ display: flex;
1453
+ justify-content: space-between;
1454
+ align-items: center;
1455
+ margin-bottom: 0.5rem;
1456
+ }
1457
+
1458
+ .tag-bar-label {
1459
+ font-size: 0.6875rem;
1460
+ text-transform: uppercase;
1461
+ letter-spacing: 0.05em;
1462
+ color: var(--muted-foreground);
1463
+ font-weight: 500;
1464
+ }
1465
+
1466
+ .tag-bar-clear {
1467
+ font-size: 0.75rem;
1468
+ font-weight: 500;
1469
+ color: var(--primary);
1470
+ background: none;
1471
+ border: none;
1472
+ cursor: pointer;
1473
+ padding: 0.125rem 0.5rem;
1474
+ border-radius: var(--radius);
1475
+ transition: all 0.15s ease;
1476
+ }
1477
+
1478
+ .tag-bar-clear:hover {
1479
+ background: var(--muted);
1480
+ }
1481
+
1482
+ .tag-bar-pills {
1483
+ display: flex;
1484
+ flex-wrap: wrap;
1485
+ gap: 0.375rem;
1486
+ }
1487
+
1488
+ .tag-pill {
1489
+ font-size: 0.75rem;
1490
+ font-weight: 500;
1491
+ padding: 0.25rem 0.625rem;
1492
+ background: var(--tag-bg);
1493
+ color: var(--tag-color);
1494
+ border: 1px solid var(--tag-border);
1495
+ border-radius: 9999px;
1496
+ font-family: var(--font-mono);
1497
+ cursor: pointer;
1498
+ transition: all 0.15s ease;
1499
+ }
1500
+
1501
+ .tag-pill:hover {
1502
+ background: var(--success-border);
1503
+ }
1504
+
1505
+ .tag-pill.active {
1506
+ background: var(--primary);
1507
+ color: var(--primary-foreground);
1508
+ border-color: var(--primary);
1509
+ }
1510
+
1511
+ /* ============================================================================
1512
+ Summary Card Status Filter
1513
+ ============================================================================ */
1514
+ .summary-card.status-active {
1515
+ box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
1516
+ }
1517
+
1518
+ /* ============================================================================
1519
+ Filter Results Counter
1520
+ ============================================================================ */
1521
+ .filter-results {
1522
+ text-align: center;
1523
+ font-size: 0.8125rem;
1524
+ color: var(--muted-foreground);
1525
+ margin-bottom: 1rem;
1526
+ font-weight: 500;
1527
+ }
1528
+
1307
1529
  /* ============================================================================
1308
1530
  Feature Sections - shadcn accordion style
1309
1531
  ============================================================================ */
@@ -1530,6 +1752,12 @@ body {
1530
1752
  color: var(--foreground);
1531
1753
  }
1532
1754
 
1755
+ .step-param {
1756
+ font-style: italic;
1757
+ font-weight: 500;
1758
+ color: var(--step-param-color);
1759
+ }
1760
+
1533
1761
  .step-duration {
1534
1762
  color: var(--muted-foreground);
1535
1763
  font-size: 0.6875rem;
@@ -1691,8 +1919,10 @@ body {
1691
1919
  padding: 0;
1692
1920
  }
1693
1921
 
1694
- .header-actions {
1695
- display: none;
1922
+ .header-actions,
1923
+ .tag-bar,
1924
+ .filter-results {
1925
+ display: none !important;
1696
1926
  }
1697
1927
 
1698
1928
  .feature,
@@ -2204,6 +2434,7 @@ body {
2204
2434
  font-family: inherit;
2205
2435
  background: none;
2206
2436
  }
2437
+
2207
2438
  `;
2208
2439
 
2209
2440
  // src/formatters/html/renderers/status.ts
@@ -2266,6 +2497,28 @@ function renderSummary(args, _deps) {
2266
2497
  </div>`;
2267
2498
  }
2268
2499
 
2500
+ // src/formatters/html/renderers/tag-bar.ts
2501
+ function renderTagBar(args, deps) {
2502
+ const { tags, totalScenarios } = args;
2503
+ if (tags.length === 0) return "";
2504
+ const pills = tags.map(
2505
+ (tag) => `<button type="button" class="tag-pill" data-tag="${deps.escapeHtml(tag)}">${deps.escapeHtml(tag)}</button>`
2506
+ ).join("\n ");
2507
+ return `
2508
+ <div class="tag-bar">
2509
+ <div class="tag-bar-header">
2510
+ <span class="tag-bar-label">Filter by tag</span>
2511
+ <button type="button" class="tag-bar-clear" style="display:none">Clear</button>
2512
+ </div>
2513
+ <div class="tag-bar-pills">
2514
+ ${pills}
2515
+ </div>
2516
+ </div>
2517
+ <div class="filter-results" style="display:none">
2518
+ Showing <span class="visible-count">0</span> of <span class="total-count">${totalScenarios}</span> scenarios
2519
+ </div>`;
2520
+ }
2521
+
2269
2522
  // src/formatters/html/renderers/error-box.ts
2270
2523
  function renderErrorBox(args, deps) {
2271
2524
  const body = args.stack != null ? `${deps.escapeHtml(args.message)}
@@ -2426,10 +2679,11 @@ function renderStep(step, stepResult, index, deps) {
2426
2679
  const isContinuation = CONTINUATION_KEYWORDS.includes(keywordTrimmed);
2427
2680
  const stepClass = isContinuation ? "step continuation" : "step";
2428
2681
  const stepDocs = deps.renderDocs(step.docs, "step-docs");
2682
+ const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
2429
2683
  return `<div class="${stepClass}">
2430
2684
  <span class="step-status ${statusClass}">${statusIcon}</span>
2431
2685
  <span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
2432
- <span class="step-text">${deps.escapeHtml(step.text)}</span>
2686
+ <span class="step-text">${textHtml}</span>
2433
2687
  <span class="step-duration">${duration}</span>
2434
2688
  </div>${stepDocs}`;
2435
2689
  }
@@ -2441,6 +2695,30 @@ function renderSteps(args, deps) {
2441
2695
  return `<div class="steps">${stepsHtml}</div>`;
2442
2696
  }
2443
2697
 
2698
+ // src/formatters/html/renderers/step-params.ts
2699
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
2700
+ function highlightStepParams(text, deps) {
2701
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
2702
+ if (matches.length === 0) {
2703
+ return deps.escapeHtml(text);
2704
+ }
2705
+ let result = "";
2706
+ let lastIndex = 0;
2707
+ for (const match of matches) {
2708
+ const matchStart = match.index;
2709
+ const matchEnd = matchStart + match[0].length;
2710
+ if (matchStart > lastIndex) {
2711
+ result += deps.escapeHtml(text.slice(lastIndex, matchStart));
2712
+ }
2713
+ result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
2714
+ lastIndex = matchEnd;
2715
+ }
2716
+ if (lastIndex < text.length) {
2717
+ result += deps.escapeHtml(text.slice(lastIndex));
2718
+ }
2719
+ return result;
2720
+ }
2721
+
2444
2722
  // src/formatters/html/renderers/scenario.ts
2445
2723
  function renderScenario(args, deps) {
2446
2724
  const { tc } = args;
@@ -2448,6 +2726,19 @@ function renderScenario(args, deps) {
2448
2726
  const statusClass = `status-${tc.status}`;
2449
2727
  const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
2450
2728
  const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
2729
+ const otelMeta = tc.story.meta?.otel;
2730
+ let traceBadge = "";
2731
+ if (otelMeta?.traceId) {
2732
+ const shortId = otelMeta.traceId.slice(0, 16);
2733
+ const traceLink = tc.story.docs?.find(
2734
+ (d) => d.kind === "link" && d.label === "View Trace"
2735
+ );
2736
+ if (traceLink) {
2737
+ traceBadge = `<a class="tag trace-tag" href="${deps.escapeHtml(traceLink.url)}" title="${deps.escapeHtml(otelMeta.traceId)}" target="_blank" rel="noopener">${deps.escapeHtml(shortId)}\u2026</a>`;
2738
+ } else {
2739
+ traceBadge = `<span class="tag trace-tag" title="${deps.escapeHtml(otelMeta.traceId)}">${deps.escapeHtml(shortId)}\u2026</span>`;
2740
+ }
2741
+ }
2451
2742
  const storyDocs = deps.renderDocs(tc.story.docs, "story-docs");
2452
2743
  const steps = deps.renderSteps(
2453
2744
  { steps: tc.story.steps, stepResults: tc.stepResults },
@@ -2478,7 +2769,7 @@ function renderScenario(args, deps) {
2478
2769
  <span class="status-icon ${statusClass}">${statusIcon}</span>
2479
2770
  <span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
2480
2771
  </div>
2481
- <div class="scenario-meta">${tags}</div>
2772
+ <div class="scenario-meta">${tags}${traceBadge}</div>
2482
2773
  </div>
2483
2774
  <span class="scenario-duration">${duration}</span>
2484
2775
  </div>
@@ -2565,6 +2856,15 @@ function buildBody(args, deps) {
2565
2856
  deps.summaryDeps
2566
2857
  )
2567
2858
  );
2859
+ const allTags = [
2860
+ ...new Set(run.testCases.flatMap((tc) => tc.tags))
2861
+ ].sort();
2862
+ parts.push(
2863
+ deps.renderTagBar(
2864
+ { tags: allTags, totalScenarios: total },
2865
+ deps.tagBarDeps
2866
+ )
2867
+ );
2568
2868
  const byFile = groupBy(run.testCases, (tc) => tc.sourceFile);
2569
2869
  for (const [file, testCases] of byFile) {
2570
2870
  parts.push(
@@ -2603,7 +2903,8 @@ function createHtmlFormatter(options = {}) {
2603
2903
  const stepsDeps = {
2604
2904
  escapeHtml,
2605
2905
  getStatusIcon,
2606
- renderDocs
2906
+ renderDocs,
2907
+ highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
2607
2908
  };
2608
2909
  const scenarioDeps = {
2609
2910
  escapeHtml,
@@ -2621,12 +2922,15 @@ function createHtmlFormatter(options = {}) {
2621
2922
  renderScenario: (args) => renderScenario(args, scenarioDeps),
2622
2923
  scenarioDeps
2623
2924
  };
2925
+ const tagBarDeps = { escapeHtml };
2624
2926
  const bodyDeps = {
2625
2927
  renderMetaInfo,
2626
2928
  renderSummary,
2929
+ renderTagBar,
2627
2930
  renderFeature,
2628
2931
  metaDeps: { escapeHtml },
2629
2932
  summaryDeps: {},
2933
+ tagBarDeps,
2630
2934
  featureDeps
2631
2935
  };
2632
2936
  return {
@@ -2902,6 +3206,7 @@ var MarkdownFormatter = class {
2902
3206
  includeSummaryTable: options.includeSummaryTable ?? false,
2903
3207
  permalinkBaseUrl: options.permalinkBaseUrl,
2904
3208
  ticketUrlTemplate: options.ticketUrlTemplate,
3209
+ traceUrlTemplate: options.traceUrlTemplate,
2905
3210
  includeSourceLinks: options.includeSourceLinks ?? true,
2906
3211
  customRenderers: options.customRenderers
2907
3212
  };
@@ -3124,6 +3429,18 @@ var MarkdownFormatter = class {
3124
3429
  meta.push(`Tickets: ${tc.story.tickets.map((t) => `\`${t}\``).join(", ")}`);
3125
3430
  }
3126
3431
  }
3432
+ const otelMeta = tc.story.meta?.otel;
3433
+ if (otelMeta?.traceId) {
3434
+ const traceTemplate = this.options.traceUrlTemplate;
3435
+ if (traceTemplate) {
3436
+ const url = traceTemplate.replace(/\{traceId\}/g, otelMeta.traceId);
3437
+ meta.push(
3438
+ `Trace: [${otelMeta.traceId.slice(0, 16)}\u2026](${url})`
3439
+ );
3440
+ } else {
3441
+ meta.push(`Trace: \`${otelMeta.traceId}\``);
3442
+ }
3443
+ }
3127
3444
  if (meta.length > 0) {
3128
3445
  lines.push(meta.join(" | "));
3129
3446
  }
@@ -5066,6 +5383,31 @@ function detectCI4(env = process.env) {
5066
5383
  return void 0;
5067
5384
  }
5068
5385
 
5386
+ // src/utils/otel-detect.ts
5387
+ import { createRequire } from "module";
5388
+ function getRequire() {
5389
+ const url = import.meta.url ?? (typeof __filename !== "undefined" ? `file://${__filename}` : void 0);
5390
+ if (!url) throw new Error("Cannot determine module URL");
5391
+ return createRequire(url);
5392
+ }
5393
+ function tryGetActiveOtelContext() {
5394
+ try {
5395
+ const api = getRequire()("@opentelemetry/api");
5396
+ const span = api.trace?.getActiveSpan?.();
5397
+ if (!span) return void 0;
5398
+ const ctx = span.spanContext?.();
5399
+ if (!ctx?.traceId || ctx.traceId === "00000000000000000000000000000000")
5400
+ return void 0;
5401
+ return { traceId: ctx.traceId, spanId: ctx.spanId };
5402
+ } catch {
5403
+ return void 0;
5404
+ }
5405
+ }
5406
+ function resolveTraceUrl(template, traceId) {
5407
+ if (!template) return void 0;
5408
+ return template.replace(/\{traceId\}/g, traceId);
5409
+ }
5410
+
5069
5411
  // src/index.ts
5070
5412
  var FORMAT_EXTENSIONS = {
5071
5413
  markdown: ".md",
@@ -5256,6 +5598,7 @@ var ReportGenerator = class {
5256
5598
  includeSummaryTable: options.markdown?.includeSummaryTable ?? false,
5257
5599
  permalinkBaseUrl: options.markdown?.permalinkBaseUrl,
5258
5600
  ticketUrlTemplate: options.markdown?.ticketUrlTemplate,
5601
+ traceUrlTemplate: options.markdown?.traceUrlTemplate,
5259
5602
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
5260
5603
  customRenderers: options.markdown?.customRenderers
5261
5604
  }
@@ -5381,6 +5724,7 @@ var ReportGenerator = class {
5381
5724
  includeSummaryTable: this.options.markdown.includeSummaryTable,
5382
5725
  permalinkBaseUrl: this.options.markdown.permalinkBaseUrl,
5383
5726
  ticketUrlTemplate: this.options.markdown.ticketUrlTemplate,
5727
+ traceUrlTemplate: this.options.markdown.traceUrlTemplate,
5384
5728
  includeSourceLinks: this.options.markdown.includeSourceLinks,
5385
5729
  customRenderers: this.options.markdown.customRenderers
5386
5730
  });
@@ -5442,7 +5786,9 @@ export {
5442
5786
  readPackageVersion,
5443
5787
  resolveAttachment,
5444
5788
  resolveAttachments,
5789
+ resolveTraceUrl,
5445
5790
  slugify,
5791
+ tryGetActiveOtelContext,
5446
5792
  validateCanonicalRun
5447
5793
  };
5448
5794
  //# sourceMappingURL=index.js.map