executable-stories-formatters 0.2.0 → 0.4.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
@@ -883,20 +883,32 @@ function initCollapse() {
883
883
  }
884
884
  });
885
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
+ });
886
898
  }
887
899
 
888
900
  function expandAll() {
889
- document.querySelectorAll('.feature, .scenario').forEach(el => {
901
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
890
902
  el.classList.remove('collapsed');
891
- const header = el.querySelector('.feature-header, .scenario-header');
903
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
892
904
  header?.setAttribute('aria-expanded', 'true');
893
905
  });
894
906
  }
895
907
 
896
908
  function collapseAll() {
897
- document.querySelectorAll('.feature, .scenario').forEach(el => {
909
+ document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
898
910
  el.classList.add('collapsed');
899
- const header = el.querySelector('.feature-header, .scenario-header');
911
+ const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
900
912
  header?.setAttribute('aria-expanded', 'false');
901
913
  });
902
914
  }
@@ -1086,6 +1098,7 @@ var CSS_STYLES = `
1086
1098
  --tag-bg: hsl(145 55% 95%);
1087
1099
  --tag-color: hsl(145 63% 30%);
1088
1100
  --tag-border: hsl(145 55% 85%);
1101
+ --step-param-color: hsl(220 70% 50%);
1089
1102
 
1090
1103
  /* Accordion/Collapsible styling */
1091
1104
  --accordion-header-hover: hsl(0 0% 98%);
@@ -1144,6 +1157,7 @@ var CSS_STYLES = `
1144
1157
  --tag-bg: hsl(145 35% 14%);
1145
1158
  --tag-color: hsl(145 63% 60%);
1146
1159
  --tag-border: hsl(145 35% 22%);
1160
+ --step-param-color: hsl(220 70% 70%);
1147
1161
 
1148
1162
  /* Accordion/Collapsible styling */
1149
1163
  --accordion-header-hover: hsl(0 0% 11%);
