executable-stories-formatters 0.9.0 → 0.10.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
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import "fs";
3
- import * as path9 from "path";
3
+ import * as path10 from "path";
4
4
  import * as fsPromises from "fs/promises";
5
5
 
6
6
  // src/converters/acl/status.ts
@@ -846,6 +846,9 @@ function buildScenario(tc, featureId) {
846
846
  if (tickets && tickets.length > 0) {
847
847
  scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
848
848
  }
849
+ if (tc.story.covers && tc.story.covers.length > 0) {
850
+ scenario.covers = [...tc.story.covers];
851
+ }
849
852
  return scenario;
850
853
  }
851
854
  function deriveFeatureTitle(group, relSourceFile) {
@@ -969,6 +972,181 @@ var StoryReportJsonFormatter = class {
969
972
  }
970
973
  };
971
974
 
975
+ // src/formatters/scenario-index-json.ts
976
+ var ScenarioIndexJsonFormatter = class {
977
+ options;
978
+ constructor(options = {}) {
979
+ this.options = {
980
+ pretty: options.pretty ?? true,
981
+ filters: options.filters
982
+ };
983
+ }
984
+ toIndex(run) {
985
+ return toScenarioIndex(toStoryReport(run), this.options.filters);
986
+ }
987
+ format(run) {
988
+ const index = this.toIndex(run);
989
+ return this.options.pretty ? JSON.stringify(index, null, 2) : JSON.stringify(index);
990
+ }
991
+ };
992
+ function toScenarioIndex(report, filters = {}) {
993
+ const scenarios = report.features.flatMap(
994
+ (feature) => feature.scenarios.map((scenario) => toScenarioIndexItem(feature, scenario))
995
+ ).filter((scenario) => matchesFilters(scenario, filters));
996
+ return {
997
+ schemaVersion: "1.0",
998
+ runId: report.runId,
999
+ generatedAtMs: report.finishedAtMs,
1000
+ summary: summarize(scenarios),
1001
+ scenarios
1002
+ };
1003
+ }
1004
+ function toScenarioIndexItem(feature, scenario) {
1005
+ return {
1006
+ id: scenario.id,
1007
+ title: scenario.title,
1008
+ status: scenario.status,
1009
+ feature: feature.title,
1010
+ sourceFile: feature.sourceFile,
1011
+ sourceLine: scenario.sourceLine,
1012
+ tags: scenario.tags,
1013
+ tickets: scenario.tickets ?? [],
1014
+ covers: scenario.covers ?? [],
1015
+ durationMs: scenario.durationMs,
1016
+ steps: scenario.steps.map((step) => ({
1017
+ id: step.id,
1018
+ index: step.index,
1019
+ keyword: step.keyword,
1020
+ text: step.text,
1021
+ status: step.status,
1022
+ durationMs: step.durationMs,
1023
+ errorMessage: step.errorMessage,
1024
+ docKinds: step.docEntries.map((entry) => entry.kind)
1025
+ })),
1026
+ docKinds: scenario.docEntries.map((entry) => entry.kind),
1027
+ error: scenario.errorMessage ? { message: scenario.errorMessage, stack: scenario.errorStack } : void 0
1028
+ };
1029
+ }
1030
+ function matchesFilters(scenario, filters) {
1031
+ if (filters.statuses?.length && !filters.statuses.includes(scenario.status)) {
1032
+ return false;
1033
+ }
1034
+ if (filters.tags?.length && !filters.tags.some((tag) => scenario.tags.includes(tag))) {
1035
+ return false;
1036
+ }
1037
+ if (filters.sourceFiles?.length && !filters.sourceFiles.some((sourceFile) => scenario.sourceFile.includes(sourceFile))) {
1038
+ return false;
1039
+ }
1040
+ return true;
1041
+ }
1042
+ function summarize(scenarios) {
1043
+ return {
1044
+ total: scenarios.length,
1045
+ passed: scenarios.filter((scenario) => scenario.status === "passed").length,
1046
+ failed: scenarios.filter((scenario) => scenario.status === "failed").length,
1047
+ skipped: scenarios.filter((scenario) => scenario.status === "skipped").length,
1048
+ pending: scenarios.filter((scenario) => scenario.status === "pending").length,
1049
+ durationMs: scenarios.reduce((total, scenario) => total + scenario.durationMs, 0)
1050
+ };
1051
+ }
1052
+
1053
+ // src/formatters/behavior-manifest-json.ts
1054
+ var BehaviorManifestJsonFormatter = class {
1055
+ pretty;
1056
+ constructor(options = {}) {
1057
+ this.pretty = options.pretty ?? true;
1058
+ }
1059
+ toManifest(run) {
1060
+ return toBehaviorManifest(toStoryReport(run));
1061
+ }
1062
+ format(run) {
1063
+ const manifest = this.toManifest(run);
1064
+ return this.pretty ? JSON.stringify(manifest, null, 2) : JSON.stringify(manifest);
1065
+ }
1066
+ };
1067
+ function toBehaviorManifest(report) {
1068
+ const index = toScenarioIndex(report);
1069
+ const bySource = /* @__PURE__ */ new Map();
1070
+ const byTag = /* @__PURE__ */ new Map();
1071
+ const docKinds = /* @__PURE__ */ new Set();
1072
+ const debuggerIssues = [];
1073
+ for (const scenario of index.scenarios) {
1074
+ const source = bySource.get(scenario.sourceFile) ?? {
1075
+ path: scenario.sourceFile,
1076
+ scenarioCount: 0,
1077
+ failed: 0,
1078
+ tags: []
1079
+ };
1080
+ source.scenarioCount += 1;
1081
+ if (scenario.status === "failed") source.failed += 1;
1082
+ source.tags = [.../* @__PURE__ */ new Set([...source.tags, ...scenario.tags])].sort();
1083
+ bySource.set(scenario.sourceFile, source);
1084
+ for (const tag of scenario.tags) {
1085
+ const tagEntry = byTag.get(tag) ?? { name: tag, scenarioCount: 0 };
1086
+ tagEntry.scenarioCount += 1;
1087
+ byTag.set(tag, tagEntry);
1088
+ }
1089
+ for (const kind of scenario.docKinds) docKinds.add(kind);
1090
+ for (const step of scenario.steps) {
1091
+ for (const kind of step.docKinds) docKinds.add(kind);
1092
+ }
1093
+ if (!scenarioHasDocs(scenario)) {
1094
+ debuggerIssues.push({
1095
+ severity: "warning",
1096
+ code: "missing-docs",
1097
+ scenarioId: scenario.id,
1098
+ title: scenario.title,
1099
+ message: "Scenario has no doc entries."
1100
+ });
1101
+ }
1102
+ if (scenario.tags.length === 0) {
1103
+ debuggerIssues.push({
1104
+ severity: "warning",
1105
+ code: "missing-tags",
1106
+ scenarioId: scenario.id,
1107
+ title: scenario.title,
1108
+ message: "Scenario has no tags."
1109
+ });
1110
+ }
1111
+ if (scenario.covers.length === 0) {
1112
+ debuggerIssues.push({
1113
+ severity: "warning",
1114
+ code: "missing-covers",
1115
+ scenarioId: scenario.id,
1116
+ title: scenario.title,
1117
+ message: "Scenario declares no covers (product-code paths), so code\u2192scenario lookup cannot find it."
1118
+ });
1119
+ }
1120
+ if (scenario.sourceLine === void 0) {
1121
+ debuggerIssues.push({
1122
+ severity: "warning",
1123
+ code: "missing-source-line",
1124
+ scenarioId: scenario.id,
1125
+ title: scenario.title,
1126
+ message: "Scenario has no source line."
1127
+ });
1128
+ }
1129
+ }
1130
+ const scenariosWithDocs = index.scenarios.filter(scenarioHasDocs).length;
1131
+ return {
1132
+ schemaVersion: "1.0",
1133
+ runId: report.runId,
1134
+ generatedAtMs: report.finishedAtMs,
1135
+ summary: index.summary,
1136
+ sourceFiles: [...bySource.values()].sort((a, b) => a.path.localeCompare(b.path)),
1137
+ tags: [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name)),
1138
+ docCoverage: {
1139
+ scenariosWithDocs,
1140
+ scenariosWithoutDocs: index.scenarios.length - scenariosWithDocs,
1141
+ docKinds: [...docKinds].sort()
1142
+ },
1143
+ debugger: debuggerIssues
1144
+ };
1145
+ }
1146
+ function scenarioHasDocs(scenario) {
1147
+ return scenario.docKinds.length > 0 || scenario.steps.some((step) => step.docKinds.length > 0);
1148
+ }
1149
+
972
1150
  // src/formatters/html/renderers/index.ts
