executable-stories-formatters 0.7.7 → 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 +111 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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
|
});
|
|
@@ -17973,6 +17973,37 @@ function listScenarios(args, _deps) {
|
|
|
17973
17973
|
}));
|
|
17974
17974
|
return JSON.stringify(items, null, 2);
|
|
17975
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
|
+
}
|
|
17976
18007
|
if (testCases.length === 0) {
|
|
17977
18008
|
return "No scenarios found.";
|
|
17978
18009
|
}
|
|
@@ -18444,6 +18475,22 @@ function copyDirRecursive(src, dest) {
|
|
|
18444
18475
|
}
|
|
18445
18476
|
}
|
|
18446
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
|
+
|
|
18447
18494
|
// src/cli.ts
|
|
18448
18495
|
var EXIT_SUCCESS = 0;
|
|
18449
18496
|
var EXIT_SCHEMA_VALIDATION = 1;
|
|
@@ -18470,7 +18517,7 @@ SUBCOMMANDS
|
|
|
18470
18517
|
init-astro Scaffold an Astro Starlight docs site for story output
|
|
18471
18518
|
|
|
18472
18519
|
OPTIONS
|
|
18473
|
-
--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)
|
|
18474
18521
|
astro Starlight-compatible Markdown (for Astro docs sites)
|
|
18475
18522
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
18476
18523
|
cucumber-html Official Cucumber HTML report
|
|
@@ -18478,6 +18525,7 @@ OPTIONS
|
|
|
18478
18525
|
junit JUnit XML
|
|
18479
18526
|
cucumber-json Cucumber JSON
|
|
18480
18527
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
18528
|
+
--config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
|
|
18481
18529
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
18482
18530
|
--output-dir <dir> Output directory (default: reports)
|
|
18483
18531
|
--output-name <name> Base filename (default: index)
|
|
@@ -18501,7 +18549,8 @@ OPTIONS
|
|
|
18501
18549
|
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
18502
18550
|
--allow-missing-assets Warn on missing assets instead of failing
|
|
18503
18551
|
--stdin Read JSON from stdin instead of file
|
|
18504
|
-
--
|
|
18552
|
+
--list-format <format> list output format: text (default), json, csv, markdown-table
|
|
18553
|
+
--json-summary Deprecated alias for --list-format json
|
|
18505
18554
|
--baseline <path|auto> Compare baseline file, or auto-pick a prior run for compare
|
|
18506
18555
|
--baseline-dir <dir> Directory to scan when --baseline auto is used
|
|
18507
18556
|
--pr-summary Print a PR-friendly markdown summary after compare
|
|
@@ -18510,7 +18559,8 @@ OPTIONS
|
|
|
18510
18559
|
--help Show this help message
|
|
18511
18560
|
|
|
18512
18561
|
LIST
|
|
18513
|
-
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)
|
|
18514
18564
|
list supports --include-tags, --exclude-tags for filtering
|
|
18515
18565
|
list supports --input-type and --stdin
|
|
18516
18566
|
|
|
@@ -18549,7 +18599,7 @@ EXIT CODES
|
|
|
18549
18599
|
3 Formatter/generation failure
|
|
18550
18600
|
4 Bad arguments / usage error
|
|
18551
18601
|
`.trim();
|
|
18552
|
-
function parseCliArgs(argv) {
|
|
18602
|
+
async function parseCliArgs(argv) {
|
|
18553
18603
|
const args = argv.slice(2);
|
|
18554
18604
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
18555
18605
|
console.log(HELP_TEXT);
|
|
@@ -18609,6 +18659,7 @@ function parseCliArgs(argv) {
|
|
|
18609
18659
|
"html-theme-picker": { type: "boolean", default: false },
|
|
18610
18660
|
stdin: { type: "boolean", default: false },
|
|
18611
18661
|
"json-summary": { type: "boolean", default: false },
|
|
18662
|
+
"list-format": { type: "string", default: "text" },
|
|
18612
18663
|
"emit-canonical": { type: "string" },
|
|
18613
18664
|
"slack-webhook": { type: "string" },
|
|
18614
18665
|
"teams-webhook": { type: "string" },
|
|
@@ -18627,6 +18678,7 @@ function parseCliArgs(argv) {
|
|
|
18627
18678
|
"allow-missing-assets": { type: "boolean", default: false },
|
|
18628
18679
|
"pr-summary": { type: "boolean", default: false },
|
|
18629
18680
|
"pr-summary-file": { type: "string" },
|
|
18681
|
+
"config": { type: "string" },
|
|
18630
18682
|
help: { type: "boolean", default: false }
|
|
18631
18683
|
},
|
|
18632
18684
|
allowPositionals: true,
|
|
@@ -18664,15 +18716,20 @@ function parseCliArgs(argv) {
|
|
|
18664
18716
|
console.error(`Error: --input-type must be "raw", "canonical", or "ndjson", got "${inputType}".`);
|
|
18665
18717
|
process.exit(EXIT_USAGE);
|
|
18666
18718
|
}
|
|
18667
|
-
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"]);
|
|
18668
18722
|
const formatStr = values.format;
|
|
18669
|
-
const
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
}
|
|
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);
|
|
18675
18731
|
}
|
|
18732
|
+
const formats = builtInRequested;
|
|
18676
18733
|
const htmlTheme = values["html-theme"];
|
|
18677
18734
|
const validThemes = /* @__PURE__ */ new Set(["default", "corporate", "terminal", "minimal", "dashboard", "playful"]);
|
|
18678
18735
|
if (!validThemes.has(htmlTheme)) {
|
|
@@ -18740,7 +18797,7 @@ function parseCliArgs(argv) {
|
|
|
18740
18797
|
console.error(`Error: --asset-mode must be "none" or "copy", got "${assetModeRaw}".`);
|
|
18741
18798
|
process.exit(EXIT_USAGE);
|
|
18742
18799
|
}
|
|
18743
|
-
|
|
18800
|
+
const cliArgs = {
|
|
18744
18801
|
subcommand,
|
|
18745
18802
|
inputFile,
|
|
18746
18803
|
baselineFile,
|
|
@@ -18769,6 +18826,7 @@ function parseCliArgs(argv) {
|
|
|
18769
18826
|
htmlNoToc: values["html-no-toc"],
|
|
18770
18827
|
htmlThemePicker: values["html-theme-picker"],
|
|
18771
18828
|
jsonSummary: values["json-summary"],
|
|
18829
|
+
listFormat: values["list-format"],
|
|
18772
18830
|
emitCanonical: values["emit-canonical"],
|
|
18773
18831
|
slackWebhook,
|
|
18774
18832
|
teamsWebhook,
|
|
@@ -18786,8 +18844,10 @@ function parseCliArgs(argv) {
|
|
|
18786
18844
|
assetMode: assetModeRaw,
|
|
18787
18845
|
allowMissingAssets: values["allow-missing-assets"],
|
|
18788
18846
|
prSummary: values["pr-summary"],
|
|
18789
|
-
prSummaryFile: values["pr-summary-file"]
|
|
18847
|
+
prSummaryFile: values["pr-summary-file"],
|
|
18848
|
+
config: values["config"]
|
|
18790
18849
|
};
|
|
18850
|
+
return { args: cliArgs, pluginConfig, customRequested };
|
|
18791
18851
|
}
|
|
18792
18852
|
async function readInput(args) {
|
|
18793
18853
|
if (args.stdin) {
|
|
@@ -18809,11 +18869,11 @@ function readFileInput(filePath) {
|
|
|
18809
18869
|
return fs7.readFileSync(resolved, "utf8");
|
|
18810
18870
|
}
|
|
18811
18871
|
function readStdin() {
|
|
18812
|
-
return new Promise((
|
|
18872
|
+
return new Promise((resolve8, reject) => {
|
|
18813
18873
|
const chunks = [];
|
|
18814
18874
|
process.stdin.setEncoding("utf8");
|
|
18815
18875
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
18816
|
-
process.stdin.on("end", () =>
|
|
18876
|
+
process.stdin.on("end", () => resolve8(chunks.join("")));
|
|
18817
18877
|
process.stdin.on("error", reject);
|
|
18818
18878
|
});
|
|
18819
18879
|
}
|
|
@@ -18973,7 +19033,7 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
18973
19033
|
return picked.file;
|
|
18974
19034
|
}
|
|
18975
19035
|
async function main() {
|
|
18976
|
-
const args = parseCliArgs(process.argv);
|
|
19036
|
+
const { args, pluginConfig, customRequested } = await parseCliArgs(process.argv);
|
|
18977
19037
|
const startMs = Date.now();
|
|
18978
19038
|
if (args.subcommand === "compare") {
|
|
18979
19039
|
const currentText = readFileInput(args.currentFile);
|
|
@@ -18994,8 +19054,16 @@ async function main() {
|
|
|
18994
19054
|
if (args.subcommand === "list") {
|
|
18995
19055
|
const text2 = await readInput(args);
|
|
18996
19056
|
const run = applySelection(normalizeRunFromText(text2, args).run, args);
|
|
18997
|
-
const
|
|
18998
|
-
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
|
+
);
|
|
18999
19067
|
console.log(output);
|
|
19000
19068
|
process.exit(EXIT_SUCCESS);
|
|
19001
19069
|
}
|
|
@@ -19048,6 +19116,7 @@ async function main() {
|
|
|
19048
19116
|
}
|
|
19049
19117
|
try {
|
|
19050
19118
|
const result = await generateReports(run, args);
|
|
19119
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19051
19120
|
await dispatchNotifications(run, args);
|
|
19052
19121
|
runHistoryPipeline(run, args);
|
|
19053
19122
|
printResult(result, args, startMs);
|
|
@@ -19106,6 +19175,7 @@ ${msg}`);
|
|
|
19106
19175
|
}
|
|
19107
19176
|
try {
|
|
19108
19177
|
const result = await generateReports(run, args);
|
|
19178
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19109
19179
|
await dispatchNotifications(run, args);
|
|
19110
19180
|
runHistoryPipeline(run, args);
|
|
19111
19181
|
printResult(result, args, startMs);
|
|
@@ -19163,6 +19233,7 @@ ${msg}`);
|
|
|
19163
19233
|
}
|
|
19164
19234
|
try {
|
|
19165
19235
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
19236
|
+
runCustomFormatters(canonical, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19166
19237
|
await dispatchNotifications(canonical, args);
|
|
19167
19238
|
runHistoryPipeline(canonical, args);
|
|
19168
19239
|
printResult(result, args, startMs, droppedMissingStory);
|
|
@@ -19173,6 +19244,25 @@ ${msg}`);
|
|
|
19173
19244
|
process.exit(EXIT_GENERATION);
|
|
19174
19245
|
}
|
|
19175
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
|
+
}
|
|
19176
19266
|
async function dispatchNotifications(run, args) {
|
|
19177
19267
|
const webhooks = args.webhookUrls.map((url) => {
|
|
19178
19268
|
const opts = { url };
|