@@ -1192,6 +1206,7 @@ var CSS_STYLES = `
1192
1206
  --tag-bg: hsl(145 35% 14%);
1193
1207
  --tag-color: hsl(145 63% 60%);
1194
1208
  --tag-border: hsl(145 35% 22%);
1209
+ --step-param-color: hsl(220 70% 70%);
1195
1210
  --accordion-header-hover: hsl(0 0% 11%);
1196
1211
  --accordion-content-bg: hsl(0 0% 7%);
1197
1212
  }
@@ -1737,6 +1752,12 @@ body {
1737
1752
  color: var(--foreground);
1738
1753
  }
1739
1754
 
1755
+ .step-param {
1756
+ font-style: italic;
1757
+ font-weight: 500;
1758
+ color: var(--step-param-color);
1759
+ }
1760
+
1740
1761
  .step-duration {
1741
1762
  color: var(--muted-foreground);
1742
1763
  font-size: 0.6875rem;
@@ -2413,6 +2434,133 @@ body {
2413
2434
  font-family: inherit;
2414
2435
  background: none;
2415
2436
  }
2437
+
2438
+ /* ============================================================================
2439
+ Trace View - OTel span waterfall
2440
+ ============================================================================ */
2441
+ .trace-view {
2442
+ margin-top: 0.75rem;
2443
+ border: 1px solid var(--border);
2444
+ border-radius: calc(var(--radius) - 2px);
2445
+ overflow: hidden;
2446
+ }
2447
+
2448
+ .trace-view-header {
2449
+ display: flex;
2450
+ align-items: center;
2451
+ gap: 0.5rem;
2452
+ padding: 0.5rem 0.75rem;
2453
+ background: var(--card);
2454
+ cursor: pointer;
2455
+ user-select: none;
2456
+ font-size: 0.8125rem;
2457
+ font-weight: 500;
2458
+ color: var(--foreground);
2459
+ transition: background-color 0.15s ease;
2460
+ }
2461
+
2462
+ .trace-view-header:hover {
2463
+ background: var(--accordion-header-hover);
2464
+ }
2465
+
2466
+ .trace-view-count {
2467
+ font-size: 0.6875rem;
2468
+ font-weight: 500;
2469
+ padding: 0.125rem 0.5rem;
2470
+ background: var(--success-light);
2471
+ color: var(--success);
2472
+ border: 1px solid var(--success-border);
2473
+ border-radius: 9999px;
2474
+ font-family: var(--font-mono);
2475
+ }
2476
+
2477
+ .trace-view-content {
2478
+ border-top: 1px solid var(--border);
2479
+ padding: 0.5rem 0.75rem;
2480
+ background: var(--accordion-content-bg);
2481
+ }
2482
+
2483
+ .trace-view.collapsed .trace-view-content {
2484
+ display: none;
2485
+ }
2486
+
2487
+ .trace-view-axis {
2488
+ display: flex;
2489
+ justify-content: space-between;
2490
+ font-size: 0.625rem;
2491
+ font-family: var(--font-mono);
2492
+ color: var(--muted-foreground);
2493
+ padding-bottom: 0.375rem;
2494
+ margin-bottom: 0.375rem;
2495
+ border-bottom: 1px solid var(--border);
2496
+ }
2497
+
2498
+ .trace-view-row {
2499
+ display: flex;
2500
+ align-items: center;
2501
+ gap: 0.5rem;
2502
+ padding: 0.1875rem 0;
2503
+ font-size: 0.75rem;
2504
+ }
2505
+
2506
+ .trace-view-name {
2507
+ width: 35%;
2508
+ flex-shrink: 0;
2509
+ display: flex;
2510
+ align-items: center;
2511
+ gap: 0.375rem;
2512
+ font-family: var(--font-mono);
2513
+ white-space: nowrap;
2514
+ overflow: hidden;
2515
+ text-overflow: ellipsis;
2516
+ color: var(--foreground);
2517
+ }
2518
+
2519
+ .trace-view-status-dot {
2520
+ width: 8px;
2521
+ height: 8px;
2522
+ border-radius: 50%;
2523
+ flex-shrink: 0;
2524
+ }
2525
+
2526
+ .trace-view-status-ok { background: var(--success); }
2527
+ .trace-view-status-error { background: var(--error); }
2528
+ .trace-view-status-unset { background: var(--muted-foreground); }
2529
+
2530
+ .trace-view-bar-container {
2531
+ flex: 1;
2532
+ position: relative;
2533
+ height: 1.25rem;
2534
+ background: var(--muted);
2535
+ border-radius: 2px;
2536
+ }
2537
+
2538
+ .trace-view-bar {
2539
+ position: absolute;
2540
+ top: 0;
2541
+ height: 100%;
2542
+ border-radius: 2px;
2543
+ min-width: 2px;
2544
+ display: flex;
2545
+ align-items: center;
2546
+ padding: 0 0.375rem;
2547
+ font-size: 0.625rem;
2548
+ font-family: var(--font-mono);
2549
+ color: white;
2550
+ white-space: nowrap;
2551
+ overflow: hidden;
2552
+ }
2553
+
2554
+ .trace-view-bar-ok { background: var(--success); }
2555
+ .trace-view-bar-error { background: var(--error); }
2556
+ .trace-view-bar-unset { background: var(--muted-foreground); }
2557
+
2558
+ @media print {
2559
+ .trace-view.collapsed .trace-view-content {
2560
+ display: block;
2561
+ }
2562
+ }
2563
+
2416
2564
  `;
2417
2565
 
2418
2566
  // src/formatters/html/renderers/status.ts
@@ -2657,10 +2805,11 @@ function renderStep(step, stepResult, index, deps) {
2657
2805
  const isContinuation = CONTINUATION_KEYWORDS.includes(keywordTrimmed);
2658
2806
  const stepClass = isContinuation ? "step continuation" : "step";
2659
2807
  const stepDocs = deps.renderDocs(step.docs, "step-docs");
2808
+ const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
2660
2809
  return `<div class="${stepClass}">
2661
2810
  <span class="step-status ${statusClass}">${statusIcon}</span>
2662
2811
  <span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
2663
- <span class="step-text">${deps.escapeHtml(step.text)}</span>
2812
+ <span class="step-text">${textHtml}</span>
2664
2813
  <span class="step-duration">${duration}</span>
2665
2814
  </div>${stepDocs}`;
2666
2815
  }
@@ -2672,6 +2821,30 @@ function renderSteps(args, deps) {
2672
2821
  return `<div class="steps">${stepsHtml}</div>`;
2673
2822
  }
2674
2823
 
