executable-stories-formatters 0.13.0 → 0.15.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.cjs CHANGED
@@ -15269,7 +15269,10 @@ var MarkdownFormatter = class {
15269
15269
  ticketUrlTemplate: options.ticketUrlTemplate,
15270
15270
  traceUrlTemplate: options.traceUrlTemplate,
15271
15271
  includeSourceLinks: options.includeSourceLinks ?? true,
15272
- customRenderers: options.customRenderers
15272
+ customRenderers: options.customRenderers,
15273
+ scenarioAnchor: options.scenarioAnchor,
15274
+ scenarioBadge: options.scenarioBadge,
15275
+ scenarioNoteLink: options.scenarioNoteLink
15273
15276
  };
15274
15277
  }
15275
15278
  /**
@@ -15456,6 +15459,11 @@ var MarkdownFormatter = class {
15456
15459
  * Render a single scenario.
15457
15460
  */
15458
15461
  renderScenario(lines, tc) {
15462
+ const anchorId = this.options.scenarioAnchor?.(tc);
15463
+ if (anchorId) {
15464
+ lines.push(`<a id="${anchorId}"></a>`);
15465
+ lines.push("");
15466
+ }
15459
15467
  if (this.options.customRenderers?.renderScenarioHeader) {
15460
15468
  const custom = this.options.customRenderers.renderScenarioHeader(tc);
15461
15469
  if (custom !== null) {
@@ -15471,6 +15479,14 @@ var MarkdownFormatter = class {
15471
15479
  icon = this.getStatusIcon(tc.status) + " ";
15472
15480
  }
15473
15481
  lines.push(`${headingPrefix} ${icon}${tc.story.scenario}`);
15482
+ const badge = this.options.scenarioBadge?.(tc);
15483
+ if (badge) {
15484
+ lines.push(badge);
15485
+ }
15486
+ const noteLink = this.options.scenarioNoteLink?.(tc);
15487
+ if (noteLink) {
15488
+ lines.push(noteLink);
15489
+ }
15474
15490
  if (this.options.includeSourceLinks && this.options.permalinkBaseUrl && tc.sourceFile !== "unknown") {
15475
15491
  const permalink = this.buildPermalink(tc);
15476
15492
  lines.push(`Source: [${tc.sourceFile}](${permalink})`);
@@ -18287,12 +18303,16 @@ function groupBy7(items, keyFn) {
18287
18303
  var fs5 = __toESM(require("fs"), 1);
18288
18304
  var path6 = __toESM(require("path"), 1);
18289
18305
  var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
18290
- function isLocalPath(src) {
18306
+ function isRemoteRef(src) {
18291
18307
  const trimmed = src.trim();
18292
- if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
18293
- return false;
18294
- }
18295
- return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
18308
+ return SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
18309
+ }
18310
+ function isAbsoluteRef(src) {
18311
+ const trimmed = src.trim();
18312
+ return path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed);
18313
+ }
18314
+ function isRelativeLocalPath(src) {
18315
+ return !isRemoteRef(src) && !isAbsoluteRef(src);
18296
18316
  }
18297
18317
  function stripCodeContent(markdown) {
18298
18318
  let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
@@ -18308,21 +18328,21 @@ function scanMarkdownAssets(markdown) {
18308
18328
  let match;
18309
18329
  while ((match = mdImageRe.exec(stripped)) !== null) {
18310
18330
  const src = match[1].trim();
18311
- if (isLocalPath(src)) {
18331
+ if (!isRemoteRef(src)) {
18312
18332
  found.add(src);
18313
18333
  }
18314
18334
  }
18315
18335
  const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
18316
18336
  while ((match = htmlSrcRe.exec(stripped)) !== null) {
18317
18337
  const src = match[1].trim();
18318
- if (isLocalPath(src)) {
18338
+ if (!isRemoteRef(src)) {
18319
18339
  found.add(src);
18320
18340
  }
18321
18341
  }
18322
18342
  const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
18323
18343
  while ((match = posterRe.exec(stripped)) !== null) {
18324
18344
  const src = match[1].trim();
18325
- if (isLocalPath(src)) {
18345
+ if (!isRemoteRef(src)) {
18326
18346
  found.add(src);
18327
18347
  }
18328
18348
  }
@@ -18348,48 +18368,21 @@ function isCode(segment) {
18348
18368
  const trimmed = segment.trimStart();
18349
18369
  return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
18350
18370
  }
18371
+ function resolveRewrite(trimmed, assetsBaseUrl, pathMap) {
18372
+ if (isRemoteRef(trimmed)) return null;
18373
+ if (pathMap) {
18374
+ const mapped = pathMap.get(trimmed);
18375
+ return mapped === void 0 ? null : `${assetsBaseUrl}/${mapped}`;
18376
+ }
18377
+ if (!isRelativeLocalPath(trimmed)) return null;
18378
+ return `${assetsBaseUrl}/${trimmed}`;
18379
+ }
18351
18380
  function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
18352
- let result = prose;
18353
- result = result.replace(
18354
- /(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g,
18355
- (full, pre, src, post) => {
18356
- const trimmed = src.trim();
18357
- if (!isLocalPath(trimmed)) return full;
18358
- if (pathMap) {
18359
- const mapped = pathMap.get(trimmed);
18360
- if (mapped === void 0) return full;
18361
- return `${pre}${assetsBaseUrl}/${mapped}${post}`;
18362
- }
18363
- return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
18364
- }
18365
- );
18366
- result = result.replace(
18367
- /(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
18368
- (full, pre, src, post) => {
18369
- const trimmed = src.trim();
18370
- if (!isLocalPath(trimmed)) return full;
18371
- if (pathMap) {
18372
- const mapped = pathMap.get(trimmed);
18373
- if (mapped === void 0) return full;
18374
- return `${pre}${assetsBaseUrl}/${mapped}${post}`;
18375
- }
18376
- return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
18377
- }
18378
- );
18379
- result = result.replace(
18380
- /(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
18381
- (full, pre, src, post) => {
18382
- const trimmed = src.trim();
18383
- if (!isLocalPath(trimmed)) return full;
18384
- if (pathMap) {
18385
- const mapped = pathMap.get(trimmed);
18386
- if (mapped === void 0) return full;
18387
- return `${pre}${assetsBaseUrl}/${mapped}${post}`;
18388
- }
18389
- return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
18390
- }
18391
- );
18392
- return result;
18381
+ const rewrite = (full, pre, src, post) => {
18382
+ const target = resolveRewrite(src.trim(), assetsBaseUrl, pathMap);
18383
+ return target === null ? full : `${pre}${target}${post}`;
18384
+ };
18385
+ return prose.replace(/(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g, rewrite).replace(/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi, rewrite).replace(/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi, rewrite);
18393
18386
  }
18394
18387
  function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
18395
18388
  return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
@@ -18406,8 +18399,9 @@ function copyMarkdownAssets(options) {
18406
18399
  const pathMap = /* @__PURE__ */ new Map();
18407
18400
  const missing = [];
18408
18401
  for (const ref of refs) {
18409
- const absPath = path6.resolve(markdownDir, ref);
18402
+ const absPath = isAbsoluteRef(ref) ? ref : path6.resolve(markdownDir, ref);
18410
18403
  if (!fs5.existsSync(absPath)) {
18404
+ if (isAbsoluteRef(ref)) continue;
18411
18405
  if (!allowMissing) {
18412
18406
  throw new Error(`Asset not found: ${absPath}`);
18413
18407
  }
@@ -22034,7 +22028,10 @@ var ReportGenerator = class {
22034
22028
  permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
22035
22029
  ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
22036
22030
  traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
22037
- customRenderers: options.astro?.markdown?.customRenderers
22031
+ customRenderers: options.astro?.markdown?.customRenderers,
22032
+ scenarioAnchor: options.astro?.markdown?.scenarioAnchor,
22033
+ scenarioBadge: options.astro?.markdown?.scenarioBadge,
22034
+ scenarioNoteLink: options.astro?.markdown?.scenarioNoteLink
22038
22035
  }
22039
22036
  },
22040
22037
  assetMode: options.assetMode ?? "none",