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