973
1151
  import * as fs2 from "fs";
974
1152
  import * as path3 from "path";
@@ -15837,8 +16015,8 @@ function extractDocAttachments(step) {
15837
16015
  }
15838
16016
  return attachments;
15839
16017
  }
15840
- function guessMediaType(path10) {
15841
- const lower = path10.toLowerCase();
16018
+ function guessMediaType(path11) {
16019
+ const lower = path11.toLowerCase();
15842
16020
  if (lower.endsWith(".png")) return "image/png";
15843
16021
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15844
16022
  if (lower.endsWith(".gif")) return "image/gif";
@@ -15979,11 +16157,11 @@ var CucumberHtmlFormatter = class {
15979
16157
  for (const envelope of envelopes) {
15980
16158
  const accepted = htmlStream.write(envelope);
15981
16159
  if (!accepted) {
15982
- await new Promise((resolve7) => htmlStream.once("drain", resolve7));
16160
+ await new Promise((resolve8) => htmlStream.once("drain", resolve8));
15983
16161
  }
15984
16162
  }
15985
- await new Promise((resolve7, reject) => {
15986
- collector.on("finish", resolve7);
16163
+ await new Promise((resolve8, reject) => {
16164
+ collector.on("finish", resolve8);
15987
16165
  collector.on("error", reject);
15988
16166
  htmlStream.end();
15989
16167
  });
@@ -17972,6 +18150,184 @@ ${result.errors.join("\n")}`);
17972
18150
  }
17973
18151
  }
17974
18152
 
18153
+ // src/coverage-index.ts
18154
+ function normalizePath(path11) {
18155
+ return path11.replace(/^\.\//, "");
18156
+ }
18157
+ function scenariosCoveringPaths(index, paths) {
18158
+ const queries = paths.map(normalizePath);
18159
+ return index.scenarios.filter(
18160
+ (scenario) => scenario.covers.some(
18161
+ (glob) => queries.some((path11) => matchesPattern(normalizePath(glob), path11))
18162
+ )
18163
+ );
18164
+ }
18165
+
18166
+ // src/watch.ts
18167
+ import * as fs6 from "fs";
18168
+ import * as path7 from "path";
18169
+
18170
+ // src/converters/synthesize.ts
18171
+ var KEYWORD_MAP = {
18172
+ given: "Given",
18173
+ when: "When",
18174
+ then: "Then",
18175
+ and: "And",
18176
+ but: "But"
18177
+ };
18178
+ function normalizeKeyword(keyword) {
18179
+ return KEYWORD_MAP[keyword.toLowerCase()] ?? keyword;
18180
+ }
18181
+ function normalizeStepKeywords(steps) {
18182
+ return steps.map((step) => ({
18183
+ ...step,
18184
+ keyword: normalizeKeyword(step.keyword)
18185
+ }));
18186
+ }
18187
+ function deriveScenarioName(tc) {
18188
+ if (tc.title) return tc.title;
18189
+ if (tc.titlePath && tc.titlePath.length > 0) {
18190
+ return tc.titlePath[tc.titlePath.length - 1];
18191
+ }
18192
+ return "Untitled";
18193
+ }
18194
+ function synthesizeStories(raw) {
18195
+ return {
18196
+ ...raw,
18197
+ testCases: raw.testCases.map(synthesizeTestCase)
18198
+ };
18199
+ }
18200
+ function synthesizeTestCase(tc) {
18201
+ if (tc.story == null) {
18202
+ const scenario = deriveScenarioName(tc);
18203
+ return {
18204
+ ...tc,
18205
+ story: {
18206
+ scenario,
18207
+ steps: [{ keyword: "Then", text: scenario }]
18208
+ }
18209
+ };
18210
+ }
18211
+ const steps = tc.story.steps;
18212
+ if (!steps || steps.length === 0) {
18213
+ return {
18214
+ ...tc,
18215
+ story: {
18216
+ ...tc.story,
18217
+ steps: [{ keyword: "Then", text: tc.story.scenario }]
18218
+ }
18219
+ };
18220
+ }
18221
+ return {
18222
+ ...tc,
18223
+ story: {
18224
+ ...tc.story,
18225
+ steps: normalizeStepKeywords(steps)
18226
+ }
18227
+ };
18228
+ }
18229
+
18230
+ // src/watch.ts
18231
+ function toRun(data, inputType, synthesize) {
18232
+ if (inputType === "canonical") return data;
18233
+ let raw = data;
18234
+ if (synthesize) raw = synthesizeStories(raw);
18235
+ return canonicalizeRun(raw);
18236
+ }
18237
+ async function regenerateArtifacts(options, deps = {}) {
18238
+ const read = deps.readFile ?? ((filePath) => fs6.readFileSync(filePath, "utf8"));
18239
+ const data = JSON.parse(read(path7.resolve(options.input)));
18240
+ const run = toRun(data, options.inputType ?? "raw", options.synthesize !== false);
18241
+ const generator = new ReportGenerator({
18242
+ formats: options.formats,
18243
+ outputDir: options.outputDir,
18244
+ outputName: options.outputName
18245
+ });
18246
+ const result = await generator.generate(run);
18247
+ return [...result.values()].flat();
18248
+ }
18249
+ function startWatch(options, deps = {}) {
18250
+ const log = deps.log ?? ((message) => console.log(message));
18251
+ const regenerate = deps.regenerate ?? ((input) => regenerateArtifacts({ ...options, input }, deps));
18252
+ const watchFn = deps.watch ?? ((filePath, listener) => fs6.watch(filePath, listener));
18253
+ const debounceMs = options.debounceMs ?? 150;
18254
+ let timer;
18255
+ let running = false;
18256
+ let pending = false;
18257
+ const run = async () => {
18258
+ if (running) {
18259
+ pending = true;
18260
+ return;
18261
+ }
18262
+ running = true;
18263
+ try {
18264
+ const files = await regenerate(options.input);
18265
+ log(`Regenerated ${files.length} artifact file(s) from ${options.input}`);
18266
+ } catch (error) {
18267
+ log(`Watch regeneration failed: ${error.message}`);
18268
+ } finally {
18269
+ running = false;
18270
+ if (pending) {
18271
+ pending = false;
18272
+ trigger();
18273
+ }
18274
+ }
18275
+ };
18276
+ const trigger = () => {
18277
+ if (timer) clearTimeout(timer);
18278
+ timer = setTimeout(() => void run(), debounceMs);
18279
+ };
18280
+ trigger();
18281
+ const watcher = watchFn(path7.resolve(options.input), trigger);
18282
+ return {
18283
+ close: () => {
18284
+ if (timer) clearTimeout(timer);
18285
+ watcher.close();
18286
+ }
18287
+ };
18288
+ }
18289
+
18290
+ // src/behavior-diff.ts
18291
+ function classifyStatusChange(baseline, current) {
18292
+ if (baseline === void 0) return "added";
18293
+ if (current === void 0) return "removed";
18294
+ if (baseline === current) return "unchanged";
18295
+ if (baseline === "passed" && current === "failed") return "regressed";
18296
+ if (baseline === "failed" && current === "passed") return "fixed";
18297
+ return "changed";
18298
+ }
18299
+ function scenarioMap(report) {
18300
+ const map = /* @__PURE__ */ new Map();
18301
+ for (const feature of report.features) {
18302
+ for (const scenario of feature.scenarios) {
18303
+ map.set(scenario.id, { scenario, sourceFile: feature.sourceFile });
18304
+ }
18305
+ }
18306
+ return map;
18307
+ }
18308
+ function diffStoryReports(baseline, current) {
18309
+ const base = scenarioMap(baseline);
18310
+ const curr = scenarioMap(current);
18311
+ const ids = [.../* @__PURE__ */ new Set([...base.keys(), ...curr.keys()])];
18312
+ const scenarios = ids.map((id) => {
18313
+ const b = base.get(id);
18314
+ const c = curr.get(id);
18315
+ const kind = classifyStatusChange(b?.scenario.status, c?.scenario.status);
18316
+ const meta = c ?? b;
18317
+ return {
18318
+ id,
18319
+ title: meta.scenario.title,
18320
+ sourceFile: meta.sourceFile,
18321
+ kind,
18322
+ baselineStatus: b?.scenario.status,
18323
+ currentStatus: c?.scenario.status
18324
+ };
18325
+ });
18326
+ const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
18327
+ for (const s of scenarios) summary[s.kind] += 1;
18328
+ return { schemaVersion: "1.0", summary, scenarios };
18329
+ }
18330
+
17975
18331
  // src/publishers/confluence.ts
17976
18332
  function parseAdf(adf) {
17977
18333
  let parsed;
@@ -18567,27 +18923,27 @@ function pickleStepArgumentToDocs(ps) {
18567
18923
  }
18568
18924
 
18569
18925
  // src/utils/git-info.ts
18570
- import * as fs6 from "fs";
18571
- import * as path7 from "path";
18926
+ import * as fs7 from "fs";
18927
+ import * as path8 from "path";
18572
18928
  function readGitSha(cwd = process.cwd()) {
18573
18929
  const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
18574
18930
  if (envSha) return envSha;
18575
18931
  const gitDir = findGitDir(cwd);
18576
18932
  if (!gitDir) return void 0;
18577
18933
  try {
18578
- const headPath = path7.join(gitDir, "HEAD");
18579
- const head = fs6.readFileSync(headPath, "utf8").trim();
18934
+ const headPath = path8.join(gitDir, "HEAD");
18935
+ const head = fs7.readFileSync(headPath, "utf8").trim();
18580
18936
  if (!head.startsWith("ref:")) {
18581
18937
  return head;
18582
18938
  }
18583
18939
  const refPath = head.replace("ref:", "").trim();
18584
- const refFile = path7.join(gitDir, refPath);
18585
- if (fs6.existsSync(refFile)) {
18586
- return fs6.readFileSync(refFile, "utf8").trim();
18940
+ const refFile = path8.join(gitDir, refPath);
18941
+ if (fs7.existsSync(refFile)) {
18942
+ return fs7.readFileSync(refFile, "utf8").trim();
18587
18943
  }
18588
- const packedRefs = path7.join(gitDir, "packed-refs");
18589
- if (fs6.existsSync(packedRefs)) {
18590
- const content = fs6.readFileSync(packedRefs, "utf8");
18944
+ const packedRefs = path8.join(gitDir, "packed-refs");
18945
+ if (fs7.existsSync(packedRefs)) {
18946
+ const content = fs7.readFileSync(packedRefs, "utf8");
18591
18947
  for (const line of content.split("\n")) {
18592
18948
  if (!line || line.startsWith("#") || line.startsWith("^")) continue;
18593
18949
  const [sha, ref] = line.split(" ");
@@ -18602,19 +18958,19 @@ function readGitSha(cwd = process.cwd()) {
18602
18958
  function findGitDir(start) {
18603
18959
  let current = start;
18604
18960
  while (true) {
18605
- const candidate = path7.join(current, ".git");
18606
- if (fs6.existsSync(candidate)) {
18607
- const stat = fs6.statSync(candidate);
18961
+ const candidate = path8.join(current, ".git");
18962
+ if (fs7.existsSync(candidate)) {
18963
+ const stat = fs7.statSync(candidate);
18608
18964
  if (stat.isFile()) {
18609
- const content = fs6.readFileSync(candidate, "utf8").trim();
18965
+ const content = fs7.readFileSync(candidate, "utf8").trim();
18610
18966
  const match = content.match(/^gitdir: (.+)$/);
18611
18967
  if (match) {
18612
- return path7.resolve(current, match[1]);
18968
+ return path8.resolve(current, match[1]);
18613
18969
  }
18614
18970
  }
18615
18971
  return candidate;
18616
18972
  }
18617
- const parent = path7.dirname(current);
18973
+ const parent = path8.dirname(current);
18618
18974
  if (parent === current) return void 0;
18619
18975
  current = parent;
18620
18976
  }
@@ -18625,8 +18981,8 @@ function readBranchName(cwd = process.cwd()) {
18625
18981
  const gitDir = findGitDir(cwd);
18626
18982
  if (!gitDir) return void 0;
18627
18983
  try {
18628
- const headPath = path7.join(gitDir, "HEAD");
18629
- const head = fs6.readFileSync(headPath, "utf8").trim();
18984
+ const headPath = path8.join(gitDir, "HEAD");
18985
+ const head = fs7.readFileSync(headPath, "utf8").trim();
18630
18986
  if (head.startsWith("ref:")) {
18631
18987
  const refPath = head.replace("ref:", "").trim();
18632
18988
  const match = refPath.match(/^refs\/heads\/(.+)$/);
@@ -18663,8 +19019,8 @@ function nanosecondsToMs(ns) {
18663
19019
  }
18664
19020
 
18665
19021
  // src/utils/metadata.ts
18666
- import * as fs7 from "fs";
18667
- import * as path8 from "path";
19022
+ import * as fs8 from "fs";
19023
+ import * as path9 from "path";
18668
19024
  var versionCache = /* @__PURE__ */ new Map();
18669
19025
  function readPackageVersion(root) {
18670
19026
  if (versionCache.has(root)) {
@@ -18675,18 +19031,18 @@ function readPackageVersion(root) {
18675
19031
  return version;
18676
19032
  }
18677
19033
  function findPackageVersion(startDir) {
18678
- let current = path8.resolve(startDir);
19034
+ let current = path9.resolve(startDir);
18679
19035
  while (true) {
18680
- const pkgPath = path8.join(current, "package.json");
19036
+ const pkgPath = path9.join(current, "package.json");
18681
19037
  try {
18682
- if (fs7.existsSync(pkgPath)) {
18683
- const raw = fs7.readFileSync(pkgPath, "utf8");
19038
+ if (fs8.existsSync(pkgPath)) {
19039
+ const raw = fs8.readFileSync(pkgPath, "utf8");
18684
19040
  const parsed = JSON.parse(raw);
18685
19041
  return parsed.version;
18686
19042
  }
18687
19043
  } catch {
18688
19044
  }
18689
- const parent = path8.dirname(current);
19045
+ const parent = path9.dirname(current);
18690
19046
  if (parent === current) {
18691
19047
  return void 0;
18692
19048
  }
@@ -19556,12 +19912,22 @@ function listScenarios(args, _deps) {
19556
19912
  const { testCases, format } = args;
19557
19913
  if (format === "json") {
19558
19914
  const items = testCases.map((tc) => ({
19915
+ id: tc.id,
19559
19916
  scenario: tc.story.scenario,
19560
19917
  status: tc.status,
19561
19918
  sourceFile: tc.sourceFile,
19562
19919
  sourceLine: tc.sourceLine,
19920
+ suitePath: tc.story.suitePath ?? tc.titlePath.slice(0, -1),
19563
19921
  tags: tc.tags,
19564
- id: tc.id
19922
+ tickets: tc.story.tickets ?? [],
19923
+ covers: tc.story.covers ?? [],
19924
+ durationMs: tc.durationMs,
19925
+ error: tc.errorMessage ? {
19926
+ message: tc.errorMessage,
19927
+ stack: tc.errorStack
19928
+ } : void 0,
19929
+ steps: tc.story.steps.map((step, index) => toScenarioStep(step, index, tc)),
19930
+ docKinds: collectDocKinds(tc)
19565
19931
  }));
19566
19932
  return JSON.stringify(items, null, 2);
19567
19933
  }
@@ -19634,6 +20000,34 @@ function listScenarios(args, _deps) {
19634
20000
  ];
19635
20001
  return lines.join("\n");
19636
20002
  }
20003
+ function toScenarioStep(step, index, testCase) {
20004
+ const result = testCase.stepResults.find(
20005
+ (candidate) => candidate.index === index || candidate.stepId === step.id
20006
+ );
20007
+ return {
20008
+ id: step.id,
20009
+ index,
20010
+ keyword: step.keyword,
20011
+ text: step.text,
20012
+ status: result?.status ?? testCase.status,
20013
+ durationMs: result?.durationMs ?? step.durationMs ?? 0,
20014
+ errorMessage: result?.errorMessage,
20015
+ mode: step.mode,
20016
+ docKinds: (step.docs ?? []).map((doc) => doc.kind)
20017
+ };
20018
+ }
20019
+ function collectDocKinds(testCase) {
20020
+ const kinds = /* @__PURE__ */ new Set();
20021
+ for (const doc of testCase.story.docs ?? []) {
20022
+ kinds.add(doc.kind);
20023
+ }
20024
+ for (const step of testCase.story.steps) {
20025
+ for (const doc of step.docs ?? []) {
20026
+ kinds.add(doc.kind);
20027
+ }
20028
+ }
20029
+ return [...kinds].sort();
20030
+ }
19637
20031
 
19638
20032
  // src/review/conventions.ts
19639
20033
  var CHANGE_TAG_PREFIX = "change:";
@@ -19681,18 +20075,18 @@ function deriveChangeType(tags) {
19681
20075
  }
19682
20076
  return "unknown";
19683
20077
  }
19684
- function extensionOf(path10) {
19685
- const base = path10.split("/").pop() ?? path10;
20078
+ function extensionOf(path11) {
20079
+ const base = path11.split("/").pop() ?? path11;
19686
20080
  const dot = base.lastIndexOf(".");
19687
20081
  return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
19688
20082
  }
19689
- function isTestFile(path10) {
19690
- return TEST_INFIX.test(path10);
20083
+ function isTestFile(path11) {
20084
+ return TEST_INFIX.test(path11);
19691
20085
  }
19692
- function isReviewableSource(path10) {
19693
- if (isTestFile(path10)) return false;
19694
- if (path10.endsWith(".d.ts")) return false;
19695
- return CODE_EXTENSIONS.has(extensionOf(path10));
20086
+ function isReviewableSource(path11) {
20087
+ if (isTestFile(path11)) return false;
20088
+ if (path11.endsWith(".d.ts")) return false;
20089
+ return CODE_EXTENSIONS.has(extensionOf(path11));
19696
20090
  }
19697
20091
  function testBaseKey(testFile) {
19698
20092
  return testFile.replace(TEST_INFIX, "");
@@ -19796,7 +20190,7 @@ function toClaim(testCase, changedSourcePaths) {
19796
20190
  const { strength, reasons } = gradeEvidence(testCase, audience);
19797
20191
  const key = testBaseKey(testCase.sourceFile);
19798
20192
  const coversFiles = changedSourcePaths.filter(
19799
- (path10) => sourceBaseKey(path10) === key
20193
+ (path11) => sourceBaseKey(path11) === key
19800
20194
  );
19801
20195
  return {
19802
20196
  id: testCase.id,
@@ -20329,6 +20723,7 @@ applyTheme(getEffectiveTheme());` : "";
20329
20723
  // src/index.ts
20330
20724
  var FORMAT_EXTENSIONS = {
20331
20725
  astro: ".md",
20726
+ "behavior-manifest-json": ".behavior-manifest.json",
20332
20727
  markdown: ".md",
20333
20728
  html: ".html",
20334
20729
  "cucumber-html": ".cucumber.html",
@@ -20336,6 +20731,7 @@ var FORMAT_EXTENSIONS = {
20336
20731
  "cucumber-json": ".cucumber.json",
20337
20732
  "cucumber-messages": ".ndjson",
20338
20733
  confluence: ".adf.json",
20734
+ "scenario-index-json": ".scenarios-index.json",
20339
20735
  "story-report-json": ".story-report.json"
20340
20736
  };
20341
20737
  var TEST_EXTENSIONS = [
@@ -20363,11 +20759,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
20363
20759
  const ext = FORMAT_EXTENSIONS[format];
20364
20760
  const effectiveName = outputName + (outputNameSuffix ?? "");
20365
20761
  if (mode === "aggregated") {
20366
- return toPosix(path9.join(baseOutputDir, `${effectiveName}${ext}`));
20762
+ return toPosix(path10.join(baseOutputDir, `${effectiveName}${ext}`));
20367
20763
  }
20368
20764
  const normalizedSource = toPosix(sourceFile);
20369
- const dirOfSource = path9.posix.dirname(normalizedSource);
20370
- let baseName = path9.posix.basename(normalizedSource);
20765
+ const dirOfSource = path10.posix.dirname(normalizedSource);
20766
+ let baseName = path10.posix.basename(normalizedSource);
20371
20767
  for (const testExt of TEST_EXTENSIONS) {
20372
20768
  if (baseName.endsWith(testExt)) {
20373
20769
  baseName = baseName.slice(0, -testExt.length);
@@ -20376,9 +20772,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
20376
20772
  }
20377
20773
  const fileName = `${baseName}.${effectiveName}${ext}`;
20378
20774
  if (colocatedStyle === "adjacent") {
20379
- return toPosix(path9.posix.join(dirOfSource, fileName));
20775
+ return toPosix(path10.posix.join(dirOfSource, fileName));
20380
20776
  }
20381
- return toPosix(path9.posix.join(baseOutputDir, dirOfSource, fileName));
20777
+ return toPosix(path10.posix.join(baseOutputDir, dirOfSource, fileName));
20382
20778
  }
20383
20779
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
20384
20780
  const groups = /* @__PURE__ */ new Map();
@@ -20464,6 +20860,12 @@ var ReportGenerator = class {
20464
20860
  storyReportJson: {
20465
20861
  pretty: options.storyReportJson?.pretty ?? true
20466
20862
  },
20863
+ scenarioIndexJson: {
20864
+ pretty: options.scenarioIndexJson?.pretty ?? true
20865
+ },
20866
+ behaviorManifestJson: {
20867
+ pretty: options.behaviorManifestJson?.pretty ?? true
20868
+ },
20467
20869
  cucumberMessages: {
20468
20870
  uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
20469
20871
  includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
@@ -20579,8 +20981,8 @@ var ReportGenerator = class {
20579
20981
  if (astroPaths) {
20580
20982
  for (const mdPath of astroPaths) {
20581
20983
  const content = await fsPromises.readFile(mdPath, "utf8");
20582
- const mdDir = path9.dirname(mdPath);
20583
- const assetsDir = path9.resolve(this.options.astro.assetsDir);
20984
+ const mdDir = path10.dirname(mdPath);
20985
+ const assetsDir = path10.resolve(this.options.astro.assetsDir);
20584
20986
  const result = copyMarkdownAssets({
20585
20987
  markdown: content,
20586
20988
  markdownDir: mdDir,
@@ -20611,9 +21013,9 @@ var ReportGenerator = class {
20611
21013
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
20612
21014
  const ext = FORMAT_EXTENSIONS[format];
20613
21015
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
20614
- const outputPath = toPosix(path9.join(this.options.outputDir, `${effectiveName}${ext}`));
21016
+ const outputPath = toPosix(path10.join(this.options.outputDir, `${effectiveName}${ext}`));
20615
21017
  const content = await this.formatContent(run, format);
20616
- const dir = path9.dirname(outputPath);
21018
+ const dir = path10.dirname(outputPath);
20617
21019
  await fsPromises.mkdir(dir, { recursive: true });
20618
21020
  await this.deps.writeFile(outputPath, content);
20619
21021
  return [outputPath];
@@ -20625,7 +21027,7 @@ var ReportGenerator = class {
20625
21027
  testCases
20626
21028
  };
20627
21029
  const content = await this.formatContent(groupRun, format);
20628
- const dir = path9.dirname(outputPath);
21030
+ const dir = path10.dirname(outputPath);
20629
21031
  await fsPromises.mkdir(dir, { recursive: true });
20630
21032
  await this.deps.writeFile(outputPath, content);
20631
21033
  writtenPaths.push(outputPath);
@@ -20738,6 +21140,18 @@ var ReportGenerator = class {
20738
21140
  });
20739
21141
  return formatter.format(run);
20740
21142
  }
21143
+ case "scenario-index-json": {
21144
+ const formatter = new ScenarioIndexJsonFormatter({
21145
+ pretty: this.options.scenarioIndexJson.pretty
21146
+ });
21147
+ return formatter.format(run);
21148
+ }
21149
+ case "behavior-manifest-json": {
21150
+ const formatter = new BehaviorManifestJsonFormatter({
21151
+ pretty: this.options.behaviorManifestJson.pretty
21152
+ });
21153
+ return formatter.format(run);
21154
+ }
20741
21155
  default:
20742
21156
  throw new Error(`Unknown format: ${format}`);
20743
21157
  }
@@ -20754,7 +21168,7 @@ async function generateRunComparison(args) {
20754
21168
  await fsPromises.mkdir(outputDir, { recursive: true });
20755
21169
  for (const format of args.formats) {
20756
21170
  const ext = format === "html" ? ".html" : ".md";
20757
- const outputPath = toPosix(path9.join(outputDir, `${outputName}${ext}`));
21171
+ const outputPath = toPosix(path10.join(outputDir, `${outputName}${ext}`));
20758
21172
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
20759
21173
  await fsPromises.writeFile(outputPath, content, "utf8");
20760
21174
  files.push(outputPath);
@@ -20775,6 +21189,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
20775
21189
  }
20776
21190
  export {
20777
21191
  AstroFormatter,
21192
+ BehaviorManifestJsonFormatter,
20778
21193
  ConfluenceFormatter,
20779
21194
  CucumberHtmlFormatter,
20780
21195
  CucumberJsonFormatter,
@@ -20795,6 +21210,7 @@ export {
20795
21210
  STORY_META_KEY,
20796
21211
  STORY_REPORT_SCHEMA_MAJOR,
20797
21212
  STORY_REPORT_SCHEMA_VERSION,
21213
+ ScenarioIndexJsonFormatter,
20798
21214
  StoryReportJsonFormatter,
20799
21215
  adaptJestRun,
20800
21216
  adaptPlaywrightRun,
@@ -20805,6 +21221,7 @@ export {
20805
21221
  calculateFlakiness,
20806
21222
  calculateStability,
20807
21223
  canonicalizeRun,
21224
+ classifyStatusChange,
20808
21225
  clearVersionCache,
20809
21226
  computeTestMetrics,
20810
21227
  copyMarkdownAssets,
@@ -20816,6 +21233,7 @@ export {
20816
21233
  detectCI4 as detectCI,
20817
21234
  detectPerformanceTrend,
20818
21235
  diffRuns,
21236
+ diffStoryReports,
20819
21237
  findGitDir,
20820
21238
  formatDuration3 as formatDuration,
20821
21239
  generateRunComparison,
@@ -20843,21 +21261,26 @@ export {
20843
21261
  readBranchName,
20844
21262
  readGitSha,
20845
21263
  readPackageVersion,
21264
+ regenerateArtifacts,
20846
21265
  resolveAttachment,
20847
21266
  resolveAttachments,
20848
21267
  resolveTheme,
20849
21268
  resolveTraceUrl,
20850
21269
  rewriteAssetPaths,
20851
21270
  saveHistory,
21271
+ scenariosCoveringPaths,
20852
21272
  sendNotifications,
20853
21273
  sendSlackNotification,
20854
21274
  sendTeamsNotification,
20855
21275
  sendWebhookNotification,
20856
21276
  signBody,
20857
21277
  slugify,
21278
+ startWatch,
20858
21279
  stripAnsi,
21280
+ toBehaviorManifest,
20859
21281
  toCIInfo,
20860
21282
  toRawCIInfo,
21283
+ toScenarioIndex,
20861
21284
  toStoryReport,
20862
21285
  tryGetActiveOtelContext,
20863
21286
  updateHistory,