2824
+ // src/formatters/html/renderers/step-params.ts
2825
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
2826
+ function highlightStepParams(text, deps) {
2827
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
2828
+ if (matches.length === 0) {
2829
+ return deps.escapeHtml(text);
2830
+ }
2831
+ let result = "";
2832
+ let lastIndex = 0;
2833
+ for (const match of matches) {
2834
+ const matchStart = match.index;
2835
+ const matchEnd = matchStart + match[0].length;
2836
+ if (matchStart > lastIndex) {
2837
+ result += deps.escapeHtml(text.slice(lastIndex, matchStart));
2838
+ }
2839
+ result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
2840
+ lastIndex = matchEnd;
2841
+ }
2842
+ if (lastIndex < text.length) {
2843
+ result += deps.escapeHtml(text.slice(lastIndex));
2844
+ }
2845
+ return result;
2846
+ }
2847
+
2675
2848
  // src/formatters/html/renderers/scenario.ts
2676
2849
  function renderScenario(args, deps) {
2677
2850
  const { tc } = args;
@@ -2679,6 +2852,19 @@ function renderScenario(args, deps) {
2679
2852
  const statusClass = `status-${tc.status}`;
2680
2853
  const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
2681
2854
  const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
2855
+ const otelMeta = tc.story.meta?.otel;
2856
+ let traceBadge = "";
2857
+ if (otelMeta?.traceId) {
2858
+ const shortId = otelMeta.traceId.slice(0, 16);
2859
+ const traceLink = tc.story.docs?.find(
2860
+ (d) => d.kind === "link" && d.label === "View Trace"
2861
+ );
2862
+ if (traceLink) {
2863
+ 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>`;
2864
+ } else {
2865
+ traceBadge = `<span class="tag trace-tag" title="${deps.escapeHtml(otelMeta.traceId)}">${deps.escapeHtml(shortId)}\u2026</span>`;
2866
+ }
2867
+ }
2682
2868
  const storyDocs = deps.renderDocs(tc.story.docs, "story-docs");
2683
2869
  const steps = deps.renderSteps(
2684
2870
  { steps: tc.story.steps, stepResults: tc.stepResults },
@@ -2699,6 +2885,10 @@ function renderScenario(args, deps) {
2699
2885
  embedScreenshots: deps.embedScreenshots
2700
2886
  }
2701
2887
  );
2888
+ const traceView = deps.renderTraceView(
2889
+ { spans: tc.story.otelSpans },
2890
+ { escapeHtml: deps.escapeHtml }
2891
+ );
2702
2892
  const collapsedClass = deps.startCollapsed ? " collapsed" : "";
2703
2893
  const ariaExpanded = !deps.startCollapsed;
2704
2894
  return `
@@ -2709,7 +2899,7 @@ function renderScenario(args, deps) {
2709
2899
  <span class="status-icon ${statusClass}">${statusIcon}</span>
2710
2900
  <span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
2711
2901
  </div>
2712
- <div class="scenario-meta">${tags}</div>
2902
+ <div class="scenario-meta">${tags}${traceBadge}</div>
2713
2903
  </div>
2714
2904
  <span class="scenario-duration">${duration}</span>
2715
2905
  </div>
@@ -2718,6 +2908,193 @@ function renderScenario(args, deps) {
2718
2908
  ${steps}
2719
2909
  ${error}
2720
2910
  ${attachments}
2911
+ ${traceView}
2912
+ </div>
2913
+ </div>`;
2914
+ }
2915
+
2916
+ // src/formatters/html/renderers/trace-view.ts
2917
+ var VALID_STATUSES = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
2918
+ var TOOLTIP_MAX_LENGTH = 4096;
2919
+ function safeStatus(status) {
2920
+ return VALID_STATUSES.has(status) ? status : "unset";
2921
+ }
2922
+ function formatDuration(ms) {
2923
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
2924
+ return `${ms.toFixed(1)}ms`;
2925
+ }
2926
+ function clamp(value, min, max) {
2927
+ return Math.min(max, Math.max(min, value));
2928
+ }
2929
+ function normalizeSpans(spans) {
2930
+ const result = [];
2931
+ for (const span of spans) {
2932
+ if (!span || typeof span !== "object") continue;
2933
+ if (typeof span.spanId !== "string" || typeof span.name !== "string") continue;
2934
+ let startTimeMs;
2935
+ let durationMs;
2936
+ if (span.startTimeMs != null && span.durationMs != null) {
2937
+ startTimeMs = span.startTimeMs;
2938
+ durationMs = span.durationMs;
2939
+ } else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
2940
+ startTimeMs = span.startTimeUnixNano / 1e6;
2941
+ durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
2942
+ } else {
2943
+ continue;
2944
+ }
2945
+ durationMs = Math.max(0, durationMs);
2946
+ if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
2947
+ result.push({
2948
+ spanId: span.spanId,
2949
+ parentSpanId: span.parentSpanId,
2950
+ name: span.name,
2951
+ startTimeMs,
2952
+ durationMs,
2953
+ status: safeStatus(span.status),
2954
+ statusMessage: span.statusMessage,
2955
+ attributes: span.attributes
2956
+ });
2957
+ }
2958
+ return result;
2959
+ }
2960
+ function buildTree(spans) {
2961
+ const byId = /* @__PURE__ */ new Map();
2962
+ for (const span of spans) {
2963
+ let key = span.spanId;
2964
+ if (byId.has(key)) {
2965
+ let suffix = 2;
2966
+ while (byId.has(`${span.spanId}__dup${suffix}`)) suffix++;
2967
+ key = `${span.spanId}__dup${suffix}`;
2968
+ }
2969
+ byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
2970
+ }
2971
+ const roots = [];
2972
+ for (const node of byId.values()) {
2973
+ const parentId = node.span.parentSpanId;
2974
+ const parent = parentId ? byId.get(parentId) : void 0;
2975
+ if (parent && parent !== node) {
2976
+ parent.children.push(node);
2977
+ } else {
2978
+ roots.push(node);
2979
+ }
2980
+ }
2981
+ for (const node of byId.values()) {
2982
+ node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
2983
+ }
2984
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
2985
+ const visited = /* @__PURE__ */ new Set();
2986
+ function assignDepth(node, depth) {
2987
+ if (visited.has(node.span.spanId)) return;
2988
+ visited.add(node.span.spanId);
2989
+ node.depth = depth;
2990
+ for (const child of node.children) {
2991
+ assignDepth(child, depth + 1);
2992
+ }
2993
+ }
2994
+ for (const root of roots) {
2995
+ assignDepth(root, 0);
2996
+ }
2997
+ for (const node of byId.values()) {
2998
+ if (!visited.has(node.span.spanId)) {
2999
+ node.children = [];
3000
+ roots.push(node);
3001
+ assignDepth(node, 0);
3002
+ }
3003
+ }
3004
+ roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
3005
+ return roots;
3006
+ }
3007
+ function flattenTree(roots) {
3008
+ const result = [];
3009
+ function walk(node) {
3010
+ result.push(node);
3011
+ for (const child of node.children) {
3012
+ walk(child);
3013
+ }
3014
+ }
3015
+ for (const root of roots) {
3016
+ walk(root);
3017
+ }
3018
+ return result;
3019
+ }
3020
+ function buildTooltip(span, escapeHtml2) {
3021
+ const parts = [];
3022
+ parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
3023
+ if (span.statusMessage) {
3024
+ parts.push(`Status: ${span.statusMessage}`);
3025
+ }
3026
+ if (span.attributes) {
3027
+ const keys = Object.keys(span.attributes).sort();
3028
+ for (const key of keys) {
3029
+ const val = span.attributes[key];
3030
+ const formatted = Array.isArray(val) ? `[${val.map((v) => String(v)).join(", ")}]` : String(val);
3031
+ parts.push(`${key}=${formatted}`);
3032
+ }
3033
+ }
3034
+ let text = parts.join("\n");
3035
+ if (text.length > TOOLTIP_MAX_LENGTH) {
3036
+ text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
3037
+ }
3038
+ return escapeHtml2(text);
3039
+ }
3040
+ function renderTraceView(args, deps) {
3041
+ if (!args.spans || args.spans.length === 0) return "";
3042
+ const normalized = normalizeSpans(args.spans);
3043
+ if (normalized.length === 0) return "";
3044
+ const roots = buildTree(normalized);
3045
+ const flat = flattenTree(roots);
3046
+ let minStart = Infinity;
3047
+ let maxEnd = -Infinity;
3048
+ for (const node of flat) {
3049
+ const s = node.span.startTimeMs;
3050
+ const e = s + node.span.durationMs;
3051
+ if (s < minStart) minStart = s;
3052
+ if (e > maxEnd) maxEnd = e;
3053
+ }
3054
+ let totalDuration = maxEnd - minStart;
3055
+ if (totalDuration <= 0) totalDuration = 1;
3056
+ const rows = flat.map((node) => {
3057
+ const { span, depth } = node;
3058
+ const indent = depth * 16;
3059
+ const minWidth = 0.5;
3060
+ let spanLeft = clamp(
3061
+ (span.startTimeMs - minStart) / totalDuration * 100,
3062
+ 0,
3063
+ 100
3064
+ );
3065
+ if (spanLeft + minWidth > 100) {
3066
+ spanLeft = 100 - minWidth;
3067
+ }
3068
+ const spanWidth = clamp(
3069
+ span.durationMs / totalDuration * 100,
3070
+ minWidth,
3071
+ 100 - spanLeft
3072
+ );
3073
+ const tooltip = buildTooltip(span, deps.escapeHtml);
3074
+ const durationLabel = formatDuration(span.durationMs);
3075
+ return ` <div class="trace-view-row">
3076
+ <div class="trace-view-name" style="padding-left: ${indent}px" title="${deps.escapeHtml(span.name)}">
3077
+ <span class="trace-view-status-dot trace-view-status-${span.status}"></span>
3078
+ ${deps.escapeHtml(span.name)}
3079
+ </div>
3080
+ <div class="trace-view-bar-container">
3081
+ <div class="trace-view-bar trace-view-bar-${span.status}" style="left: ${spanLeft.toFixed(2)}%; width: ${spanWidth.toFixed(2)}%" title="${tooltip}">${durationLabel}</div>
3082
+ </div>
3083
+ </div>`;
3084
+ }).join("\n");
3085
+ const axisEnd = formatDuration(maxEnd - minStart);
3086
+ return `<div class="trace-view collapsed">
3087
+ <div class="trace-view-header" role="button" tabindex="0" aria-expanded="false">
3088
+ <span>Spans</span>
3089
+ <span class="trace-view-count">${flat.length}</span>
3090
+ <span class="chevron">&#9660;</span>
3091
+ </div>
3092
+ <div class="trace-view-content">
3093
+ <div class="trace-view-axis">
3094
+ <span>0ms</span>
3095
+ <span>${axisEnd}</span>
3096
+ </div>
3097
+ ${rows}
2721
3098
  </div>
2722
3099
  </div>`;
2723
3100
  }
@@ -2843,7 +3220,8 @@ function createHtmlFormatter(options = {}) {
2843
3220
  const stepsDeps = {
2844
3221
  escapeHtml,
2845
3222
  getStatusIcon,
2846
- renderDocs
3223
+ renderDocs,
3224
+ highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
2847
3225
  };
2848
3226
  const scenarioDeps = {
2849
3227
  escapeHtml,
@@ -2853,6 +3231,7 @@ function createHtmlFormatter(options = {}) {
2853
3231
  renderDocs,
2854
3232
  renderErrorBox: (args, d) => renderErrorBox(args, d),
2855
3233
  renderAttachments: (args, d) => renderAttachments(args, d),
3234
+ renderTraceView: (args, d) => renderTraceView(args, d),
2856
3235
  embedScreenshots: opts.embedScreenshots
2857
3236
  };
2858
3237
  const featureDeps = {
@@ -3145,6 +3524,7 @@ var MarkdownFormatter = class {
3145
3524
  includeSummaryTable: options.includeSummaryTable ?? false,
3146
3525
  permalinkBaseUrl: options.permalinkBaseUrl,
3147
3526
  ticketUrlTemplate: options.ticketUrlTemplate,
3527
+ traceUrlTemplate: options.traceUrlTemplate,
3148
3528
  includeSourceLinks: options.includeSourceLinks ?? true,
3149
3529
  customRenderers: options.customRenderers
3150
3530
  };
@@ -3367,6 +3747,18 @@ var MarkdownFormatter = class {
3367
3747
  meta.push(`Tickets: ${tc.story.tickets.map((t) => `\`${t}\``).join(", ")}`);
3368
3748
  }
3369
3749
  }
3750
+ const otelMeta = tc.story.meta?.otel;
3751
+ if (otelMeta?.traceId) {
3752
+ const traceTemplate = this.options.traceUrlTemplate;
3753
+ if (traceTemplate) {
3754
+ const url = traceTemplate.replace(/\{traceId\}/g, otelMeta.traceId);
3755
+ meta.push(
3756
+ `Trace: [${otelMeta.traceId.slice(0, 16)}\u2026](${url})`
3757
+ );
3758
+ } else {
3759
+ meta.push(`Trace: \`${otelMeta.traceId}\``);
3760
+ }
3761
+ }
3370
3762
  if (meta.length > 0) {
3371
3763
  lines.push(meta.join(" | "));
3372
3764
  }
@@ -5205,7 +5597,7 @@ function readBranchName(cwd = process.cwd()) {
5205
5597
  }
5206
5598
 
5207
5599
  // src/utils/duration.ts
5208
- function formatDuration(ms) {
5600
+ function formatDuration2(ms) {
5209
5601
  if (ms < 1e3) {
5210
5602
  return `${Math.round(ms)} ms`;
5211
5603
  }
@@ -5309,6 +5701,31 @@ function detectCI4(env = process.env) {
5309
5701
  return void 0;
5310
5702
  }
5311
5703
 
5704
+ // src/utils/otel-detect.ts
5705
+ import { createRequire } from "module";
5706
+ function getRequire() {
5707
+ const url = import.meta.url ?? (typeof __filename !== "undefined" ? `file://${__filename}` : void 0);
5708
+ if (!url) throw new Error("Cannot determine module URL");
5709
+ return createRequire(url);
5710
+ }
5711
+ function tryGetActiveOtelContext() {
5712
+ try {
5713
+ const api = getRequire()("@opentelemetry/api");
5714
+ const span = api.trace?.getActiveSpan?.();
5715
+ if (!span) return void 0;
5716
+ const ctx = span.spanContext?.();
5717
+ if (!ctx?.traceId || ctx.traceId === "00000000000000000000000000000000")
5718
+ return void 0;
5719
+ return { traceId: ctx.traceId, spanId: ctx.spanId };
5720
+ } catch {
5721
+ return void 0;
5722
+ }
5723
+ }
5724
+ function resolveTraceUrl(template, traceId) {
5725
+ if (!template) return void 0;
5726
+ return template.replace(/\{traceId\}/g, traceId);
5727
+ }
5728
+
5312
5729
  // src/index.ts
5313
5730
  var FORMAT_EXTENSIONS = {
5314
5731
  markdown: ".md",
@@ -5499,6 +5916,7 @@ var ReportGenerator = class {
5499
5916
  includeSummaryTable: options.markdown?.includeSummaryTable ?? false,
5500
5917
  permalinkBaseUrl: options.markdown?.permalinkBaseUrl,
5501
5918
  ticketUrlTemplate: options.markdown?.ticketUrlTemplate,
5919
+ traceUrlTemplate: options.markdown?.traceUrlTemplate,
5502
5920
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
5503
5921
  customRenderers: options.markdown?.customRenderers
5504
5922
  }
@@ -5624,6 +6042,7 @@ var ReportGenerator = class {
5624
6042
  includeSummaryTable: this.options.markdown.includeSummaryTable,
5625
6043
  permalinkBaseUrl: this.options.markdown.permalinkBaseUrl,
5626
6044
  ticketUrlTemplate: this.options.markdown.ticketUrlTemplate,
6045
+ traceUrlTemplate: this.options.markdown.traceUrlTemplate,
5627
6046
  includeSourceLinks: this.options.markdown.includeSourceLinks,
5628
6047
  customRenderers: this.options.markdown.customRenderers
5629
6048
  });
@@ -5668,7 +6087,7 @@ export {
5668
6087
  deriveStepResults,
5669
6088
  detectCI4 as detectCI,
5670
6089
  findGitDir,
5671
- formatDuration,
6090
+ formatDuration2 as formatDuration,
5672
6091
  generateRunId,
5673
6092
  generateTestCaseId,
5674
6093
  mergeStepResults,
@@ -5685,7 +6104,9 @@ export {
5685
6104
  readPackageVersion,
5686
6105
  resolveAttachment,
5687
6106
  resolveAttachments,
6107
+ resolveTraceUrl,
5688
6108
  slugify,
6109
+ tryGetActiveOtelContext,
5689
6110
  validateCanonicalRun
5690
6111
  };
5691
6112
  //# sourceMappingURL=index.js.map