executable-stories-formatters 0.7.5 → 0.7.7

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
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ AstroFormatter: () => AstroFormatter,
33
34
  CucumberHtmlFormatter: () => CucumberHtmlFormatter,
34
35
  CucumberJsonFormatter: () => CucumberJsonFormatter,
35
36
  CucumberMessagesFormatter: () => CucumberMessagesFormatter,
@@ -53,6 +54,7 @@ __export(src_exports, {
53
54
  canonicalizeRun: () => canonicalizeRun,
54
55
  clearVersionCache: () => clearVersionCache,
55
56
  computeTestMetrics: () => computeTestMetrics,
57
+ copyMarkdownAssets: () => copyMarkdownAssets,
56
58
  createPrCommentSummary: () => createPrCommentSummary,
57
59
  createReportGenerator: () => createReportGenerator,
58
60
  deriveStepResults: () => deriveStepResults,
@@ -85,6 +87,7 @@ __export(src_exports, {
85
87
  resolveAttachments: () => resolveAttachments,
86
88
  resolveTheme: () => resolveTheme,
87
89
  resolveTraceUrl: () => resolveTraceUrl,
90
+ rewriteAssetPaths: () => rewriteAssetPaths,
88
91
  saveHistory: () => saveHistory,
89
92
  sendNotifications: () => sendNotifications,
90
93
  sendSlackNotification: () => sendSlackNotification,
@@ -100,8 +103,8 @@ __export(src_exports, {
100
103
  validateCanonicalRun: () => validateCanonicalRun
101
104
  });
102
105
  module.exports = __toCommonJS(src_exports);
103
- var fs6 = require("fs");
104
- var path6 = __toESM(require("path"), 1);
106
+ var fs7 = require("fs");
107
+ var path7 = __toESM(require("path"), 1);
105
108
  var fsPromises = __toESM(require("fs/promises"), 1);
106
109
 
107
110
  // src/converters/acl/status.ts
@@ -3452,6 +3455,12 @@ body {
3452
3455
  font-weight: 600;
3453
3456
  font-size: 0.875rem;
3454
3457
  color: var(--foreground);
3458
+ text-decoration: none;
3459
+ cursor: pointer;
3460
+ }
3461
+
3462
+ a.toc-title:hover {
3463
+ color: var(--primary);
3455
3464
  }
3456
3465
 
3457
3466
  .toc-feature {
@@ -3629,7 +3638,7 @@ function corporateBuildBody(args, deps) {
3629
3638
  const sidebar = `
3630
3639
  <nav class="toc">
3631
3640
  <div class="toc-header">
3632
- <div class="toc-title">Test Report</div>
3641
+ <a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Test Report</a>
3633
3642
  <div class="toc-stats">
3634
3643
  <div class="toc-stat-row">
3635
3644
  <span class="toc-stat-label">Total</span>
@@ -13257,7 +13266,7 @@ function renderScenario(args, deps) {
13257
13266
  <div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
13258
13267
  </div>
13259
13268
  <div class="scenario-actions">
13260
- <button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown">&#x2398;</button>
13269
+ <button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
13261
13270
  <button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
13262
13271
  <span class="scenario-duration">${duration}</span>
13263
13272
  </div>
@@ -13636,7 +13645,7 @@ function renderToc(args, deps) {
13636
13645
  }
13637
13646
  return `<nav class="toc-sidebar" aria-label="Table of contents">
13638
13647
  <div class="toc-header">
13639
- <span class="toc-title">Contents</span>
13648
+ <a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Contents</a>
13640
13649
  </div>
13641
13650
  <div class="toc-body">
13642
13651
  ${features.join("\n")}
@@ -15097,8 +15106,8 @@ function extractDocAttachments(step) {
15097
15106
  }
15098
15107
  return attachments;
15099
15108
  }
15100
- function guessMediaType(path7) {
15101
- const lower = path7.toLowerCase();
15109
+ function guessMediaType(path8) {
15110
+ const lower = path8.toLowerCase();
15102
15111
  if (lower.endsWith(".png")) return "image/png";
15103
15112
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15104
15113
  if (lower.endsWith(".gif")) return "image/gif";
@@ -15239,11 +15248,11 @@ var CucumberHtmlFormatter = class {
15239
15248
  for (const envelope of envelopes) {
15240
15249
  const accepted = htmlStream.write(envelope);
15241
15250
  if (!accepted) {
15242
- await new Promise((resolve5) => htmlStream.once("drain", resolve5));
15251
+ await new Promise((resolve7) => htmlStream.once("drain", resolve7));
15243
15252
  }
15244
15253
  }
15245
- await new Promise((resolve5, reject) => {
15246
- collector.on("finish", resolve5);
15254
+ await new Promise((resolve7, reject) => {
15255
+ collector.on("finish", resolve7);
15247
15256
  collector.on("error", reject);
15248
15257
  htmlStream.end();
15249
15258
  });
@@ -16245,6 +16254,177 @@ function replaceAssetRef(html, original, replacement) {
16245
16254
  return html;
16246
16255
  }
16247
16256
 
16257
+ // src/formatters/astro.ts
16258
+ var AstroFormatter = class _AstroFormatter {
16259
+ markdownFormatter;
16260
+ title;
16261
+ constructor(options = {}) {
16262
+ this.title = options.markdown?.title ?? "User Stories";
16263
+ this.markdownFormatter = new MarkdownFormatter({
16264
+ ...options.markdown,
16265
+ title: this.title,
16266
+ stepStyle: "gherkin",
16267
+ includeFrontMatter: false,
16268
+ includeSummaryTable: false,
16269
+ includeMetadata: false
16270
+ });
16271
+ }
16272
+ format(run) {
16273
+ const markdown = this.markdownFormatter.format(run);
16274
+ const body = markdown.replace(/^# .+\n\n?/, "");
16275
+ const frontmatter = this.buildFrontmatter(run);
16276
+ return `${frontmatter}
16277
+ ${body}`;
16278
+ }
16279
+ buildFrontmatter(run) {
16280
+ const badge = _AstroFormatter.computeBadge(run.testCases);
16281
+ const count = run.testCases.length;
16282
+ const description = `${count} scenario${count !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
16283
+ const lines = [
16284
+ "---",
16285
+ `title: ${this.title}`,
16286
+ `description: ${description}`,
16287
+ "sidebar:",
16288
+ " badge:",
16289
+ ` text: ${badge.text}`,
16290
+ ` variant: ${badge.variant}`,
16291
+ "---"
16292
+ ];
16293
+ return lines.join("\n");
16294
+ }
16295
+ static computeBadge(testCases) {
16296
+ const statuses = new Set(testCases.map((tc) => tc.status));
16297
+ if (statuses.has("failed")) return { text: "Failed", variant: "danger" };
16298
+ if (statuses.has("pending")) return { text: "Pending", variant: "caution" };
16299
+ if (statuses.has("skipped") && !statuses.has("passed")) return { text: "Skipped", variant: "caution" };
16300
+ return { text: "Passed", variant: "success" };
16301
+ }
16302
+ };
16303
+
16304
+ // src/formatters/astro-assets.ts
16305
+ var fs4 = __toESM(require("fs"), 1);
16306
+ var path4 = __toESM(require("path"), 1);
16307
+ var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
16308
+ function isLocalPath(src) {
16309
+ const trimmed = src.trim();
16310
+ if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
16311
+ return false;
16312
+ }
16313
+ return !path4.posix.isAbsolute(trimmed) && !path4.win32.isAbsolute(trimmed);
16314
+ }
16315
+ function stripCodeContent(markdown) {
16316
+ let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
16317
+ result = result.replace(/(`+)(?:(?!\1).)+\1/g, "");
16318
+ result = result.replace(/<pre\b[^>]*>[\s\S]*?<\/pre>/gi, "");
16319
+ result = result.replace(/<code\b[^>]*>[\s\S]*?<\/code>/gi, "");
16320
+ return result;
16321
+ }
16322
+ function scanMarkdownAssets(markdown) {
16323
+ const found = /* @__PURE__ */ new Set();
16324
+ const stripped = stripCodeContent(markdown);
16325
+ const mdImageRe = /!\[[^\]]*\]\(([^)"'\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
16326
+ let match;
16327
+ while ((match = mdImageRe.exec(stripped)) !== null) {
16328
+ const src = match[1].trim();
16329
+ if (isLocalPath(src)) {
16330
+ found.add(src);
16331
+ }
16332
+ }
16333
+ const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
16334
+ while ((match = htmlSrcRe.exec(stripped)) !== null) {
16335
+ const src = match[1].trim();
16336
+ if (isLocalPath(src)) {
16337
+ found.add(src);
16338
+ }
16339
+ }
16340
+ return Array.from(found);
16341
+ }
16342
+ function splitByCode(markdown) {
16343
+ const codeRe = /^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$|<pre\b[^>]*>[\s\S]*?<\/pre>|<code\b[^>]*>[\s\S]*?<\/code>|(`+)(?:(?!\2).)+\2/gim;
16344
+ const segments = [];
16345
+ let lastIndex = 0;
16346
+ for (const match of markdown.matchAll(codeRe)) {
16347
+ if (match.index > lastIndex) {
16348
+ segments.push(markdown.slice(lastIndex, match.index));
16349
+ }
16350
+ segments.push(match[0]);
16351
+ lastIndex = match.index + match[0].length;
16352
+ }
16353
+ if (lastIndex < markdown.length) {
16354
+ segments.push(markdown.slice(lastIndex));
16355
+ }
16356
+ return segments;
16357
+ }
16358
+ function isCode(segment) {
16359
+ const trimmed = segment.trimStart();
16360
+ return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
16361
+ }
16362
+ function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
16363
+ let result = prose;
16364
+ result = result.replace(
16365
+ /(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g,
16366
+ (full, pre, src, post) => {
16367
+ const trimmed = src.trim();
16368
+ if (!isLocalPath(trimmed)) return full;
16369
+ if (pathMap) {
16370
+ const mapped = pathMap.get(trimmed);
16371
+ if (mapped === void 0) return full;
16372
+ return `${pre}${assetsBaseUrl}/${mapped}${post}`;
16373
+ }
16374
+ return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
16375
+ }
16376
+ );
16377
+ result = result.replace(
16378
+ /(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
16379
+ (full, pre, src, post) => {
16380
+ const trimmed = src.trim();
16381
+ if (!isLocalPath(trimmed)) return full;
16382
+ if (pathMap) {
16383
+ const mapped = pathMap.get(trimmed);
16384
+ if (mapped === void 0) return full;
16385
+ return `${pre}${assetsBaseUrl}/${mapped}${post}`;
16386
+ }
16387
+ return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
16388
+ }
16389
+ );
16390
+ return result;
16391
+ }
16392
+ function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
16393
+ return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
16394
+ }
16395
+ function copyMarkdownAssets(options) {
16396
+ const {
16397
+ markdown,
16398
+ markdownDir,
16399
+ assetsDir,
16400
+ assetsBaseUrl,
16401
+ allowMissing = false
16402
+ } = options;
16403
+ const refs = scanMarkdownAssets(markdown);
16404
+ const pathMap = /* @__PURE__ */ new Map();
16405
+ const missing = [];
16406
+ for (const ref of refs) {
16407
+ const absPath = path4.resolve(markdownDir, ref);
16408
+ if (!fs4.existsSync(absPath)) {
16409
+ if (!allowMissing) {
16410
+ throw new Error(`Asset not found: ${absPath}`);
16411
+ }
16412
+ missing.push(ref);
16413
+ continue;
16414
+ }
16415
+ const relativeCopied = copyAsset(absPath, assetsDir);
16416
+ const fileName = relativeCopied.replace(/^assets\//, "");
16417
+ pathMap.set(ref, fileName);
16418
+ }
16419
+ const rewritten = rewriteAssetPaths(markdown, assetsBaseUrl, pathMap);
16420
+ return {
16421
+ markdown: rewritten,
16422
+ copiedCount: pathMap.size,
16423
+ missingCount: missing.length,
16424
+ missing
16425
+ };
16426
+ }
16427
+
16248
16428
  // src/converters/adapters/jest.ts
16249
16429
  function mapJestStatus(status) {
16250
16430
  switch (status) {
@@ -17012,27 +17192,27 @@ function pickleStepArgumentToDocs(ps) {
17012
17192
  }
17013
17193
 
17014
17194
  // src/utils/git-info.ts
17015
- var fs4 = __toESM(require("fs"), 1);
17016
- var path4 = __toESM(require("path"), 1);
17195
+ var fs5 = __toESM(require("fs"), 1);
17196
+ var path5 = __toESM(require("path"), 1);
17017
17197
  function readGitSha(cwd = process.cwd()) {
17018
17198
  const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
17019
17199
  if (envSha) return envSha;
17020
17200
  const gitDir = findGitDir(cwd);
17021
17201
  if (!gitDir) return void 0;
17022
17202
  try {
17023
- const headPath = path4.join(gitDir, "HEAD");
17024
- const head = fs4.readFileSync(headPath, "utf8").trim();
17203
+ const headPath = path5.join(gitDir, "HEAD");
17204
+ const head = fs5.readFileSync(headPath, "utf8").trim();
17025
17205
  if (!head.startsWith("ref:")) {
17026
17206
  return head;
17027
17207
  }
17028
17208
  const refPath = head.replace("ref:", "").trim();
17029
- const refFile = path4.join(gitDir, refPath);
17030
- if (fs4.existsSync(refFile)) {
17031
- return fs4.readFileSync(refFile, "utf8").trim();
17209
+ const refFile = path5.join(gitDir, refPath);
17210
+ if (fs5.existsSync(refFile)) {
17211
+ return fs5.readFileSync(refFile, "utf8").trim();
17032
17212
  }
17033
- const packedRefs = path4.join(gitDir, "packed-refs");
17034
- if (fs4.existsSync(packedRefs)) {
17035
- const content = fs4.readFileSync(packedRefs, "utf8");
17213
+ const packedRefs = path5.join(gitDir, "packed-refs");
17214
+ if (fs5.existsSync(packedRefs)) {
17215
+ const content = fs5.readFileSync(packedRefs, "utf8");
17036
17216
  for (const line of content.split("\n")) {
17037
17217
  if (!line || line.startsWith("#") || line.startsWith("^")) continue;
17038
17218
  const [sha, ref] = line.split(" ");
@@ -17047,19 +17227,19 @@ function readGitSha(cwd = process.cwd()) {
17047
17227
  function findGitDir(start) {
17048
17228
  let current = start;
17049
17229
  while (true) {
17050
- const candidate = path4.join(current, ".git");
17051
- if (fs4.existsSync(candidate)) {
17052
- const stat = fs4.statSync(candidate);
17230
+ const candidate = path5.join(current, ".git");
17231
+ if (fs5.existsSync(candidate)) {
17232
+ const stat = fs5.statSync(candidate);
17053
17233
  if (stat.isFile()) {
17054
- const content = fs4.readFileSync(candidate, "utf8").trim();
17234
+ const content = fs5.readFileSync(candidate, "utf8").trim();
17055
17235
  const match = content.match(/^gitdir: (.+)$/);
17056
17236
  if (match) {
17057
- return path4.resolve(current, match[1]);
17237
+ return path5.resolve(current, match[1]);
17058
17238
  }
17059
17239
  }
17060
17240
  return candidate;
17061
17241
  }
17062
- const parent = path4.dirname(current);
17242
+ const parent = path5.dirname(current);
17063
17243
  if (parent === current) return void 0;
17064
17244
  current = parent;
17065
17245
  }
@@ -17070,8 +17250,8 @@ function readBranchName(cwd = process.cwd()) {
17070
17250
  const gitDir = findGitDir(cwd);
17071
17251
  if (!gitDir) return void 0;
17072
17252
  try {
17073
- const headPath = path4.join(gitDir, "HEAD");
17074
- const head = fs4.readFileSync(headPath, "utf8").trim();
17253
+ const headPath = path5.join(gitDir, "HEAD");
17254
+ const head = fs5.readFileSync(headPath, "utf8").trim();
17075
17255
  if (head.startsWith("ref:")) {
17076
17256
  const refPath = head.replace("ref:", "").trim();
17077
17257
  const match = refPath.match(/^refs\/heads\/(.+)$/);
@@ -17108,8 +17288,8 @@ function nanosecondsToMs(ns) {
17108
17288
  }
17109
17289
 
17110
17290
  // src/utils/metadata.ts
17111
- var fs5 = __toESM(require("fs"), 1);
17112
- var path5 = __toESM(require("path"), 1);
17291
+ var fs6 = __toESM(require("fs"), 1);
17292
+ var path6 = __toESM(require("path"), 1);
17113
17293
  var versionCache = /* @__PURE__ */ new Map();
17114
17294
  function readPackageVersion(root) {
17115
17295
  if (versionCache.has(root)) {
@@ -17120,18 +17300,18 @@ function readPackageVersion(root) {
17120
17300
  return version;
17121
17301
  }
17122
17302
  function findPackageVersion(startDir) {
17123
- let current = path5.resolve(startDir);
17303
+ let current = path6.resolve(startDir);
17124
17304
  while (true) {
17125
- const pkgPath = path5.join(current, "package.json");
17305
+ const pkgPath = path6.join(current, "package.json");
17126
17306
  try {
17127
- if (fs5.existsSync(pkgPath)) {
17128
- const raw = fs5.readFileSync(pkgPath, "utf8");
17307
+ if (fs6.existsSync(pkgPath)) {
17308
+ const raw = fs6.readFileSync(pkgPath, "utf8");
17129
17309
  const parsed = JSON.parse(raw);
17130
17310
  return parsed.version;
17131
17311
  }
17132
17312
  } catch {
17133
17313
  }
17134
- const parent = path5.dirname(current);
17314
+ const parent = path6.dirname(current);
17135
17315
  if (parent === current) {
17136
17316
  return void 0;
17137
17317
  }
@@ -18057,6 +18237,7 @@ function listScenarios(args, _deps) {
18057
18237
 
18058
18238
  // src/index.ts
18059
18239
  var FORMAT_EXTENSIONS = {
18240
+ astro: ".md",
18060
18241
  markdown: ".md",
18061
18242
  html: ".html",
18062
18243
  "cucumber-html": ".cucumber.html",
@@ -18089,11 +18270,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
18089
18270
  const ext = FORMAT_EXTENSIONS[format];
18090
18271
  const effectiveName = outputName + (outputNameSuffix ?? "");
18091
18272
  if (mode === "aggregated") {
18092
- return toPosix(path6.join(baseOutputDir, `${effectiveName}${ext}`));
18273
+ return toPosix(path7.join(baseOutputDir, `${effectiveName}${ext}`));
18093
18274
  }
18094
18275
  const normalizedSource = toPosix(sourceFile);
18095
- const dirOfSource = path6.posix.dirname(normalizedSource);
18096
- let baseName = path6.posix.basename(normalizedSource);
18276
+ const dirOfSource = path7.posix.dirname(normalizedSource);
18277
+ let baseName = path7.posix.basename(normalizedSource);
18097
18278
  for (const testExt of TEST_EXTENSIONS) {
18098
18279
  if (baseName.endsWith(testExt)) {
18099
18280
  baseName = baseName.slice(0, -testExt.length);
@@ -18102,9 +18283,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
18102
18283
  }
18103
18284
  const fileName = `${baseName}.${effectiveName}${ext}`;
18104
18285
  if (colocatedStyle === "adjacent") {
18105
- return toPosix(path6.posix.join(dirOfSource, fileName));
18286
+ return toPosix(path7.posix.join(dirOfSource, fileName));
18106
18287
  }
18107
- return toPosix(path6.posix.join(baseOutputDir, dirOfSource, fileName));
18288
+ return toPosix(path7.posix.join(baseOutputDir, dirOfSource, fileName));
18108
18289
  }
18109
18290
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
18110
18291
  const groups = /* @__PURE__ */ new Map();
@@ -18175,7 +18356,7 @@ var ReportGenerator = class {
18175
18356
  excludeTags: options.excludeTags ?? [],
18176
18357
  formats: options.formats ?? ["cucumber-json"],
18177
18358
  outputDir: options.outputDir ?? "reports",
18178
- outputName: options.outputName ?? "test-results",
18359
+ outputName: options.outputName ?? "index",
18179
18360
  outputNameTimestamp: options.outputNameTimestamp ?? false,
18180
18361
  sortTestCases: options.sortTestCases ?? "none",
18181
18362
  output: {
@@ -18230,6 +18411,24 @@ var ReportGenerator = class {
18230
18411
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
18231
18412
  customRenderers: options.markdown?.customRenderers
18232
18413
  },
18414
+ astro: {
18415
+ assetsDir: options.astro?.assetsDir ?? "public/stories/assets",
18416
+ assetsBaseUrl: options.astro?.assetsBaseUrl ?? "/stories/assets",
18417
+ markdown: {
18418
+ title: options.astro?.markdown?.title ?? "User Stories",
18419
+ includeStatusIcons: options.astro?.markdown?.includeStatusIcons ?? true,
18420
+ includeErrors: options.astro?.markdown?.includeErrors ?? true,
18421
+ scenarioHeadingLevel: options.astro?.markdown?.scenarioHeadingLevel ?? 3,
18422
+ groupBy: options.astro?.markdown?.groupBy ?? "file",
18423
+ sortScenarios: options.astro?.markdown?.sortScenarios ?? "source",
18424
+ suiteSeparator: options.astro?.markdown?.suiteSeparator ?? " - ",
18425
+ includeSourceLinks: options.astro?.markdown?.includeSourceLinks ?? true,
18426
+ permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
18427
+ ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
18428
+ traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
18429
+ customRenderers: options.astro?.markdown?.customRenderers
18430
+ }
18431
+ },
18233
18432
  assetMode: options.assetMode ?? "none",
18234
18433
  allowMissingAssets: options.allowMissingAssets ?? false
18235
18434
  };
@@ -18267,6 +18466,24 @@ var ReportGenerator = class {
18267
18466
  });
18268
18467
  }
18269
18468
  }
18469
+ const astroPaths = results.get("astro");
18470
+ if (astroPaths) {
18471
+ for (const mdPath of astroPaths) {
18472
+ const content = await fsPromises.readFile(mdPath, "utf8");
18473
+ const mdDir = path7.dirname(mdPath);
18474
+ const assetsDir = path7.resolve(this.options.astro.assetsDir);
18475
+ const result = copyMarkdownAssets({
18476
+ markdown: content,
18477
+ markdownDir: mdDir,
18478
+ assetsDir,
18479
+ assetsBaseUrl: this.options.astro.assetsBaseUrl,
18480
+ allowMissing: this.options.allowMissingAssets
18481
+ });
18482
+ if (result.copiedCount > 0 || result.missingCount > 0) {
18483
+ await this.deps.writeFile(mdPath, result.markdown);
18484
+ }
18485
+ }
18486
+ }
18270
18487
  }
18271
18488
  return results;
18272
18489
  }
@@ -18285,9 +18502,9 @@ var ReportGenerator = class {
18285
18502
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
18286
18503
  const ext = FORMAT_EXTENSIONS[format];
18287
18504
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
18288
- const outputPath = toPosix(path6.join(this.options.outputDir, `${effectiveName}${ext}`));
18505
+ const outputPath = toPosix(path7.join(this.options.outputDir, `${effectiveName}${ext}`));
18289
18506
  const content = await this.formatContent(run, format);
18290
- const dir = path6.dirname(outputPath);
18507
+ const dir = path7.dirname(outputPath);
18291
18508
  await fsPromises.mkdir(dir, { recursive: true });
18292
18509
  await this.deps.writeFile(outputPath, content);
18293
18510
  return [outputPath];
@@ -18299,7 +18516,7 @@ var ReportGenerator = class {
18299
18516
  testCases
18300
18517
  };
18301
18518
  const content = await this.formatContent(groupRun, format);
18302
- const dir = path6.dirname(outputPath);
18519
+ const dir = path7.dirname(outputPath);
18303
18520
  await fsPromises.mkdir(dir, { recursive: true });
18304
18521
  await this.deps.writeFile(outputPath, content);
18305
18522
  writtenPaths.push(outputPath);
@@ -18362,6 +18579,13 @@ var ReportGenerator = class {
18362
18579
  });
18363
18580
  return formatter.formatToString(run);
18364
18581
  }
18582
+ case "astro": {
18583
+ const formatter = new AstroFormatter({
18584
+ assetsBaseUrl: this.options.astro.assetsBaseUrl,
18585
+ markdown: this.options.astro.markdown
18586
+ });
18587
+ return formatter.format(run);
18588
+ }
18365
18589
  case "markdown": {
18366
18590
  const formatter = new MarkdownFormatter({
18367
18591
  title: this.options.markdown.title,
@@ -18399,7 +18623,7 @@ async function generateRunComparison(args) {
18399
18623
  await fsPromises.mkdir(outputDir, { recursive: true });
18400
18624
  for (const format of args.formats) {
18401
18625
  const ext = format === "html" ? ".html" : ".md";
18402
- const outputPath = toPosix(path6.join(outputDir, `${outputName}${ext}`));
18626
+ const outputPath = toPosix(path7.join(outputDir, `${outputName}${ext}`));
18403
18627
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
18404
18628
  await fsPromises.writeFile(outputPath, content, "utf8");
18405
18629
  files.push(outputPath);
@@ -18420,6 +18644,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
18420
18644
  }
18421
18645
  // Annotate the CommonJS export names for ESM import in node:
18422
18646
  0 && (module.exports = {
18647
+ AstroFormatter,
18423
18648
  CucumberHtmlFormatter,
18424
18649
  CucumberJsonFormatter,
18425
18650
  CucumberMessagesFormatter,
@@ -18443,6 +18668,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
18443
18668
  canonicalizeRun,
18444
18669
  clearVersionCache,
18445
18670
  computeTestMetrics,
18671
+ copyMarkdownAssets,
18446
18672
  createPrCommentSummary,
18447
18673
  createReportGenerator,
18448
18674
  deriveStepResults,
@@ -18475,6 +18701,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
18475
18701
  resolveAttachments,
18476
18702
  resolveTheme,
18477
18703
  resolveTraceUrl,
18704
+ rewriteAssetPaths,
18478
18705
  saveHistory,
18479
18706
  sendNotifications,
18480
18707
  sendSlackNotification,