executable-stories-formatters 0.7.6 → 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/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "util";
5
- import * as fs5 from "fs";
6
- import * as path5 from "path";
5
+ import * as fs7 from "fs";
6
+ import * as path7 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 path6 = err.instancePath || "/";
495
+ const path8 = 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 `${path6}: ${message} \u2014 '${extra}'`;
499
+ return `${path8}: ${message} \u2014 '${extra}'`;
500
500
  }
501
501
  if (err.keyword === "enum") {
502
502
  const allowed = err.params.allowedValues;
503
- return `${path6}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
503
+ return `${path8}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
504
504
  }
505
- return `${path6}: ${message}`;
505
+ return `${path8}: ${message}`;
506
506
  });
507
507
  return { valid: false, errors };
508
508
  }
@@ -966,7 +966,7 @@ ${result.errors.join("\n")}`);
966
966
 
967
967
  // src/index.ts
968
968
  import "fs";
969
- import * as path4 from "path";
969
+ import * as path5 from "path";
970
970
  import * as fsPromises from "fs/promises";
971
971
 
972
972
  // src/converters/acl/lines.ts
@@ -15671,8 +15671,8 @@ function extractDocAttachments(step) {
15671
15671
  }
15672
15672
  return attachments;
15673
15673
  }
15674
- function guessMediaType(path6) {
15675
- const lower = path6.toLowerCase();
15674
+ function guessMediaType(path8) {
15675
+ const lower = path8.toLowerCase();
15676
15676
  if (lower.endsWith(".png")) return "image/png";
15677
15677
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15678
15678
  if (lower.endsWith(".gif")) return "image/gif";
@@ -15813,11 +15813,11 @@ var CucumberHtmlFormatter = class {
15813
15813
  for (const envelope of envelopes) {
15814
15814
  const accepted = htmlStream.write(envelope);
15815
15815
  if (!accepted) {
15816
- await new Promise((resolve4) => htmlStream.once("drain", resolve4));
15816
+ await new Promise((resolve7) => htmlStream.once("drain", resolve7));
15817
15817
  }
15818
15818
  }
15819
- await new Promise((resolve4, reject) => {
15820
- collector.on("finish", resolve4);
15819
+ await new Promise((resolve7, reject) => {
15820
+ collector.on("finish", resolve7);
15821
15821
  collector.on("error", reject);
15822
15822
  htmlStream.end();
15823
15823
  });
@@ -16849,6 +16849,177 @@ function replaceAssetRef(html, original, replacement) {
16849
16849
  return html;
16850
16850
  }
16851
16851
 
16852
+ // src/formatters/astro.ts
16853
+ var AstroFormatter = class _AstroFormatter {
16854
+ markdownFormatter;
16855
+ title;
16856
+ constructor(options = {}) {
16857
+ this.title = options.markdown?.title ?? "User Stories";
16858
+ this.markdownFormatter = new MarkdownFormatter({
16859
+ ...options.markdown,
16860
+ title: this.title,
16861
+ stepStyle: "gherkin",
16862
+ includeFrontMatter: false,
16863
+ includeSummaryTable: false,
16864
+ includeMetadata: false
16865
+ });
16866
+ }
16867
+ format(run) {
16868
+ const markdown = this.markdownFormatter.format(run);
16869
+ const body = markdown.replace(/^# .+\n\n?/, "");
16870
+ const frontmatter = this.buildFrontmatter(run);
16871
+ return `${frontmatter}
16872
+ ${body}`;
16873
+ }
16874
+ buildFrontmatter(run) {
16875
+ const badge = _AstroFormatter.computeBadge(run.testCases);
16876
+ const count = run.testCases.length;
16877
+ const description = `${count} scenario${count !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
16878
+ const lines = [
16879
+ "---",
16880
+ `title: ${this.title}`,
16881
+ `description: ${description}`,
16882
+ "sidebar:",
16883
+ " badge:",
16884
+ ` text: ${badge.text}`,
16885
+ ` variant: ${badge.variant}`,
16886
+ "---"
16887
+ ];
16888
+ return lines.join("\n");
16889
+ }
16890
+ static computeBadge(testCases) {
16891
+ const statuses = new Set(testCases.map((tc) => tc.status));
16892
+ if (statuses.has("failed")) return { text: "Failed", variant: "danger" };
16893
+ if (statuses.has("pending")) return { text: "Pending", variant: "caution" };
16894
+ if (statuses.has("skipped") && !statuses.has("passed")) return { text: "Skipped", variant: "caution" };
16895
+ return { text: "Passed", variant: "success" };
16896
+ }
16897
+ };
16898
+
16899
+ // src/formatters/astro-assets.ts
16900
+ import * as fs4 from "fs";
16901
+ import * as path4 from "path";
16902
+ var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
16903
+ function isLocalPath(src) {
16904
+ const trimmed = src.trim();
16905
+ if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
16906
+ return false;
16907
+ }
16908
+ return !path4.posix.isAbsolute(trimmed) && !path4.win32.isAbsolute(trimmed);
16909
+ }
16910
+ function stripCodeContent(markdown) {
16911
+ let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
16912
+ result = result.replace(/(`+)(?:(?!\1).)+\1/g, "");
16913
+ result = result.replace(/<pre\b[^>]*>[\s\S]*?<\/pre>/gi, "");
16914
+ result = result.replace(/<code\b[^>]*>[\s\S]*?<\/code>/gi, "");
16915
+ return result;
16916
+ }
16917
+ function scanMarkdownAssets(markdown) {
16918
+ const found = /* @__PURE__ */ new Set();
16919
+ const stripped = stripCodeContent(markdown);
16920
+ const mdImageRe = /!\[[^\]]*\]\(([^)"'\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
16921
+ let match;
16922
+ while ((match = mdImageRe.exec(stripped)) !== null) {
16923
+ const src = match[1].trim();
16924
+ if (isLocalPath(src)) {
16925
+ found.add(src);
16926
+ }
16927
+ }
16928
+ const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
16929
+ while ((match = htmlSrcRe.exec(stripped)) !== null) {
16930
+ const src = match[1].trim();
16931
+ if (isLocalPath(src)) {
16932
+ found.add(src);
16933
+ }
16934
+ }
16935
+ return Array.from(found);
16936
+ }
16937
+ function splitByCode(markdown) {
16938
+ 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;
16939
+ const segments = [];
16940
+ let lastIndex = 0;
16941
+ for (const match of markdown.matchAll(codeRe)) {
16942
+ if (match.index > lastIndex) {
16943
+ segments.push(markdown.slice(lastIndex, match.index));
16944
+ }
16945
+ segments.push(match[0]);
16946
+ lastIndex = match.index + match[0].length;
16947
+ }
16948
+ if (lastIndex < markdown.length) {
16949
+ segments.push(markdown.slice(lastIndex));
16950
+ }
16951
+ return segments;
16952
+ }
16953
+ function isCode(segment) {
16954
+ const trimmed = segment.trimStart();
16955
+ return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
16956
+ }
16957
+ function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
16958
+ let result = prose;
16959
+ result = result.replace(
16960
+ /(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g,
16961
+ (full, pre, src, post) => {
16962
+ const trimmed = src.trim();
16963
+ if (!isLocalPath(trimmed)) return full;
16964
+ if (pathMap) {
16965
+ const mapped = pathMap.get(trimmed);
16966
+ if (mapped === void 0) return full;
16967
+ return `${pre}${assetsBaseUrl}/${mapped}${post}`;
16968
+ }
16969
+ return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
16970
+ }
16971
+ );
16972
+ result = result.replace(
16973
+ /(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
16974
+ (full, pre, src, post) => {
16975
+ const trimmed = src.trim();
16976
+ if (!isLocalPath(trimmed)) return full;
16977
+ if (pathMap) {
16978
+ const mapped = pathMap.get(trimmed);
16979
+ if (mapped === void 0) return full;
16980
+ return `${pre}${assetsBaseUrl}/${mapped}${post}`;
16981
+ }
16982
+ return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
16983
+ }
16984
+ );
16985
+ return result;
16986
+ }
16987
+ function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
16988
+ return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
16989
+ }
16990
+ function copyMarkdownAssets(options) {
16991
+ const {
16992
+ markdown,
16993
+ markdownDir,
16994
+ assetsDir,
16995
+ assetsBaseUrl,
16996
+ allowMissing = false
16997
+ } = options;
16998
+ const refs = scanMarkdownAssets(markdown);
16999
+ const pathMap = /* @__PURE__ */ new Map();
17000
+ const missing = [];
17001
+ for (const ref of refs) {
17002
+ const absPath = path4.resolve(markdownDir, ref);
17003
+ if (!fs4.existsSync(absPath)) {
17004
+ if (!allowMissing) {
17005
+ throw new Error(`Asset not found: ${absPath}`);
17006
+ }
17007
+ missing.push(ref);
17008
+ continue;
17009
+ }
17010
+ const relativeCopied = copyAsset(absPath, assetsDir);
17011
+ const fileName = relativeCopied.replace(/^assets\//, "");
17012
+ pathMap.set(ref, fileName);
17013
+ }
17014
+ const rewritten = rewriteAssetPaths(markdown, assetsBaseUrl, pathMap);
17015
+ return {
17016
+ markdown: rewritten,
17017
+ copiedCount: pathMap.size,
17018
+ missingCount: missing.length,
17019
+ missing
17020
+ };
17021
+ }
17022
+
16852
17023
  // src/converters/ndjson-parser.ts
16853
17024
  function parseNdjson(ndjson) {
16854
17025
  const lines = ndjson.trim().split("\n").filter(Boolean);
@@ -17843,6 +18014,7 @@ function listScenarios(args, _deps) {
17843
18014
 
17844
18015
  // src/index.ts
17845
18016
  var FORMAT_EXTENSIONS = {
18017
+ astro: ".md",
17846
18018
  markdown: ".md",
17847
18019
  html: ".html",
17848
18020
  "cucumber-html": ".cucumber.html",
@@ -17875,11 +18047,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
17875
18047
  const ext = FORMAT_EXTENSIONS[format];
17876
18048
  const effectiveName = outputName + (outputNameSuffix ?? "");
17877
18049
  if (mode === "aggregated") {
17878
- return toPosix(path4.join(baseOutputDir, `${effectiveName}${ext}`));
18050
+ return toPosix(path5.join(baseOutputDir, `${effectiveName}${ext}`));
17879
18051
  }
17880
18052
  const normalizedSource = toPosix(sourceFile);
17881
- const dirOfSource = path4.posix.dirname(normalizedSource);
17882
- let baseName = path4.posix.basename(normalizedSource);
18053
+ const dirOfSource = path5.posix.dirname(normalizedSource);
18054
+ let baseName = path5.posix.basename(normalizedSource);
17883
18055
  for (const testExt of TEST_EXTENSIONS) {
17884
18056
  if (baseName.endsWith(testExt)) {
17885
18057
  baseName = baseName.slice(0, -testExt.length);
@@ -17888,9 +18060,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
17888
18060
  }
17889
18061
  const fileName = `${baseName}.${effectiveName}${ext}`;
17890
18062
  if (colocatedStyle === "adjacent") {
17891
- return toPosix(path4.posix.join(dirOfSource, fileName));
18063
+ return toPosix(path5.posix.join(dirOfSource, fileName));
17892
18064
  }
17893
- return toPosix(path4.posix.join(baseOutputDir, dirOfSource, fileName));
18065
+ return toPosix(path5.posix.join(baseOutputDir, dirOfSource, fileName));
17894
18066
  }
17895
18067
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
17896
18068
  const groups = /* @__PURE__ */ new Map();
@@ -18016,6 +18188,24 @@ var ReportGenerator = class {
18016
18188
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
18017
18189
  customRenderers: options.markdown?.customRenderers
18018
18190
  },
18191
+ astro: {
18192
+ assetsDir: options.astro?.assetsDir ?? "public/stories/assets",
18193
+ assetsBaseUrl: options.astro?.assetsBaseUrl ?? "/stories/assets",
18194
+ markdown: {
18195
+ title: options.astro?.markdown?.title ?? "User Stories",
18196
+ includeStatusIcons: options.astro?.markdown?.includeStatusIcons ?? true,
18197
+ includeErrors: options.astro?.markdown?.includeErrors ?? true,
18198
+ scenarioHeadingLevel: options.astro?.markdown?.scenarioHeadingLevel ?? 3,
18199
+ groupBy: options.astro?.markdown?.groupBy ?? "file",
18200
+ sortScenarios: options.astro?.markdown?.sortScenarios ?? "source",
18201
+ suiteSeparator: options.astro?.markdown?.suiteSeparator ?? " - ",
18202
+ includeSourceLinks: options.astro?.markdown?.includeSourceLinks ?? true,
18203
+ permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
18204
+ ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
18205
+ traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
18206
+ customRenderers: options.astro?.markdown?.customRenderers
18207
+ }
18208
+ },
18019
18209
  assetMode: options.assetMode ?? "none",
18020
18210
  allowMissingAssets: options.allowMissingAssets ?? false
18021
18211
  };
@@ -18053,6 +18243,24 @@ var ReportGenerator = class {
18053
18243
  });
18054
18244
  }
18055
18245
  }
18246
+ const astroPaths = results.get("astro");
18247
+ if (astroPaths) {
18248
+ for (const mdPath of astroPaths) {
18249
+ const content = await fsPromises.readFile(mdPath, "utf8");
18250
+ const mdDir = path5.dirname(mdPath);
18251
+ const assetsDir = path5.resolve(this.options.astro.assetsDir);
18252
+ const result = copyMarkdownAssets({
18253
+ markdown: content,
18254
+ markdownDir: mdDir,
18255
+ assetsDir,
18256
+ assetsBaseUrl: this.options.astro.assetsBaseUrl,
18257
+ allowMissing: this.options.allowMissingAssets
18258
+ });
18259
+ if (result.copiedCount > 0 || result.missingCount > 0) {
18260
+ await this.deps.writeFile(mdPath, result.markdown);
18261
+ }
18262
+ }
18263
+ }
18056
18264
  }
18057
18265
  return results;
18058
18266
  }
@@ -18071,9 +18279,9 @@ var ReportGenerator = class {
18071
18279
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
18072
18280
  const ext = FORMAT_EXTENSIONS[format];
18073
18281
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
18074
- const outputPath = toPosix(path4.join(this.options.outputDir, `${effectiveName}${ext}`));
18282
+ const outputPath = toPosix(path5.join(this.options.outputDir, `${effectiveName}${ext}`));
18075
18283
  const content = await this.formatContent(run, format);
18076
- const dir = path4.dirname(outputPath);
18284
+ const dir = path5.dirname(outputPath);
18077
18285
  await fsPromises.mkdir(dir, { recursive: true });
18078
18286
  await this.deps.writeFile(outputPath, content);
18079
18287
  return [outputPath];
@@ -18085,7 +18293,7 @@ var ReportGenerator = class {
18085
18293
  testCases
18086
18294
  };
18087
18295
  const content = await this.formatContent(groupRun, format);
18088
- const dir = path4.dirname(outputPath);
18296
+ const dir = path5.dirname(outputPath);
18089
18297
  await fsPromises.mkdir(dir, { recursive: true });
18090
18298
  await this.deps.writeFile(outputPath, content);
18091
18299
  writtenPaths.push(outputPath);
@@ -18148,6 +18356,13 @@ var ReportGenerator = class {
18148
18356
  });
18149
18357
  return formatter.formatToString(run);
18150
18358
  }
18359
+ case "astro": {
18360
+ const formatter = new AstroFormatter({
18361
+ assetsBaseUrl: this.options.astro.assetsBaseUrl,
18362
+ markdown: this.options.astro.markdown
18363
+ });
18364
+ return formatter.format(run);
18365
+ }
18151
18366
  case "markdown": {
18152
18367
  const formatter = new MarkdownFormatter({
18153
18368
  title: this.options.markdown.title,
@@ -18182,7 +18397,7 @@ async function generateRunComparison(args) {
18182
18397
  await fsPromises.mkdir(outputDir, { recursive: true });
18183
18398
  for (const format of args.formats) {
18184
18399
  const ext = format === "html" ? ".html" : ".md";
18185
- const outputPath = toPosix(path4.join(outputDir, `${outputName}${ext}`));
18400
+ const outputPath = toPosix(path5.join(outputDir, `${outputName}${ext}`));
18186
18401
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
18187
18402
  await fsPromises.writeFile(outputPath, content, "utf8");
18188
18403
  files.push(outputPath);
@@ -18190,6 +18405,45 @@ async function generateRunComparison(args) {
18190
18405
  return { files, diff };
18191
18406
  }
18192
18407
 
18408
+ // src/init-astro.ts
18409
+ import * as fs6 from "fs";
18410
+ import * as path6 from "path";
18411
+ import { fileURLToPath } from "url";
18412
+ var __dirname = path6.dirname(fileURLToPath(import.meta.url));
18413
+ function initAstro(options = {}) {
18414
+ const targetDir = options.targetDir ?? "./story-docs";
18415
+ const force = options.force ?? false;
18416
+ if (fs6.existsSync(targetDir)) {
18417
+ const entries = fs6.readdirSync(targetDir);
18418
+ if (entries.length > 0 && !force) {
18419
+ throw new Error(
18420
+ `Directory "${targetDir}" already exists and is not empty. Use --force to overwrite.`
18421
+ );
18422
+ }
18423
+ }
18424
+ const templateDir = path6.resolve(__dirname, "..", "templates", "astro-starlight");
18425
+ if (!fs6.existsSync(templateDir)) {
18426
+ throw new Error(
18427
+ `Template directory not found at ${templateDir}. Ensure the package is installed correctly.`
18428
+ );
18429
+ }
18430
+ copyDirRecursive(templateDir, targetDir);
18431
+ return { targetDir };
18432
+ }
18433
+ function copyDirRecursive(src, dest) {
18434
+ fs6.mkdirSync(dest, { recursive: true });
18435
+ const entries = fs6.readdirSync(src, { withFileTypes: true });
18436
+ for (const entry of entries) {
18437
+ const srcPath = path6.join(src, entry.name);
18438
+ const destPath = path6.join(dest, entry.name);
18439
+ if (entry.isDirectory()) {
18440
+ copyDirRecursive(srcPath, destPath);
18441
+ } else {
18442
+ fs6.copyFileSync(srcPath, destPath);
18443
+ }
18444
+ }
18445
+ }
18446
+
18193
18447
  // src/cli.ts
18194
18448
  var EXIT_SUCCESS = 0;
18195
18449
  var EXIT_SCHEMA_VALIDATION = 1;
@@ -18206,15 +18460,18 @@ USAGE
18206
18460
  executable-stories list <file> [options]
18207
18461
  executable-stories validate <file>
18208
18462
  executable-stories validate --stdin
18463
+ executable-stories init-astro [directory]
18209
18464
 
18210
18465
  SUBCOMMANDS
18211
18466
  format Read raw test results and generate reports
18212
18467
  compare Compare two runs and generate a diff report
18213
18468
  list List scenarios from a test run (text table or JSON)
18214
18469
  validate Validate a JSON file against the schema (no output generated)
18470
+ init-astro Scaffold an Astro Starlight docs site for story output
18215
18471
 
18216
18472
  OPTIONS
18217
18473
  --format <formats> Comma-separated formats (default: html)
18474
+ astro Starlight-compatible Markdown (for Astro docs sites)
18218
18475
  html Custom HTML report (accessible, dark mode, mermaid)
18219
18476
  cucumber-html Official Cucumber HTML report
18220
18477
  markdown Markdown documentation
@@ -18261,6 +18518,10 @@ COMPARE
18261
18518
  compare supports --format html,markdown
18262
18519
  compare uses the same --input-type for both baseline and current files
18263
18520
 
18521
+ INIT-ASTRO
18522
+ executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
18523
+ --force Overwrite existing directory
18524
+
18264
18525
  NOTIFICATIONS
18265
18526
  --slack-webhook <url> Slack incoming webhook URL (fallback: SLACK_WEBHOOK_URL env var)
18266
18527
  --teams-webhook <url> Teams incoming webhook URL (fallback: TEAMS_WEBHOOK_URL env var)
@@ -18295,10 +18556,31 @@ function parseCliArgs(argv) {
18295
18556
  process.exit(EXIT_SUCCESS);
18296
18557
  }
18297
18558
  const subcommand = args[0];
18298
- if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "list" && subcommand !== "validate") {
18299
- console.error(`Unknown subcommand: "${subcommand}". Use "format", "compare", "list", or "validate".`);
18559
+ if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro") {
18560
+ console.error(`Unknown subcommand: "${subcommand}". Use "format", "compare", "list", "validate", or "init-astro".`);
18300
18561
  process.exit(EXIT_USAGE);
18301
18562
  }
18563
+ if (subcommand === "init-astro") {
18564
+ const initArgs = args.slice(1);
18565
+ const targetDir = initArgs.find((a) => !a.startsWith("--")) ?? "./story-docs";
18566
+ const force = initArgs.includes("--force");
18567
+ try {
18568
+ const result = initAstro({ targetDir, force });
18569
+ console.log(`Scaffolded Astro Starlight project at ${result.targetDir}`);
18570
+ console.log("");
18571
+ console.log("Next steps:");
18572
+ console.log(` cd ${result.targetDir}`);
18573
+ console.log(" pnpm install # or npm install");
18574
+ console.log(" pnpm dev # start the dev server");
18575
+ console.log("");
18576
+ console.log("Generate story docs with:");
18577
+ console.log(` executable-stories format run.json --format astro --output-dir ${result.targetDir}/src/content/docs/stories --asset-mode copy`);
18578
+ process.exit(EXIT_SUCCESS);
18579
+ } catch (err) {
18580
+ console.error(`Error: ${err.message}`);
18581
+ process.exit(EXIT_USAGE);
18582
+ }
18583
+ }
18302
18584
  const { values, positionals } = parseArgs({
18303
18585
  args: args.slice(1),
18304
18586
  options: {
@@ -18382,12 +18664,12 @@ function parseCliArgs(argv) {
18382
18664
  console.error(`Error: --input-type must be "raw", "canonical", or "ndjson", got "${inputType}".`);
18383
18665
  process.exit(EXIT_USAGE);
18384
18666
  }
18385
- const validFormats = /* @__PURE__ */ new Set(["html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
18667
+ const validFormats = /* @__PURE__ */ new Set(["astro", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
18386
18668
  const formatStr = values.format;
18387
18669
  const formats = formatStr.split(",").map((f) => f.trim());
18388
18670
  for (const f of formats) {
18389
18671
  if (!validFormats.has(f)) {
18390
- console.error(`Error: Unknown format "${f}". Valid: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html.`);
18672
+ console.error(`Error: Unknown format "${f}". Valid: astro, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html.`);
18391
18673
  process.exit(EXIT_USAGE);
18392
18674
  }
18393
18675
  }
@@ -18511,27 +18793,27 @@ async function readInput(args) {
18511
18793
  if (args.stdin) {
18512
18794
  return readStdin();
18513
18795
  }
18514
- const filePath = path5.resolve(args.inputFile);
18515
- if (!fs5.existsSync(filePath)) {
18796
+ const filePath = path7.resolve(args.inputFile);
18797
+ if (!fs7.existsSync(filePath)) {
18516
18798
  console.error(`Error: File not found: ${filePath}`);
18517
18799
  process.exit(EXIT_USAGE);
18518
18800
  }
18519
- return fs5.readFileSync(filePath, "utf8");
18801
+ return fs7.readFileSync(filePath, "utf8");
18520
18802
  }
18521
18803
  function readFileInput(filePath) {
18522
- const resolved = path5.resolve(filePath);
18523
- if (!fs5.existsSync(resolved)) {
18804
+ const resolved = path7.resolve(filePath);
18805
+ if (!fs7.existsSync(resolved)) {
18524
18806
  console.error(`Error: File not found: ${resolved}`);
18525
18807
  process.exit(EXIT_USAGE);
18526
18808
  }
18527
- return fs5.readFileSync(resolved, "utf8");
18809
+ return fs7.readFileSync(resolved, "utf8");
18528
18810
  }
18529
18811
  function readStdin() {
18530
- return new Promise((resolve4, reject) => {
18812
+ return new Promise((resolve7, reject) => {
18531
18813
  const chunks = [];
18532
18814
  process.stdin.setEncoding("utf8");
18533
18815
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18534
- process.stdin.on("end", () => resolve4(chunks.join("")));
18816
+ process.stdin.on("end", () => resolve7(chunks.join("")));
18535
18817
  process.stdin.on("error", reject);
18536
18818
  });
18537
18819
  }
@@ -18657,14 +18939,14 @@ function tryNormalizeRunFromText(text, args) {
18657
18939
  }
18658
18940
  }
18659
18941
  function listBaselineCandidates(currentFile, args) {
18660
- const baselineDir = path5.resolve(args.baselineDir ?? path5.dirname(currentFile));
18661
- const currentResolved = path5.resolve(currentFile);
18662
- if (!fs5.existsSync(baselineDir)) {
18942
+ const baselineDir = path7.resolve(args.baselineDir ?? path7.dirname(currentFile));
18943
+ const currentResolved = path7.resolve(currentFile);
18944
+ if (!fs7.existsSync(baselineDir)) {
18663
18945
  console.error(`Error: baseline directory not found: ${baselineDir}`);
18664
18946
  process.exit(EXIT_USAGE);
18665
18947
  }
18666
- const entries = fs5.readdirSync(baselineDir, { withFileTypes: true });
18667
- return entries.filter((entry) => entry.isFile()).map((entry) => path5.join(baselineDir, entry.name)).filter((candidate) => path5.resolve(candidate) !== currentResolved).filter(
18948
+ const entries = fs7.readdirSync(baselineDir, { withFileTypes: true });
18949
+ return entries.filter((entry) => entry.isFile()).map((entry) => path7.join(baselineDir, entry.name)).filter((candidate) => path7.resolve(candidate) !== currentResolved).filter(
18668
18950
  (candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
18669
18951
  );
18670
18952
  }
@@ -18672,14 +18954,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
18672
18954
  const candidates = listBaselineCandidates(currentFile, args);
18673
18955
  const comparable = [];
18674
18956
  for (const candidate of candidates) {
18675
- const run = tryNormalizeRunFromText(fs5.readFileSync(candidate, "utf8"), args);
18957
+ const run = tryNormalizeRunFromText(fs7.readFileSync(candidate, "utf8"), args);
18676
18958
  if (run) {
18677
18959
  comparable.push({ file: candidate, run });
18678
18960
  }
18679
18961
  }
18680
18962
  if (comparable.length === 0) {
18681
18963
  console.error(
18682
- `Error: no compatible baseline files found in ${path5.resolve(args.baselineDir ?? path5.dirname(currentFile))}.`
18964
+ `Error: no compatible baseline files found in ${path7.resolve(args.baselineDir ?? path7.dirname(currentFile))}.`
18683
18965
  );
18684
18966
  process.exit(EXIT_USAGE);
18685
18967
  }
@@ -18760,9 +19042,9 @@ async function main() {
18760
19042
  process.exit(EXIT_SCHEMA_VALIDATION);
18761
19043
  }
18762
19044
  if (args.emitCanonical) {
18763
- const outPath = path5.resolve(args.emitCanonical);
18764
- fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
18765
- fs5.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
19045
+ const outPath = path7.resolve(args.emitCanonical);
19046
+ fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
19047
+ fs7.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
18766
19048
  }
18767
19049
  try {
18768
19050
  const result = await generateReports(run, args);
@@ -18818,9 +19100,9 @@ ${msg}`);
18818
19100
  }
18819
19101
  const run = data;
18820
19102
  if (args.emitCanonical) {
18821
- const outPath = path5.resolve(args.emitCanonical);
18822
- fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
18823
- fs5.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
19103
+ const outPath = path7.resolve(args.emitCanonical);
19104
+ fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
19105
+ fs7.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
18824
19106
  }
18825
19107
  try {
18826
19108
  const result = await generateReports(run, args);
@@ -18875,9 +19157,9 @@ ${msg}`);
18875
19157
  process.exit(EXIT_CANONICAL_VALIDATION);
18876
19158
  }
18877
19159
  if (args.emitCanonical) {
18878
- const outPath = path5.resolve(args.emitCanonical);
18879
- fs5.mkdirSync(path5.dirname(outPath), { recursive: true });
18880
- fs5.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
19160
+ const outPath = path7.resolve(args.emitCanonical);
19161
+ fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
19162
+ fs7.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
18881
19163
  }
18882
19164
  try {
18883
19165
  const result = await generateReports(canonical, args, droppedMissingStory);
@@ -18934,13 +19216,13 @@ async function dispatchNotifications(run, args) {
18934
19216
  }
18935
19217
  function runHistoryPipeline(run, args) {
18936
19218
  if (!args.historyFile) return;
18937
- const historyPath = path5.resolve(args.historyFile);
19219
+ const historyPath = path7.resolve(args.historyFile);
18938
19220
  const store = loadHistory(
18939
19221
  { filePath: historyPath },
18940
19222
  {
18941
19223
  readFile: (p) => {
18942
19224
  try {
18943
- return fs5.readFileSync(p, "utf8");
19225
+ return fs7.readFileSync(p, "utf8");
18944
19226
  } catch {
18945
19227
  return void 0;
18946
19228
  }
@@ -18953,11 +19235,11 @@ function runHistoryPipeline(run, args) {
18953
19235
  run,
18954
19236
  maxRuns: args.maxHistoryRuns
18955
19237
  });
18956
- const dir = path5.dirname(historyPath);
18957
- fs5.mkdirSync(dir, { recursive: true });
19238
+ const dir = path7.dirname(historyPath);
19239
+ fs7.mkdirSync(dir, { recursive: true });
18958
19240
  saveHistory(
18959
19241
  { filePath: historyPath, store: updated },
18960
- { writeFile: (p, content) => fs5.writeFileSync(p, content, "utf8") }
19242
+ { writeFile: (p, content) => fs7.writeFileSync(p, content, "utf8") }
18961
19243
  );
18962
19244
  let metricsCount = 0;
18963
19245
  for (const testId of Object.keys(updated.tests)) {
@@ -19055,9 +19337,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
19055
19337
  function printCompareResult(result, args, startMs) {
19056
19338
  const durationMs = Date.now() - startMs;
19057
19339
  if (result.prSummary && args.prSummaryFile) {
19058
- const outputPath = path5.resolve(args.prSummaryFile);
19059
- fs5.mkdirSync(path5.dirname(outputPath), { recursive: true });
19060
- fs5.writeFileSync(outputPath, result.prSummary, "utf8");
19340
+ const outputPath = path7.resolve(args.prSummaryFile);
19341
+ fs7.mkdirSync(path7.dirname(outputPath), { recursive: true });
19342
+ fs7.writeFileSync(outputPath, result.prSummary, "utf8");
19061
19343
  }
19062
19344
  if (args.jsonSummary) {
19063
19345
  console.log(