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/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +418 -96
- package/dist/cli.js.map +1 -1
- package/dist/{index-it3Pkmqv.d.cts → index-CbWFyoTx.d.cts} +2 -0
- package/dist/{index-it3Pkmqv.d.ts → index-CbWFyoTx.d.ts} +2 -0
- package/dist/index.cjs +485 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +201 -4
- package/dist/index.d.ts +201 -4
- package/dist/index.js +475 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/behavior-manifest-v1.json +65 -0
- package/schemas/examples/dotnet.json +84 -20
- package/schemas/examples/go.json +77 -20
- package/schemas/examples/junit5.json +84 -20
- package/schemas/examples/pytest.json +92 -20
- package/schemas/examples/rust.json +84 -20
- package/schemas/scenario-index-v1.json +88 -0
- package/schemas/story-report-v1.json +5 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import "fs";
|
|
3
|
-
import * as
|
|
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(
|
|
15841
|
-
const lower =
|
|
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((
|
|
16160
|
+
await new Promise((resolve8) => htmlStream.once("drain", resolve8));
|
|
15983
16161
|
}
|
|
15984
16162
|
}
|
|
15985
|
-
await new Promise((
|
|
15986
|
-
collector.on("finish",
|
|
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
|
|
18571
|
-
import * as
|
|
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 =
|
|
18579
|
-
const head =
|
|
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 =
|
|
18585
|
-
if (
|
|
18586
|
-
return
|
|
18940
|
+
const refFile = path8.join(gitDir, refPath);
|
|
18941
|
+
if (fs7.existsSync(refFile)) {
|
|
18942
|
+
return fs7.readFileSync(refFile, "utf8").trim();
|
|
18587
18943
|
}
|
|
18588
|
-
const packedRefs =
|
|
18589
|
-
if (
|
|
18590
|
-
const content =
|
|
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 =
|
|
18606
|
-
if (
|
|
18607
|
-
const stat =
|
|
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 =
|
|
18965
|
+
const content = fs7.readFileSync(candidate, "utf8").trim();
|
|
18610
18966
|
const match = content.match(/^gitdir: (.+)$/);
|
|
18611
18967
|
if (match) {
|
|
18612
|
-
return
|
|
18968
|
+
return path8.resolve(current, match[1]);
|
|
18613
18969
|
}
|
|
18614
18970
|
}
|
|
18615
18971
|
return candidate;
|
|
18616
18972
|
}
|
|
18617
|
-
const parent =
|
|
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 =
|
|
18629
|
-
const head =
|
|
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
|
|
18667
|
-
import * as
|
|
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 =
|
|
19034
|
+
let current = path9.resolve(startDir);
|
|
18679
19035
|
while (true) {
|
|
18680
|
-
const pkgPath =
|
|
19036
|
+
const pkgPath = path9.join(current, "package.json");
|
|
18681
19037
|
try {
|
|
18682
|
-
if (
|
|
18683
|
-
const raw =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
19685
|
-
const base =
|
|
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(
|
|
19690
|
-
return TEST_INFIX.test(
|
|
20083
|
+
function isTestFile(path11) {
|
|
20084
|
+
return TEST_INFIX.test(path11);
|
|
19691
20085
|
}
|
|
19692
|
-
function isReviewableSource(
|
|
19693
|
-
if (isTestFile(
|
|
19694
|
-
if (
|
|
19695
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
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
|
-
(
|
|
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(
|
|
20762
|
+
return toPosix(path10.join(baseOutputDir, `${effectiveName}${ext}`));
|
|
20367
20763
|
}
|
|
20368
20764
|
const normalizedSource = toPosix(sourceFile);
|
|
20369
|
-
const dirOfSource =
|
|
20370
|
-
let baseName =
|
|
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(
|
|
20775
|
+
return toPosix(path10.posix.join(dirOfSource, fileName));
|
|
20380
20776
|
}
|
|
20381
|
-
return toPosix(
|
|
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 =
|
|
20583
|
-
const 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(
|
|
21016
|
+
const outputPath = toPosix(path10.join(this.options.outputDir, `${effectiveName}${ext}`));
|
|
20615
21017
|
const content = await this.formatContent(run, format);
|
|
20616
|
-
const dir =
|
|
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 =
|
|
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(
|
|
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,
|