executable-stories-formatters 0.7.6 → 0.7.8
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 +443 -71
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +293 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -3
- package/dist/index.d.ts +90 -3
- package/dist/index.js +289 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
6
|
-
import * as
|
|
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
|
|
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 `${
|
|
499
|
+
return `${path8}: ${message} \u2014 '${extra}'`;
|
|
500
500
|
}
|
|
501
501
|
if (err.keyword === "enum") {
|
|
502
502
|
const allowed = err.params.allowedValues;
|
|
503
|
-
return `${
|
|
503
|
+
return `${path8}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
|
|
504
504
|
}
|
|
505
|
-
return `${
|
|
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
|
|
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(
|
|
15675
|
-
const lower =
|
|
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((
|
|
15816
|
+
await new Promise((resolve8) => htmlStream.once("drain", resolve8));
|
|
15817
15817
|
}
|
|
15818
15818
|
}
|
|
15819
|
-
await new Promise((
|
|
15820
|
-
collector.on("finish",
|
|
15819
|
+
await new Promise((resolve8, reject) => {
|
|
15820
|
+
collector.on("finish", resolve8);
|
|
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);
|
|
@@ -17802,6 +17973,37 @@ function listScenarios(args, _deps) {
|
|
|
17802
17973
|
}));
|
|
17803
17974
|
return JSON.stringify(items, null, 2);
|
|
17804
17975
|
}
|
|
17976
|
+
if (format === "csv") {
|
|
17977
|
+
const header = "id,scenario,status,sourceFile,sourceLine,tags";
|
|
17978
|
+
const rows = testCases.map((tc) => {
|
|
17979
|
+
const fields = [
|
|
17980
|
+
tc.id,
|
|
17981
|
+
tc.story.scenario,
|
|
17982
|
+
tc.status,
|
|
17983
|
+
tc.sourceFile,
|
|
17984
|
+
String(tc.sourceLine),
|
|
17985
|
+
tc.tags.join(" ")
|
|
17986
|
+
];
|
|
17987
|
+
return fields.map((f) => {
|
|
17988
|
+
if (f.includes(",") || f.includes('"') || f.includes("\n")) {
|
|
17989
|
+
return `"${f.replace(/"/g, '""')}"`;
|
|
17990
|
+
}
|
|
17991
|
+
return f;
|
|
17992
|
+
}).join(",");
|
|
17993
|
+
});
|
|
17994
|
+
return [header, ...rows].join("\n");
|
|
17995
|
+
}
|
|
17996
|
+
if (format === "markdown-table") {
|
|
17997
|
+
const header = "| Status | Scenario | Location | Tags |";
|
|
17998
|
+
const divider = "|--------|----------|----------|------|";
|
|
17999
|
+
const rows = testCases.map((tc) => {
|
|
18000
|
+
const icon = STATUS_ICONS[tc.status] ?? "?";
|
|
18001
|
+
const location = `${tc.sourceFile}:${tc.sourceLine}`;
|
|
18002
|
+
const tags = tc.tags.map((t) => `@${t}`).join(" ");
|
|
18003
|
+
return `| ${icon} | ${tc.story.scenario} | ${location} | ${tags} |`;
|
|
18004
|
+
});
|
|
18005
|
+
return [header, divider, ...rows].join("\n");
|
|
18006
|
+
}
|
|
17805
18007
|
if (testCases.length === 0) {
|
|
17806
18008
|
return "No scenarios found.";
|
|
17807
18009
|
}
|
|
@@ -17843,6 +18045,7 @@ function listScenarios(args, _deps) {
|
|
|
17843
18045
|
|
|
17844
18046
|
// src/index.ts
|
|
17845
18047
|
var FORMAT_EXTENSIONS = {
|
|
18048
|
+
astro: ".md",
|
|
17846
18049
|
markdown: ".md",
|
|
17847
18050
|
html: ".html",
|
|
17848
18051
|
"cucumber-html": ".cucumber.html",
|
|
@@ -17875,11 +18078,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
17875
18078
|
const ext = FORMAT_EXTENSIONS[format];
|
|
17876
18079
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
17877
18080
|
if (mode === "aggregated") {
|
|
17878
|
-
return toPosix(
|
|
18081
|
+
return toPosix(path5.join(baseOutputDir, `${effectiveName}${ext}`));
|
|
17879
18082
|
}
|
|
17880
18083
|
const normalizedSource = toPosix(sourceFile);
|
|
17881
|
-
const dirOfSource =
|
|
17882
|
-
let baseName =
|
|
18084
|
+
const dirOfSource = path5.posix.dirname(normalizedSource);
|
|
18085
|
+
let baseName = path5.posix.basename(normalizedSource);
|
|
17883
18086
|
for (const testExt of TEST_EXTENSIONS) {
|
|
17884
18087
|
if (baseName.endsWith(testExt)) {
|
|
17885
18088
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -17888,9 +18091,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
17888
18091
|
}
|
|
17889
18092
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
17890
18093
|
if (colocatedStyle === "adjacent") {
|
|
17891
|
-
return toPosix(
|
|
18094
|
+
return toPosix(path5.posix.join(dirOfSource, fileName));
|
|
17892
18095
|
}
|
|
17893
|
-
return toPosix(
|
|
18096
|
+
return toPosix(path5.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
17894
18097
|
}
|
|
17895
18098
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
17896
18099
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -18016,6 +18219,24 @@ var ReportGenerator = class {
|
|
|
18016
18219
|
includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
|
|
18017
18220
|
customRenderers: options.markdown?.customRenderers
|
|
18018
18221
|
},
|
|
18222
|
+
astro: {
|
|
18223
|
+
assetsDir: options.astro?.assetsDir ?? "public/stories/assets",
|
|
18224
|
+
assetsBaseUrl: options.astro?.assetsBaseUrl ?? "/stories/assets",
|
|
18225
|
+
markdown: {
|
|
18226
|
+
title: options.astro?.markdown?.title ?? "User Stories",
|
|
18227
|
+
includeStatusIcons: options.astro?.markdown?.includeStatusIcons ?? true,
|
|
18228
|
+
includeErrors: options.astro?.markdown?.includeErrors ?? true,
|
|
18229
|
+
scenarioHeadingLevel: options.astro?.markdown?.scenarioHeadingLevel ?? 3,
|
|
18230
|
+
groupBy: options.astro?.markdown?.groupBy ?? "file",
|
|
18231
|
+
sortScenarios: options.astro?.markdown?.sortScenarios ?? "source",
|
|
18232
|
+
suiteSeparator: options.astro?.markdown?.suiteSeparator ?? " - ",
|
|
18233
|
+
includeSourceLinks: options.astro?.markdown?.includeSourceLinks ?? true,
|
|
18234
|
+
permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
|
|
18235
|
+
ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
|
|
18236
|
+
traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
|
|
18237
|
+
customRenderers: options.astro?.markdown?.customRenderers
|
|
18238
|
+
}
|
|
18239
|
+
},
|
|
18019
18240
|
assetMode: options.assetMode ?? "none",
|
|
18020
18241
|
allowMissingAssets: options.allowMissingAssets ?? false
|
|
18021
18242
|
};
|
|
@@ -18053,6 +18274,24 @@ var ReportGenerator = class {
|
|
|
18053
18274
|
});
|
|
18054
18275
|
}
|
|
18055
18276
|
}
|
|
18277
|
+
const astroPaths = results.get("astro");
|
|
18278
|
+
if (astroPaths) {
|
|
18279
|
+
for (const mdPath of astroPaths) {
|
|
18280
|
+
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
18281
|
+
const mdDir = path5.dirname(mdPath);
|
|
18282
|
+
const assetsDir = path5.resolve(this.options.astro.assetsDir);
|
|
18283
|
+
const result = copyMarkdownAssets({
|
|
18284
|
+
markdown: content,
|
|
18285
|
+
markdownDir: mdDir,
|
|
18286
|
+
assetsDir,
|
|
18287
|
+
assetsBaseUrl: this.options.astro.assetsBaseUrl,
|
|
18288
|
+
allowMissing: this.options.allowMissingAssets
|
|
18289
|
+
});
|
|
18290
|
+
if (result.copiedCount > 0 || result.missingCount > 0) {
|
|
18291
|
+
await this.deps.writeFile(mdPath, result.markdown);
|
|
18292
|
+
}
|
|
18293
|
+
}
|
|
18294
|
+
}
|
|
18056
18295
|
}
|
|
18057
18296
|
return results;
|
|
18058
18297
|
}
|
|
@@ -18071,9 +18310,9 @@ var ReportGenerator = class {
|
|
|
18071
18310
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
18072
18311
|
const ext = FORMAT_EXTENSIONS[format];
|
|
18073
18312
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
18074
|
-
const outputPath = toPosix(
|
|
18313
|
+
const outputPath = toPosix(path5.join(this.options.outputDir, `${effectiveName}${ext}`));
|
|
18075
18314
|
const content = await this.formatContent(run, format);
|
|
18076
|
-
const dir =
|
|
18315
|
+
const dir = path5.dirname(outputPath);
|
|
18077
18316
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
18078
18317
|
await this.deps.writeFile(outputPath, content);
|
|
18079
18318
|
return [outputPath];
|
|
@@ -18085,7 +18324,7 @@ var ReportGenerator = class {
|
|
|
18085
18324
|
testCases
|
|
18086
18325
|
};
|
|
18087
18326
|
const content = await this.formatContent(groupRun, format);
|
|
18088
|
-
const dir =
|
|
18327
|
+
const dir = path5.dirname(outputPath);
|
|
18089
18328
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
18090
18329
|
await this.deps.writeFile(outputPath, content);
|
|
18091
18330
|
writtenPaths.push(outputPath);
|
|
@@ -18148,6 +18387,13 @@ var ReportGenerator = class {
|
|
|
18148
18387
|
});
|
|
18149
18388
|
return formatter.formatToString(run);
|
|
18150
18389
|
}
|
|
18390
|
+
case "astro": {
|
|
18391
|
+
const formatter = new AstroFormatter({
|
|
18392
|
+
assetsBaseUrl: this.options.astro.assetsBaseUrl,
|
|
18393
|
+
markdown: this.options.astro.markdown
|
|
18394
|
+
});
|
|
18395
|
+
return formatter.format(run);
|
|
18396
|
+
}
|
|
18151
18397
|
case "markdown": {
|
|
18152
18398
|
const formatter = new MarkdownFormatter({
|
|
18153
18399
|
title: this.options.markdown.title,
|
|
@@ -18182,7 +18428,7 @@ async function generateRunComparison(args) {
|
|
|
18182
18428
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
18183
18429
|
for (const format of args.formats) {
|
|
18184
18430
|
const ext = format === "html" ? ".html" : ".md";
|
|
18185
|
-
const outputPath = toPosix(
|
|
18431
|
+
const outputPath = toPosix(path5.join(outputDir, `${outputName}${ext}`));
|
|
18186
18432
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
18187
18433
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
18188
18434
|
files.push(outputPath);
|
|
@@ -18190,6 +18436,61 @@ async function generateRunComparison(args) {
|
|
|
18190
18436
|
return { files, diff };
|
|
18191
18437
|
}
|
|
18192
18438
|
|
|
18439
|
+
// src/init-astro.ts
|
|
18440
|
+
import * as fs6 from "fs";
|
|
18441
|
+
import * as path6 from "path";
|
|
18442
|
+
import { fileURLToPath } from "url";
|
|
18443
|
+
var __dirname = path6.dirname(fileURLToPath(import.meta.url));
|
|
18444
|
+
function initAstro(options = {}) {
|
|
18445
|
+
const targetDir = options.targetDir ?? "./story-docs";
|
|
18446
|
+
const force = options.force ?? false;
|
|
18447
|
+
if (fs6.existsSync(targetDir)) {
|
|
18448
|
+
const entries = fs6.readdirSync(targetDir);
|
|
18449
|
+
if (entries.length > 0 && !force) {
|
|
18450
|
+
throw new Error(
|
|
18451
|
+
`Directory "${targetDir}" already exists and is not empty. Use --force to overwrite.`
|
|
18452
|
+
);
|
|
18453
|
+
}
|
|
18454
|
+
}
|
|
18455
|
+
const templateDir = path6.resolve(__dirname, "..", "templates", "astro-starlight");
|
|
18456
|
+
if (!fs6.existsSync(templateDir)) {
|
|
18457
|
+
throw new Error(
|
|
18458
|
+
`Template directory not found at ${templateDir}. Ensure the package is installed correctly.`
|
|
18459
|
+
);
|
|
18460
|
+
}
|
|
18461
|
+
copyDirRecursive(templateDir, targetDir);
|
|
18462
|
+
return { targetDir };
|
|
18463
|
+
}
|
|
18464
|
+
function copyDirRecursive(src, dest) {
|
|
18465
|
+
fs6.mkdirSync(dest, { recursive: true });
|
|
18466
|
+
const entries = fs6.readdirSync(src, { withFileTypes: true });
|
|
18467
|
+
for (const entry of entries) {
|
|
18468
|
+
const srcPath = path6.join(src, entry.name);
|
|
18469
|
+
const destPath = path6.join(dest, entry.name);
|
|
18470
|
+
if (entry.isDirectory()) {
|
|
18471
|
+
copyDirRecursive(srcPath, destPath);
|
|
18472
|
+
} else {
|
|
18473
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
18474
|
+
}
|
|
18475
|
+
}
|
|
18476
|
+
}
|
|
18477
|
+
|
|
18478
|
+
// src/config.ts
|
|
18479
|
+
import { existsSync as existsSync6 } from "fs";
|
|
18480
|
+
import { resolve as resolve6 } from "path";
|
|
18481
|
+
async function loadConfig(configPath) {
|
|
18482
|
+
const resolved = configPath ? resolve6(configPath) : resolve6(process.cwd(), "executable-stories.config.js");
|
|
18483
|
+
if (!existsSync6(resolved)) return {};
|
|
18484
|
+
const mod = await import(resolved);
|
|
18485
|
+
const config = mod.default;
|
|
18486
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
18487
|
+
throw new Error(
|
|
18488
|
+
`Config file at ${resolved} must export a default object. Got: ${typeof config}`
|
|
18489
|
+
);
|
|
18490
|
+
}
|
|
18491
|
+
return config;
|
|
18492
|
+
}
|
|
18493
|
+
|
|
18193
18494
|
// src/cli.ts
|
|
18194
18495
|
var EXIT_SUCCESS = 0;
|
|
18195
18496
|
var EXIT_SCHEMA_VALIDATION = 1;
|
|
@@ -18206,21 +18507,25 @@ USAGE
|
|
|
18206
18507
|
executable-stories list <file> [options]
|
|
18207
18508
|
executable-stories validate <file>
|
|
18208
18509
|
executable-stories validate --stdin
|
|
18510
|
+
executable-stories init-astro [directory]
|
|
18209
18511
|
|
|
18210
18512
|
SUBCOMMANDS
|
|
18211
18513
|
format Read raw test results and generate reports
|
|
18212
18514
|
compare Compare two runs and generate a diff report
|
|
18213
18515
|
list List scenarios from a test run (text table or JSON)
|
|
18214
18516
|
validate Validate a JSON file against the schema (no output generated)
|
|
18517
|
+
init-astro Scaffold an Astro Starlight docs site for story output
|
|
18215
18518
|
|
|
18216
18519
|
OPTIONS
|
|
18217
|
-
--format <formats> Comma-separated formats (default: html)
|
|
18520
|
+
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, or custom names from config (default: html)
|
|
18521
|
+
astro Starlight-compatible Markdown (for Astro docs sites)
|
|
18218
18522
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
18219
18523
|
cucumber-html Official Cucumber HTML report
|
|
18220
18524
|
markdown Markdown documentation
|
|
18221
18525
|
junit JUnit XML
|
|
18222
18526
|
cucumber-json Cucumber JSON
|
|
18223
18527
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
18528
|
+
--config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
|
|
18224
18529
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
18225
18530
|
--output-dir <dir> Output directory (default: reports)
|
|
18226
18531
|
--output-name <name> Base filename (default: index)
|
|
@@ -18244,7 +18549,8 @@ OPTIONS
|
|
|
18244
18549
|
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
18245
18550
|
--allow-missing-assets Warn on missing assets instead of failing
|
|
18246
18551
|
--stdin Read JSON from stdin instead of file
|
|
18247
|
-
--
|
|
18552
|
+
--list-format <format> list output format: text (default), json, csv, markdown-table
|
|
18553
|
+
--json-summary Deprecated alias for --list-format json
|
|
18248
18554
|
--baseline <path|auto> Compare baseline file, or auto-pick a prior run for compare
|
|
18249
18555
|
--baseline-dir <dir> Directory to scan when --baseline auto is used
|
|
18250
18556
|
--pr-summary Print a PR-friendly markdown summary after compare
|
|
@@ -18253,7 +18559,8 @@ OPTIONS
|
|
|
18253
18559
|
--help Show this help message
|
|
18254
18560
|
|
|
18255
18561
|
LIST
|
|
18256
|
-
list prints one scenario per line (text by default
|
|
18562
|
+
list prints one scenario per line (--list-format text by default)
|
|
18563
|
+
list --list-format json outputs machine-parsable JSON (--json-summary is a deprecated alias)
|
|
18257
18564
|
list supports --include-tags, --exclude-tags for filtering
|
|
18258
18565
|
list supports --input-type and --stdin
|
|
18259
18566
|
|
|
@@ -18261,6 +18568,10 @@ COMPARE
|
|
|
18261
18568
|
compare supports --format html,markdown
|
|
18262
18569
|
compare uses the same --input-type for both baseline and current files
|
|
18263
18570
|
|
|
18571
|
+
INIT-ASTRO
|
|
18572
|
+
executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
|
|
18573
|
+
--force Overwrite existing directory
|
|
18574
|
+
|
|
18264
18575
|
NOTIFICATIONS
|
|
18265
18576
|
--slack-webhook <url> Slack incoming webhook URL (fallback: SLACK_WEBHOOK_URL env var)
|
|
18266
18577
|
--teams-webhook <url> Teams incoming webhook URL (fallback: TEAMS_WEBHOOK_URL env var)
|
|
@@ -18288,17 +18599,38 @@ EXIT CODES
|
|
|
18288
18599
|
3 Formatter/generation failure
|
|
18289
18600
|
4 Bad arguments / usage error
|
|
18290
18601
|
`.trim();
|
|
18291
|
-
function parseCliArgs(argv) {
|
|
18602
|
+
async function parseCliArgs(argv) {
|
|
18292
18603
|
const args = argv.slice(2);
|
|
18293
18604
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
18294
18605
|
console.log(HELP_TEXT);
|
|
18295
18606
|
process.exit(EXIT_SUCCESS);
|
|
18296
18607
|
}
|
|
18297
18608
|
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 "
|
|
18609
|
+
if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro") {
|
|
18610
|
+
console.error(`Unknown subcommand: "${subcommand}". Use "format", "compare", "list", "validate", or "init-astro".`);
|
|
18300
18611
|
process.exit(EXIT_USAGE);
|
|
18301
18612
|
}
|
|
18613
|
+
if (subcommand === "init-astro") {
|
|
18614
|
+
const initArgs = args.slice(1);
|
|
18615
|
+
const targetDir = initArgs.find((a) => !a.startsWith("--")) ?? "./story-docs";
|
|
18616
|
+
const force = initArgs.includes("--force");
|
|
18617
|
+
try {
|
|
18618
|
+
const result = initAstro({ targetDir, force });
|
|
18619
|
+
console.log(`Scaffolded Astro Starlight project at ${result.targetDir}`);
|
|
18620
|
+
console.log("");
|
|
18621
|
+
console.log("Next steps:");
|
|
18622
|
+
console.log(` cd ${result.targetDir}`);
|
|
18623
|
+
console.log(" pnpm install # or npm install");
|
|
18624
|
+
console.log(" pnpm dev # start the dev server");
|
|
18625
|
+
console.log("");
|
|
18626
|
+
console.log("Generate story docs with:");
|
|
18627
|
+
console.log(` executable-stories format run.json --format astro --output-dir ${result.targetDir}/src/content/docs/stories --asset-mode copy`);
|
|
18628
|
+
process.exit(EXIT_SUCCESS);
|
|
18629
|
+
} catch (err) {
|
|
18630
|
+
console.error(`Error: ${err.message}`);
|
|
18631
|
+
process.exit(EXIT_USAGE);
|
|
18632
|
+
}
|
|
18633
|
+
}
|
|
18302
18634
|
const { values, positionals } = parseArgs({
|
|
18303
18635
|
args: args.slice(1),
|
|
18304
18636
|
options: {
|
|
@@ -18327,6 +18659,7 @@ function parseCliArgs(argv) {
|
|
|
18327
18659
|
"html-theme-picker": { type: "boolean", default: false },
|
|
18328
18660
|
stdin: { type: "boolean", default: false },
|
|
18329
18661
|
"json-summary": { type: "boolean", default: false },
|
|
18662
|
+
"list-format": { type: "string", default: "text" },
|
|
18330
18663
|
"emit-canonical": { type: "string" },
|
|
18331
18664
|
"slack-webhook": { type: "string" },
|
|
18332
18665
|
"teams-webhook": { type: "string" },
|
|
@@ -18345,6 +18678,7 @@ function parseCliArgs(argv) {
|
|
|
18345
18678
|
"allow-missing-assets": { type: "boolean", default: false },
|
|
18346
18679
|
"pr-summary": { type: "boolean", default: false },
|
|
18347
18680
|
"pr-summary-file": { type: "string" },
|
|
18681
|
+
"config": { type: "string" },
|
|
18348
18682
|
help: { type: "boolean", default: false }
|
|
18349
18683
|
},
|
|
18350
18684
|
allowPositionals: true,
|
|
@@ -18382,15 +18716,20 @@ function parseCliArgs(argv) {
|
|
|
18382
18716
|
console.error(`Error: --input-type must be "raw", "canonical", or "ndjson", got "${inputType}".`);
|
|
18383
18717
|
process.exit(EXIT_USAGE);
|
|
18384
18718
|
}
|
|
18385
|
-
const
|
|
18719
|
+
const pluginConfig = await loadConfig(values["config"]);
|
|
18720
|
+
const customFormatterNames = new Set(Object.keys(pluginConfig.formatters ?? {}));
|
|
18721
|
+
const builtInFormats = /* @__PURE__ */ new Set(["astro", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
|
|
18386
18722
|
const formatStr = values.format;
|
|
18387
|
-
const
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
}
|
|
18723
|
+
const allRequestedFormats = formatStr.split(",").map((f) => f.trim());
|
|
18724
|
+
const builtInRequested = allRequestedFormats.filter((f) => builtInFormats.has(f));
|
|
18725
|
+
const customRequested = allRequestedFormats.filter((f) => customFormatterNames.has(f));
|
|
18726
|
+
const unknownFormats = allRequestedFormats.filter((f) => !builtInFormats.has(f) && !customFormatterNames.has(f));
|
|
18727
|
+
if (unknownFormats.length > 0) {
|
|
18728
|
+
const knownCustom = customFormatterNames.size > 0 ? `, ${[...customFormatterNames].join(", ")}` : "";
|
|
18729
|
+
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html${knownCustom}.`);
|
|
18730
|
+
process.exit(EXIT_USAGE);
|
|
18393
18731
|
}
|
|
18732
|
+
const formats = builtInRequested;
|
|
18394
18733
|
const htmlTheme = values["html-theme"];
|
|
18395
18734
|
const validThemes = /* @__PURE__ */ new Set(["default", "corporate", "terminal", "minimal", "dashboard", "playful"]);
|
|
18396
18735
|
if (!validThemes.has(htmlTheme)) {
|
|
@@ -18458,7 +18797,7 @@ function parseCliArgs(argv) {
|
|
|
18458
18797
|
console.error(`Error: --asset-mode must be "none" or "copy", got "${assetModeRaw}".`);
|
|
18459
18798
|
process.exit(EXIT_USAGE);
|
|
18460
18799
|
}
|
|
18461
|
-
|
|
18800
|
+
const cliArgs = {
|
|
18462
18801
|
subcommand,
|
|
18463
18802
|
inputFile,
|
|
18464
18803
|
baselineFile,
|
|
@@ -18487,6 +18826,7 @@ function parseCliArgs(argv) {
|
|
|
18487
18826
|
htmlNoToc: values["html-no-toc"],
|
|
18488
18827
|
htmlThemePicker: values["html-theme-picker"],
|
|
18489
18828
|
jsonSummary: values["json-summary"],
|
|
18829
|
+
listFormat: values["list-format"],
|
|
18490
18830
|
emitCanonical: values["emit-canonical"],
|
|
18491
18831
|
slackWebhook,
|
|
18492
18832
|
teamsWebhook,
|
|
@@ -18504,34 +18844,36 @@ function parseCliArgs(argv) {
|
|
|
18504
18844
|
assetMode: assetModeRaw,
|
|
18505
18845
|
allowMissingAssets: values["allow-missing-assets"],
|
|
18506
18846
|
prSummary: values["pr-summary"],
|
|
18507
|
-
prSummaryFile: values["pr-summary-file"]
|
|
18847
|
+
prSummaryFile: values["pr-summary-file"],
|
|
18848
|
+
config: values["config"]
|
|
18508
18849
|
};
|
|
18850
|
+
return { args: cliArgs, pluginConfig, customRequested };
|
|
18509
18851
|
}
|
|
18510
18852
|
async function readInput(args) {
|
|
18511
18853
|
if (args.stdin) {
|
|
18512
18854
|
return readStdin();
|
|
18513
18855
|
}
|
|
18514
|
-
const filePath =
|
|
18515
|
-
if (!
|
|
18856
|
+
const filePath = path7.resolve(args.inputFile);
|
|
18857
|
+
if (!fs7.existsSync(filePath)) {
|
|
18516
18858
|
console.error(`Error: File not found: ${filePath}`);
|
|
18517
18859
|
process.exit(EXIT_USAGE);
|
|
18518
18860
|
}
|
|
18519
|
-
return
|
|
18861
|
+
return fs7.readFileSync(filePath, "utf8");
|
|
18520
18862
|
}
|
|
18521
18863
|
function readFileInput(filePath) {
|
|
18522
|
-
const resolved =
|
|
18523
|
-
if (!
|
|
18864
|
+
const resolved = path7.resolve(filePath);
|
|
18865
|
+
if (!fs7.existsSync(resolved)) {
|
|
18524
18866
|
console.error(`Error: File not found: ${resolved}`);
|
|
18525
18867
|
process.exit(EXIT_USAGE);
|
|
18526
18868
|
}
|
|
18527
|
-
return
|
|
18869
|
+
return fs7.readFileSync(resolved, "utf8");
|
|
18528
18870
|
}
|
|
18529
18871
|
function readStdin() {
|
|
18530
|
-
return new Promise((
|
|
18872
|
+
return new Promise((resolve8, reject) => {
|
|
18531
18873
|
const chunks = [];
|
|
18532
18874
|
process.stdin.setEncoding("utf8");
|
|
18533
18875
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
18534
|
-
process.stdin.on("end", () =>
|
|
18876
|
+
process.stdin.on("end", () => resolve8(chunks.join("")));
|
|
18535
18877
|
process.stdin.on("error", reject);
|
|
18536
18878
|
});
|
|
18537
18879
|
}
|
|
@@ -18657,14 +18999,14 @@ function tryNormalizeRunFromText(text, args) {
|
|
|
18657
18999
|
}
|
|
18658
19000
|
}
|
|
18659
19001
|
function listBaselineCandidates(currentFile, args) {
|
|
18660
|
-
const baselineDir =
|
|
18661
|
-
const currentResolved =
|
|
18662
|
-
if (!
|
|
19002
|
+
const baselineDir = path7.resolve(args.baselineDir ?? path7.dirname(currentFile));
|
|
19003
|
+
const currentResolved = path7.resolve(currentFile);
|
|
19004
|
+
if (!fs7.existsSync(baselineDir)) {
|
|
18663
19005
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
18664
19006
|
process.exit(EXIT_USAGE);
|
|
18665
19007
|
}
|
|
18666
|
-
const entries =
|
|
18667
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
19008
|
+
const entries = fs7.readdirSync(baselineDir, { withFileTypes: true });
|
|
19009
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path7.join(baselineDir, entry.name)).filter((candidate) => path7.resolve(candidate) !== currentResolved).filter(
|
|
18668
19010
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
18669
19011
|
);
|
|
18670
19012
|
}
|
|
@@ -18672,14 +19014,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
18672
19014
|
const candidates = listBaselineCandidates(currentFile, args);
|
|
18673
19015
|
const comparable = [];
|
|
18674
19016
|
for (const candidate of candidates) {
|
|
18675
|
-
const run = tryNormalizeRunFromText(
|
|
19017
|
+
const run = tryNormalizeRunFromText(fs7.readFileSync(candidate, "utf8"), args);
|
|
18676
19018
|
if (run) {
|
|
18677
19019
|
comparable.push({ file: candidate, run });
|
|
18678
19020
|
}
|
|
18679
19021
|
}
|
|
18680
19022
|
if (comparable.length === 0) {
|
|
18681
19023
|
console.error(
|
|
18682
|
-
`Error: no compatible baseline files found in ${
|
|
19024
|
+
`Error: no compatible baseline files found in ${path7.resolve(args.baselineDir ?? path7.dirname(currentFile))}.`
|
|
18683
19025
|
);
|
|
18684
19026
|
process.exit(EXIT_USAGE);
|
|
18685
19027
|
}
|
|
@@ -18691,7 +19033,7 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
18691
19033
|
return picked.file;
|
|
18692
19034
|
}
|
|
18693
19035
|
async function main() {
|
|
18694
|
-
const args = parseCliArgs(process.argv);
|
|
19036
|
+
const { args, pluginConfig, customRequested } = await parseCliArgs(process.argv);
|
|
18695
19037
|
const startMs = Date.now();
|
|
18696
19038
|
if (args.subcommand === "compare") {
|
|
18697
19039
|
const currentText = readFileInput(args.currentFile);
|
|
@@ -18712,8 +19054,16 @@ async function main() {
|
|
|
18712
19054
|
if (args.subcommand === "list") {
|
|
18713
19055
|
const text2 = await readInput(args);
|
|
18714
19056
|
const run = applySelection(normalizeRunFromText(text2, args).run, args);
|
|
18715
|
-
const
|
|
18716
|
-
const
|
|
19057
|
+
const resolvedFormat = args.jsonSummary ? "json" : args.listFormat;
|
|
19058
|
+
const validListFormats = /* @__PURE__ */ new Set(["text", "json", "csv", "markdown-table"]);
|
|
19059
|
+
if (!validListFormats.has(resolvedFormat)) {
|
|
19060
|
+
console.error(`Error: Unknown list format "${resolvedFormat}". Valid: text, json, csv, markdown-table.`);
|
|
19061
|
+
process.exit(EXIT_USAGE);
|
|
19062
|
+
}
|
|
19063
|
+
const output = listScenarios(
|
|
19064
|
+
{ testCases: run.testCases, format: resolvedFormat },
|
|
19065
|
+
{}
|
|
19066
|
+
);
|
|
18717
19067
|
console.log(output);
|
|
18718
19068
|
process.exit(EXIT_SUCCESS);
|
|
18719
19069
|
}
|
|
@@ -18760,12 +19110,13 @@ async function main() {
|
|
|
18760
19110
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
18761
19111
|
}
|
|
18762
19112
|
if (args.emitCanonical) {
|
|
18763
|
-
const outPath =
|
|
18764
|
-
|
|
18765
|
-
|
|
19113
|
+
const outPath = path7.resolve(args.emitCanonical);
|
|
19114
|
+
fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
|
|
19115
|
+
fs7.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
18766
19116
|
}
|
|
18767
19117
|
try {
|
|
18768
19118
|
const result = await generateReports(run, args);
|
|
19119
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
18769
19120
|
await dispatchNotifications(run, args);
|
|
18770
19121
|
runHistoryPipeline(run, args);
|
|
18771
19122
|
printResult(result, args, startMs);
|
|
@@ -18818,12 +19169,13 @@ ${msg}`);
|
|
|
18818
19169
|
}
|
|
18819
19170
|
const run = data;
|
|
18820
19171
|
if (args.emitCanonical) {
|
|
18821
|
-
const outPath =
|
|
18822
|
-
|
|
18823
|
-
|
|
19172
|
+
const outPath = path7.resolve(args.emitCanonical);
|
|
19173
|
+
fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
|
|
19174
|
+
fs7.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
18824
19175
|
}
|
|
18825
19176
|
try {
|
|
18826
19177
|
const result = await generateReports(run, args);
|
|
19178
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
18827
19179
|
await dispatchNotifications(run, args);
|
|
18828
19180
|
runHistoryPipeline(run, args);
|
|
18829
19181
|
printResult(result, args, startMs);
|
|
@@ -18875,12 +19227,13 @@ ${msg}`);
|
|
|
18875
19227
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
18876
19228
|
}
|
|
18877
19229
|
if (args.emitCanonical) {
|
|
18878
|
-
const outPath =
|
|
18879
|
-
|
|
18880
|
-
|
|
19230
|
+
const outPath = path7.resolve(args.emitCanonical);
|
|
19231
|
+
fs7.mkdirSync(path7.dirname(outPath), { recursive: true });
|
|
19232
|
+
fs7.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
18881
19233
|
}
|
|
18882
19234
|
try {
|
|
18883
19235
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
19236
|
+
runCustomFormatters(canonical, customRequested, pluginConfig.formatters ?? {}, args);
|
|
18884
19237
|
await dispatchNotifications(canonical, args);
|
|
18885
19238
|
runHistoryPipeline(canonical, args);
|
|
18886
19239
|
printResult(result, args, startMs, droppedMissingStory);
|
|
@@ -18891,6 +19244,25 @@ ${msg}`);
|
|
|
18891
19244
|
process.exit(EXIT_GENERATION);
|
|
18892
19245
|
}
|
|
18893
19246
|
}
|
|
19247
|
+
function runCustomFormatters(run, customRequested, formatters, args) {
|
|
19248
|
+
if (customRequested.length === 0) return;
|
|
19249
|
+
const outputDir = args.outputDir ?? ".";
|
|
19250
|
+
for (const formatName of customRequested) {
|
|
19251
|
+
const formatter = formatters[formatName];
|
|
19252
|
+
try {
|
|
19253
|
+
const content = formatter.format(run);
|
|
19254
|
+
const ext = formatter.fileExtension ?? formatName;
|
|
19255
|
+
const baseName = args.outputName ?? "report";
|
|
19256
|
+
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
19257
|
+
const filepath = path7.join(outputDir, filename);
|
|
19258
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
19259
|
+
fs7.writeFileSync(filepath, content, "utf8");
|
|
19260
|
+
console.log(`Generated: ${filepath}`);
|
|
19261
|
+
} catch (err) {
|
|
19262
|
+
console.error(`Error running custom formatter "${formatName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
19263
|
+
}
|
|
19264
|
+
}
|
|
19265
|
+
}
|
|
18894
19266
|
async function dispatchNotifications(run, args) {
|
|
18895
19267
|
const webhooks = args.webhookUrls.map((url) => {
|
|
18896
19268
|
const opts = { url };
|
|
@@ -18934,13 +19306,13 @@ async function dispatchNotifications(run, args) {
|
|
|
18934
19306
|
}
|
|
18935
19307
|
function runHistoryPipeline(run, args) {
|
|
18936
19308
|
if (!args.historyFile) return;
|
|
18937
|
-
const historyPath =
|
|
19309
|
+
const historyPath = path7.resolve(args.historyFile);
|
|
18938
19310
|
const store = loadHistory(
|
|
18939
19311
|
{ filePath: historyPath },
|
|
18940
19312
|
{
|
|
18941
19313
|
readFile: (p) => {
|
|
18942
19314
|
try {
|
|
18943
|
-
return
|
|
19315
|
+
return fs7.readFileSync(p, "utf8");
|
|
18944
19316
|
} catch {
|
|
18945
19317
|
return void 0;
|
|
18946
19318
|
}
|
|
@@ -18953,11 +19325,11 @@ function runHistoryPipeline(run, args) {
|
|
|
18953
19325
|
run,
|
|
18954
19326
|
maxRuns: args.maxHistoryRuns
|
|
18955
19327
|
});
|
|
18956
|
-
const dir =
|
|
18957
|
-
|
|
19328
|
+
const dir = path7.dirname(historyPath);
|
|
19329
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
18958
19330
|
saveHistory(
|
|
18959
19331
|
{ filePath: historyPath, store: updated },
|
|
18960
|
-
{ writeFile: (p, content) =>
|
|
19332
|
+
{ writeFile: (p, content) => fs7.writeFileSync(p, content, "utf8") }
|
|
18961
19333
|
);
|
|
18962
19334
|
let metricsCount = 0;
|
|
18963
19335
|
for (const testId of Object.keys(updated.tests)) {
|
|
@@ -19055,9 +19427,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
19055
19427
|
function printCompareResult(result, args, startMs) {
|
|
19056
19428
|
const durationMs = Date.now() - startMs;
|
|
19057
19429
|
if (result.prSummary && args.prSummaryFile) {
|
|
19058
|
-
const outputPath =
|
|
19059
|
-
|
|
19060
|
-
|
|
19430
|
+
const outputPath = path7.resolve(args.prSummaryFile);
|
|
19431
|
+
fs7.mkdirSync(path7.dirname(outputPath), { recursive: true });
|
|
19432
|
+
fs7.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
19061
19433
|
}
|
|
19062
19434
|
if (args.jsonSummary) {
|
|
19063
19435
|
console.log(
|