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/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 fs9 from "fs";
|
|
6
|
+
import * as path10 from "path";
|
|
7
7
|
|
|
8
8
|
// src/validation/schema-validator.ts
|
|
9
9
|
import Ajv from "ajv/dist/2020.js";
|
|
@@ -539,17 +539,17 @@ function validateRawRun(data) {
|
|
|
539
539
|
return { valid: true, errors: [] };
|
|
540
540
|
}
|
|
541
541
|
const errors = (validate.errors ?? []).map((err) => {
|
|
542
|
-
const
|
|
542
|
+
const path11 = err.instancePath || "/";
|
|
543
543
|
const message = err.message ?? "unknown error";
|
|
544
544
|
if (err.keyword === "additionalProperties") {
|
|
545
545
|
const extra = err.params.additionalProperty;
|
|
546
|
-
return `${
|
|
546
|
+
return `${path11}: ${message} \u2014 '${extra}'`;
|
|
547
547
|
}
|
|
548
548
|
if (err.keyword === "enum") {
|
|
549
549
|
const allowed = err.params.allowedValues;
|
|
550
|
-
return `${
|
|
550
|
+
return `${path11}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
|
|
551
551
|
}
|
|
552
|
-
return `${
|
|
552
|
+
return `${path11}: ${message}`;
|
|
553
553
|
});
|
|
554
554
|
return { valid: false, errors };
|
|
555
555
|
}
|
|
@@ -1024,7 +1024,7 @@ ${result.errors.join("\n")}`);
|
|
|
1024
1024
|
|
|
1025
1025
|
// src/index.ts
|
|
1026
1026
|
import "fs";
|
|
1027
|
-
import * as
|
|
1027
|
+
import * as path8 from "path";
|
|
1028
1028
|
import * as fsPromises from "fs/promises";
|
|
1029
1029
|
|
|
1030
1030
|
// src/converters/acl/lines.ts
|
|
@@ -1569,6 +1569,9 @@ function buildScenario(tc, featureId) {
|
|
|
1569
1569
|
if (tickets && tickets.length > 0) {
|
|
1570
1570
|
scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
|
|
1571
1571
|
}
|
|
1572
|
+
if (tc.story.covers && tc.story.covers.length > 0) {
|
|
1573
|
+
scenario.covers = [...tc.story.covers];
|
|
1574
|
+
}
|
|
1572
1575
|
return scenario;
|
|
1573
1576
|
}
|
|
1574
1577
|
function deriveFeatureTitle(group, relSourceFile) {
|
|
@@ -1692,6 +1695,181 @@ var StoryReportJsonFormatter = class {
|
|
|
1692
1695
|
}
|
|
1693
1696
|
};
|
|
1694
1697
|
|
|
1698
|
+
// src/formatters/scenario-index-json.ts
|
|
1699
|
+
var ScenarioIndexJsonFormatter = class {
|
|
1700
|
+
options;
|
|
1701
|
+
constructor(options = {}) {
|
|
1702
|
+
this.options = {
|
|
1703
|
+
pretty: options.pretty ?? true,
|
|
1704
|
+
filters: options.filters
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
toIndex(run) {
|
|
1708
|
+
return toScenarioIndex(toStoryReport(run), this.options.filters);
|
|
1709
|
+
}
|
|
1710
|
+
format(run) {
|
|
1711
|
+
const index = this.toIndex(run);
|
|
1712
|
+
return this.options.pretty ? JSON.stringify(index, null, 2) : JSON.stringify(index);
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
function toScenarioIndex(report, filters = {}) {
|
|
1716
|
+
const scenarios = report.features.flatMap(
|
|
1717
|
+
(feature) => feature.scenarios.map((scenario) => toScenarioIndexItem(feature, scenario))
|
|
1718
|
+
).filter((scenario) => matchesFilters(scenario, filters));
|
|
1719
|
+
return {
|
|
1720
|
+
schemaVersion: "1.0",
|
|
1721
|
+
runId: report.runId,
|
|
1722
|
+
generatedAtMs: report.finishedAtMs,
|
|
1723
|
+
summary: summarize(scenarios),
|
|
1724
|
+
scenarios
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function toScenarioIndexItem(feature, scenario) {
|
|
1728
|
+
return {
|
|
1729
|
+
id: scenario.id,
|
|
1730
|
+
title: scenario.title,
|
|
1731
|
+
status: scenario.status,
|
|
1732
|
+
feature: feature.title,
|
|
1733
|
+
sourceFile: feature.sourceFile,
|
|
1734
|
+
sourceLine: scenario.sourceLine,
|
|
1735
|
+
tags: scenario.tags,
|
|
1736
|
+
tickets: scenario.tickets ?? [],
|
|
1737
|
+
covers: scenario.covers ?? [],
|
|
1738
|
+
durationMs: scenario.durationMs,
|
|
1739
|
+
steps: scenario.steps.map((step) => ({
|
|
1740
|
+
id: step.id,
|
|
1741
|
+
index: step.index,
|
|
1742
|
+
keyword: step.keyword,
|
|
1743
|
+
text: step.text,
|
|
1744
|
+
status: step.status,
|
|
1745
|
+
durationMs: step.durationMs,
|
|
1746
|
+
errorMessage: step.errorMessage,
|
|
1747
|
+
docKinds: step.docEntries.map((entry) => entry.kind)
|
|
1748
|
+
})),
|
|
1749
|
+
docKinds: scenario.docEntries.map((entry) => entry.kind),
|
|
1750
|
+
error: scenario.errorMessage ? { message: scenario.errorMessage, stack: scenario.errorStack } : void 0
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
function matchesFilters(scenario, filters) {
|
|
1754
|
+
if (filters.statuses?.length && !filters.statuses.includes(scenario.status)) {
|
|
1755
|
+
return false;
|
|
1756
|
+
}
|
|
1757
|
+
if (filters.tags?.length && !filters.tags.some((tag) => scenario.tags.includes(tag))) {
|
|
1758
|
+
return false;
|
|
1759
|
+
}
|
|
1760
|
+
if (filters.sourceFiles?.length && !filters.sourceFiles.some((sourceFile) => scenario.sourceFile.includes(sourceFile))) {
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
function summarize(scenarios) {
|
|
1766
|
+
return {
|
|
1767
|
+
total: scenarios.length,
|
|
1768
|
+
passed: scenarios.filter((scenario) => scenario.status === "passed").length,
|
|
1769
|
+
failed: scenarios.filter((scenario) => scenario.status === "failed").length,
|
|
1770
|
+
skipped: scenarios.filter((scenario) => scenario.status === "skipped").length,
|
|
1771
|
+
pending: scenarios.filter((scenario) => scenario.status === "pending").length,
|
|
1772
|
+
durationMs: scenarios.reduce((total, scenario) => total + scenario.durationMs, 0)
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/formatters/behavior-manifest-json.ts
|
|
1777
|
+
var BehaviorManifestJsonFormatter = class {
|
|
1778
|
+
pretty;
|
|
1779
|
+
constructor(options = {}) {
|
|
1780
|
+
this.pretty = options.pretty ?? true;
|
|
1781
|
+
}
|
|
1782
|
+
toManifest(run) {
|
|
1783
|
+
return toBehaviorManifest(toStoryReport(run));
|
|
1784
|
+
}
|
|
1785
|
+
format(run) {
|
|
1786
|
+
const manifest = this.toManifest(run);
|
|
1787
|
+
return this.pretty ? JSON.stringify(manifest, null, 2) : JSON.stringify(manifest);
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1790
|
+
function toBehaviorManifest(report) {
|
|
1791
|
+
const index = toScenarioIndex(report);
|
|
1792
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1793
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1794
|
+
const docKinds = /* @__PURE__ */ new Set();
|
|
1795
|
+
const debuggerIssues = [];
|
|
1796
|
+
for (const scenario of index.scenarios) {
|
|
1797
|
+
const source = bySource.get(scenario.sourceFile) ?? {
|
|
1798
|
+
path: scenario.sourceFile,
|
|
1799
|
+
scenarioCount: 0,
|
|
1800
|
+
failed: 0,
|
|
1801
|
+
tags: []
|
|
1802
|
+
};
|
|
1803
|
+
source.scenarioCount += 1;
|
|
1804
|
+
if (scenario.status === "failed") source.failed += 1;
|
|
1805
|
+
source.tags = [.../* @__PURE__ */ new Set([...source.tags, ...scenario.tags])].sort();
|
|
1806
|
+
bySource.set(scenario.sourceFile, source);
|
|
1807
|
+
for (const tag of scenario.tags) {
|
|
1808
|
+
const tagEntry = byTag.get(tag) ?? { name: tag, scenarioCount: 0 };
|
|
1809
|
+
tagEntry.scenarioCount += 1;
|
|
1810
|
+
byTag.set(tag, tagEntry);
|
|
1811
|
+
}
|
|
1812
|
+
for (const kind of scenario.docKinds) docKinds.add(kind);
|
|
1813
|
+
for (const step of scenario.steps) {
|
|
1814
|
+
for (const kind of step.docKinds) docKinds.add(kind);
|
|
1815
|
+
}
|
|
1816
|
+
if (!scenarioHasDocs(scenario)) {
|
|
1817
|
+
debuggerIssues.push({
|
|
1818
|
+
severity: "warning",
|
|
1819
|
+
code: "missing-docs",
|
|
1820
|
+
scenarioId: scenario.id,
|
|
1821
|
+
title: scenario.title,
|
|
1822
|
+
message: "Scenario has no doc entries."
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
if (scenario.tags.length === 0) {
|
|
1826
|
+
debuggerIssues.push({
|
|
1827
|
+
severity: "warning",
|
|
1828
|
+
code: "missing-tags",
|
|
1829
|
+
scenarioId: scenario.id,
|
|
1830
|
+
title: scenario.title,
|
|
1831
|
+
message: "Scenario has no tags."
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
if (scenario.covers.length === 0) {
|
|
1835
|
+
debuggerIssues.push({
|
|
1836
|
+
severity: "warning",
|
|
1837
|
+
code: "missing-covers",
|
|
1838
|
+
scenarioId: scenario.id,
|
|
1839
|
+
title: scenario.title,
|
|
1840
|
+
message: "Scenario declares no covers (product-code paths), so code\u2192scenario lookup cannot find it."
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
if (scenario.sourceLine === void 0) {
|
|
1844
|
+
debuggerIssues.push({
|
|
1845
|
+
severity: "warning",
|
|
1846
|
+
code: "missing-source-line",
|
|
1847
|
+
scenarioId: scenario.id,
|
|
1848
|
+
title: scenario.title,
|
|
1849
|
+
message: "Scenario has no source line."
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
const scenariosWithDocs = index.scenarios.filter(scenarioHasDocs).length;
|
|
1854
|
+
return {
|
|
1855
|
+
schemaVersion: "1.0",
|
|
1856
|
+
runId: report.runId,
|
|
1857
|
+
generatedAtMs: report.finishedAtMs,
|
|
1858
|
+
summary: index.summary,
|
|
1859
|
+
sourceFiles: [...bySource.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
1860
|
+
tags: [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name)),
|
|
1861
|
+
docCoverage: {
|
|
1862
|
+
scenariosWithDocs,
|
|
1863
|
+
scenariosWithoutDocs: index.scenarios.length - scenariosWithDocs,
|
|
1864
|
+
docKinds: [...docKinds].sort()
|
|
1865
|
+
},
|
|
1866
|
+
debugger: debuggerIssues
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
function scenarioHasDocs(scenario) {
|
|
1870
|
+
return scenario.docKinds.length > 0 || scenario.steps.some((step) => step.docKinds.length > 0);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1695
1873
|
// src/formatters/html/renderers/index.ts
|
|
1696
1874
|
import * as fs2 from "fs";
|
|
1697
1875
|
import * as path3 from "path";
|
|
@@ -16526,8 +16704,8 @@ function extractDocAttachments(step) {
|
|
|
16526
16704
|
}
|
|
16527
16705
|
return attachments;
|
|
16528
16706
|
}
|
|
16529
|
-
function guessMediaType(
|
|
16530
|
-
const lower =
|
|
16707
|
+
function guessMediaType(path11) {
|
|
16708
|
+
const lower = path11.toLowerCase();
|
|
16531
16709
|
if (lower.endsWith(".png")) return "image/png";
|
|
16532
16710
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
16533
16711
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -16668,11 +16846,11 @@ var CucumberHtmlFormatter = class {
|
|
|
16668
16846
|
for (const envelope of envelopes) {
|
|
16669
16847
|
const accepted = htmlStream.write(envelope);
|
|
16670
16848
|
if (!accepted) {
|
|
16671
|
-
await new Promise((
|
|
16849
|
+
await new Promise((resolve9) => htmlStream.once("drain", resolve9));
|
|
16672
16850
|
}
|
|
16673
16851
|
}
|
|
16674
|
-
await new Promise((
|
|
16675
|
-
collector.on("finish",
|
|
16852
|
+
await new Promise((resolve9, reject) => {
|
|
16853
|
+
collector.on("finish", resolve9);
|
|
16676
16854
|
collector.on("error", reject);
|
|
16677
16855
|
htmlStream.end();
|
|
16678
16856
|
});
|
|
@@ -18283,6 +18461,68 @@ function copyMarkdownAssets(options) {
|
|
|
18283
18461
|
};
|
|
18284
18462
|
}
|
|
18285
18463
|
|
|
18464
|
+
// src/watch.ts
|
|
18465
|
+
import * as fs6 from "fs";
|
|
18466
|
+
import * as path7 from "path";
|
|
18467
|
+
function toRun(data, inputType, synthesize) {
|
|
18468
|
+
if (inputType === "canonical") return data;
|
|
18469
|
+
let raw = data;
|
|
18470
|
+
if (synthesize) raw = synthesizeStories(raw);
|
|
18471
|
+
return canonicalizeRun(raw);
|
|
18472
|
+
}
|
|
18473
|
+
async function regenerateArtifacts(options, deps = {}) {
|
|
18474
|
+
const read = deps.readFile ?? ((filePath) => fs6.readFileSync(filePath, "utf8"));
|
|
18475
|
+
const data = JSON.parse(read(path7.resolve(options.input)));
|
|
18476
|
+
const run = toRun(data, options.inputType ?? "raw", options.synthesize !== false);
|
|
18477
|
+
const generator = new ReportGenerator({
|
|
18478
|
+
formats: options.formats,
|
|
18479
|
+
outputDir: options.outputDir,
|
|
18480
|
+
outputName: options.outputName
|
|
18481
|
+
});
|
|
18482
|
+
const result = await generator.generate(run);
|
|
18483
|
+
return [...result.values()].flat();
|
|
18484
|
+
}
|
|
18485
|
+
function startWatch(options, deps = {}) {
|
|
18486
|
+
const log = deps.log ?? ((message) => console.log(message));
|
|
18487
|
+
const regenerate = deps.regenerate ?? ((input) => regenerateArtifacts({ ...options, input }, deps));
|
|
18488
|
+
const watchFn = deps.watch ?? ((filePath, listener) => fs6.watch(filePath, listener));
|
|
18489
|
+
const debounceMs = options.debounceMs ?? 150;
|
|
18490
|
+
let timer;
|
|
18491
|
+
let running = false;
|
|
18492
|
+
let pending = false;
|
|
18493
|
+
const run = async () => {
|
|
18494
|
+
if (running) {
|
|
18495
|
+
pending = true;
|
|
18496
|
+
return;
|
|
18497
|
+
}
|
|
18498
|
+
running = true;
|
|
18499
|
+
try {
|
|
18500
|
+
const files = await regenerate(options.input);
|
|
18501
|
+
log(`Regenerated ${files.length} artifact file(s) from ${options.input}`);
|
|
18502
|
+
} catch (error) {
|
|
18503
|
+
log(`Watch regeneration failed: ${error.message}`);
|
|
18504
|
+
} finally {
|
|
18505
|
+
running = false;
|
|
18506
|
+
if (pending) {
|
|
18507
|
+
pending = false;
|
|
18508
|
+
trigger();
|
|
18509
|
+
}
|
|
18510
|
+
}
|
|
18511
|
+
};
|
|
18512
|
+
const trigger = () => {
|
|
18513
|
+
if (timer) clearTimeout(timer);
|
|
18514
|
+
timer = setTimeout(() => void run(), debounceMs);
|
|
18515
|
+
};
|
|
18516
|
+
trigger();
|
|
18517
|
+
const watcher = watchFn(path7.resolve(options.input), trigger);
|
|
18518
|
+
return {
|
|
18519
|
+
close: () => {
|
|
18520
|
+
if (timer) clearTimeout(timer);
|
|
18521
|
+
watcher.close();
|
|
18522
|
+
}
|
|
18523
|
+
};
|
|
18524
|
+
}
|
|
18525
|
+
|
|
18286
18526
|
// src/publishers/confluence.ts
|
|
18287
18527
|
function parseAdf(adf) {
|
|
18288
18528
|
let parsed;
|
|
@@ -19451,12 +19691,22 @@ function listScenarios(args, _deps) {
|
|
|
19451
19691
|
const { testCases, format } = args;
|
|
19452
19692
|
if (format === "json") {
|
|
19453
19693
|
const items = testCases.map((tc) => ({
|
|
19694
|
+
id: tc.id,
|
|
19454
19695
|
scenario: tc.story.scenario,
|
|
19455
19696
|
status: tc.status,
|
|
19456
19697
|
sourceFile: tc.sourceFile,
|
|
19457
19698
|
sourceLine: tc.sourceLine,
|
|
19699
|
+
suitePath: tc.story.suitePath ?? tc.titlePath.slice(0, -1),
|
|
19458
19700
|
tags: tc.tags,
|
|
19459
|
-
|
|
19701
|
+
tickets: tc.story.tickets ?? [],
|
|
19702
|
+
covers: tc.story.covers ?? [],
|
|
19703
|
+
durationMs: tc.durationMs,
|
|
19704
|
+
error: tc.errorMessage ? {
|
|
19705
|
+
message: tc.errorMessage,
|
|
19706
|
+
stack: tc.errorStack
|
|
19707
|
+
} : void 0,
|
|
19708
|
+
steps: tc.story.steps.map((step, index) => toScenarioStep(step, index, tc)),
|
|
19709
|
+
docKinds: collectDocKinds(tc)
|
|
19460
19710
|
}));
|
|
19461
19711
|
return JSON.stringify(items, null, 2);
|
|
19462
19712
|
}
|
|
@@ -19529,6 +19779,34 @@ function listScenarios(args, _deps) {
|
|
|
19529
19779
|
];
|
|
19530
19780
|
return lines.join("\n");
|
|
19531
19781
|
}
|
|
19782
|
+
function toScenarioStep(step, index, testCase) {
|
|
19783
|
+
const result = testCase.stepResults.find(
|
|
19784
|
+
(candidate) => candidate.index === index || candidate.stepId === step.id
|
|
19785
|
+
);
|
|
19786
|
+
return {
|
|
19787
|
+
id: step.id,
|
|
19788
|
+
index,
|
|
19789
|
+
keyword: step.keyword,
|
|
19790
|
+
text: step.text,
|
|
19791
|
+
status: result?.status ?? testCase.status,
|
|
19792
|
+
durationMs: result?.durationMs ?? step.durationMs ?? 0,
|
|
19793
|
+
errorMessage: result?.errorMessage,
|
|
19794
|
+
mode: step.mode,
|
|
19795
|
+
docKinds: (step.docs ?? []).map((doc) => doc.kind)
|
|
19796
|
+
};
|
|
19797
|
+
}
|
|
19798
|
+
function collectDocKinds(testCase) {
|
|
19799
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
19800
|
+
for (const doc of testCase.story.docs ?? []) {
|
|
19801
|
+
kinds.add(doc.kind);
|
|
19802
|
+
}
|
|
19803
|
+
for (const step of testCase.story.steps) {
|
|
19804
|
+
for (const doc of step.docs ?? []) {
|
|
19805
|
+
kinds.add(doc.kind);
|
|
19806
|
+
}
|
|
19807
|
+
}
|
|
19808
|
+
return [...kinds].sort();
|
|
19809
|
+
}
|
|
19532
19810
|
|
|
19533
19811
|
// src/review/conventions.ts
|
|
19534
19812
|
var CHANGE_TAG_PREFIX = "change:";
|
|
@@ -19576,18 +19854,18 @@ function deriveChangeType(tags) {
|
|
|
19576
19854
|
}
|
|
19577
19855
|
return "unknown";
|
|
19578
19856
|
}
|
|
19579
|
-
function extensionOf(
|
|
19580
|
-
const base =
|
|
19857
|
+
function extensionOf(path11) {
|
|
19858
|
+
const base = path11.split("/").pop() ?? path11;
|
|
19581
19859
|
const dot = base.lastIndexOf(".");
|
|
19582
19860
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
19583
19861
|
}
|
|
19584
|
-
function isTestFile(
|
|
19585
|
-
return TEST_INFIX.test(
|
|
19862
|
+
function isTestFile(path11) {
|
|
19863
|
+
return TEST_INFIX.test(path11);
|
|
19586
19864
|
}
|
|
19587
|
-
function isReviewableSource(
|
|
19588
|
-
if (isTestFile(
|
|
19589
|
-
if (
|
|
19590
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
19865
|
+
function isReviewableSource(path11) {
|
|
19866
|
+
if (isTestFile(path11)) return false;
|
|
19867
|
+
if (path11.endsWith(".d.ts")) return false;
|
|
19868
|
+
return CODE_EXTENSIONS.has(extensionOf(path11));
|
|
19591
19869
|
}
|
|
19592
19870
|
function testBaseKey(testFile) {
|
|
19593
19871
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -19691,7 +19969,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
19691
19969
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
19692
19970
|
const key = testBaseKey(testCase.sourceFile);
|
|
19693
19971
|
const coversFiles = changedSourcePaths.filter(
|
|
19694
|
-
(
|
|
19972
|
+
(path11) => sourceBaseKey(path11) === key
|
|
19695
19973
|
);
|
|
19696
19974
|
return {
|
|
19697
19975
|
id: testCase.id,
|
|
@@ -20224,6 +20502,7 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
20224
20502
|
// src/index.ts
|
|
20225
20503
|
var FORMAT_EXTENSIONS = {
|
|
20226
20504
|
astro: ".md",
|
|
20505
|
+
"behavior-manifest-json": ".behavior-manifest.json",
|
|
20227
20506
|
markdown: ".md",
|
|
20228
20507
|
html: ".html",
|
|
20229
20508
|
"cucumber-html": ".cucumber.html",
|
|
@@ -20231,6 +20510,7 @@ var FORMAT_EXTENSIONS = {
|
|
|
20231
20510
|
"cucumber-json": ".cucumber.json",
|
|
20232
20511
|
"cucumber-messages": ".ndjson",
|
|
20233
20512
|
confluence: ".adf.json",
|
|
20513
|
+
"scenario-index-json": ".scenarios-index.json",
|
|
20234
20514
|
"story-report-json": ".story-report.json"
|
|
20235
20515
|
};
|
|
20236
20516
|
var TEST_EXTENSIONS = [
|
|
@@ -20258,11 +20538,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20258
20538
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20259
20539
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
20260
20540
|
if (mode === "aggregated") {
|
|
20261
|
-
return toPosix(
|
|
20541
|
+
return toPosix(path8.join(baseOutputDir, `${effectiveName}${ext}`));
|
|
20262
20542
|
}
|
|
20263
20543
|
const normalizedSource = toPosix(sourceFile);
|
|
20264
|
-
const dirOfSource =
|
|
20265
|
-
let baseName =
|
|
20544
|
+
const dirOfSource = path8.posix.dirname(normalizedSource);
|
|
20545
|
+
let baseName = path8.posix.basename(normalizedSource);
|
|
20266
20546
|
for (const testExt of TEST_EXTENSIONS) {
|
|
20267
20547
|
if (baseName.endsWith(testExt)) {
|
|
20268
20548
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -20271,9 +20551,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20271
20551
|
}
|
|
20272
20552
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
20273
20553
|
if (colocatedStyle === "adjacent") {
|
|
20274
|
-
return toPosix(
|
|
20554
|
+
return toPosix(path8.posix.join(dirOfSource, fileName));
|
|
20275
20555
|
}
|
|
20276
|
-
return toPosix(
|
|
20556
|
+
return toPosix(path8.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
20277
20557
|
}
|
|
20278
20558
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
20279
20559
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -20359,6 +20639,12 @@ var ReportGenerator = class {
|
|
|
20359
20639
|
storyReportJson: {
|
|
20360
20640
|
pretty: options.storyReportJson?.pretty ?? true
|
|
20361
20641
|
},
|
|
20642
|
+
scenarioIndexJson: {
|
|
20643
|
+
pretty: options.scenarioIndexJson?.pretty ?? true
|
|
20644
|
+
},
|
|
20645
|
+
behaviorManifestJson: {
|
|
20646
|
+
pretty: options.behaviorManifestJson?.pretty ?? true
|
|
20647
|
+
},
|
|
20362
20648
|
cucumberMessages: {
|
|
20363
20649
|
uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
|
|
20364
20650
|
includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
|
|
@@ -20474,8 +20760,8 @@ var ReportGenerator = class {
|
|
|
20474
20760
|
if (astroPaths) {
|
|
20475
20761
|
for (const mdPath of astroPaths) {
|
|
20476
20762
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
20477
|
-
const mdDir =
|
|
20478
|
-
const assetsDir =
|
|
20763
|
+
const mdDir = path8.dirname(mdPath);
|
|
20764
|
+
const assetsDir = path8.resolve(this.options.astro.assetsDir);
|
|
20479
20765
|
const result = copyMarkdownAssets({
|
|
20480
20766
|
markdown: content,
|
|
20481
20767
|
markdownDir: mdDir,
|
|
@@ -20506,9 +20792,9 @@ var ReportGenerator = class {
|
|
|
20506
20792
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
20507
20793
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20508
20794
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
20509
|
-
const outputPath = toPosix(
|
|
20795
|
+
const outputPath = toPosix(path8.join(this.options.outputDir, `${effectiveName}${ext}`));
|
|
20510
20796
|
const content = await this.formatContent(run, format);
|
|
20511
|
-
const dir =
|
|
20797
|
+
const dir = path8.dirname(outputPath);
|
|
20512
20798
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20513
20799
|
await this.deps.writeFile(outputPath, content);
|
|
20514
20800
|
return [outputPath];
|
|
@@ -20520,7 +20806,7 @@ var ReportGenerator = class {
|
|
|
20520
20806
|
testCases
|
|
20521
20807
|
};
|
|
20522
20808
|
const content = await this.formatContent(groupRun, format);
|
|
20523
|
-
const dir =
|
|
20809
|
+
const dir = path8.dirname(outputPath);
|
|
20524
20810
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20525
20811
|
await this.deps.writeFile(outputPath, content);
|
|
20526
20812
|
writtenPaths.push(outputPath);
|
|
@@ -20633,6 +20919,18 @@ var ReportGenerator = class {
|
|
|
20633
20919
|
});
|
|
20634
20920
|
return formatter.format(run);
|
|
20635
20921
|
}
|
|
20922
|
+
case "scenario-index-json": {
|
|
20923
|
+
const formatter = new ScenarioIndexJsonFormatter({
|
|
20924
|
+
pretty: this.options.scenarioIndexJson.pretty
|
|
20925
|
+
});
|
|
20926
|
+
return formatter.format(run);
|
|
20927
|
+
}
|
|
20928
|
+
case "behavior-manifest-json": {
|
|
20929
|
+
const formatter = new BehaviorManifestJsonFormatter({
|
|
20930
|
+
pretty: this.options.behaviorManifestJson.pretty
|
|
20931
|
+
});
|
|
20932
|
+
return formatter.format(run);
|
|
20933
|
+
}
|
|
20636
20934
|
default:
|
|
20637
20935
|
throw new Error(`Unknown format: ${format}`);
|
|
20638
20936
|
}
|
|
@@ -20646,7 +20944,7 @@ async function generateRunComparison(args) {
|
|
|
20646
20944
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
20647
20945
|
for (const format of args.formats) {
|
|
20648
20946
|
const ext = format === "html" ? ".html" : ".md";
|
|
20649
|
-
const outputPath = toPosix(
|
|
20947
|
+
const outputPath = toPosix(path8.join(outputDir, `${outputName}${ext}`));
|
|
20650
20948
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
20651
20949
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
20652
20950
|
files.push(outputPath);
|
|
@@ -20655,23 +20953,23 @@ async function generateRunComparison(args) {
|
|
|
20655
20953
|
}
|
|
20656
20954
|
|
|
20657
20955
|
// src/init-astro.ts
|
|
20658
|
-
import * as
|
|
20659
|
-
import * as
|
|
20956
|
+
import * as fs8 from "fs";
|
|
20957
|
+
import * as path9 from "path";
|
|
20660
20958
|
import { fileURLToPath } from "url";
|
|
20661
|
-
var __dirname =
|
|
20959
|
+
var __dirname = path9.dirname(fileURLToPath(import.meta.url));
|
|
20662
20960
|
function initAstro(options = {}) {
|
|
20663
20961
|
const targetDir = options.targetDir ?? "./story-docs";
|
|
20664
20962
|
const force = options.force ?? false;
|
|
20665
|
-
if (
|
|
20666
|
-
const entries =
|
|
20963
|
+
if (fs8.existsSync(targetDir)) {
|
|
20964
|
+
const entries = fs8.readdirSync(targetDir);
|
|
20667
20965
|
if (entries.length > 0 && !force) {
|
|
20668
20966
|
throw new Error(
|
|
20669
20967
|
`Directory "${targetDir}" already exists and is not empty. Use --force to overwrite.`
|
|
20670
20968
|
);
|
|
20671
20969
|
}
|
|
20672
20970
|
}
|
|
20673
|
-
const templateDir =
|
|
20674
|
-
if (!
|
|
20971
|
+
const templateDir = path9.resolve(__dirname, "..", "templates", "astro-starlight");
|
|
20972
|
+
if (!fs8.existsSync(templateDir)) {
|
|
20675
20973
|
throw new Error(
|
|
20676
20974
|
`Template directory not found at ${templateDir}. Ensure the package is installed correctly.`
|
|
20677
20975
|
);
|
|
@@ -20680,24 +20978,24 @@ function initAstro(options = {}) {
|
|
|
20680
20978
|
return { targetDir };
|
|
20681
20979
|
}
|
|
20682
20980
|
function copyDirRecursive(src, dest) {
|
|
20683
|
-
|
|
20684
|
-
const entries =
|
|
20981
|
+
fs8.mkdirSync(dest, { recursive: true });
|
|
20982
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
20685
20983
|
for (const entry of entries) {
|
|
20686
|
-
const srcPath =
|
|
20687
|
-
const destPath =
|
|
20984
|
+
const srcPath = path9.join(src, entry.name);
|
|
20985
|
+
const destPath = path9.join(dest, entry.name);
|
|
20688
20986
|
if (entry.isDirectory()) {
|
|
20689
20987
|
copyDirRecursive(srcPath, destPath);
|
|
20690
20988
|
} else {
|
|
20691
|
-
|
|
20989
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
20692
20990
|
}
|
|
20693
20991
|
}
|
|
20694
20992
|
}
|
|
20695
20993
|
|
|
20696
20994
|
// src/config.ts
|
|
20697
20995
|
import { existsSync as existsSync7 } from "fs";
|
|
20698
|
-
import { resolve as
|
|
20996
|
+
import { resolve as resolve7 } from "path";
|
|
20699
20997
|
async function loadConfig(configPath) {
|
|
20700
|
-
const resolved = configPath ?
|
|
20998
|
+
const resolved = configPath ? resolve7(configPath) : resolve7(process.cwd(), "executable-stories.config.js");
|
|
20701
20999
|
if (!existsSync7(resolved)) return {};
|
|
20702
21000
|
const mod = await import(resolved);
|
|
20703
21001
|
const config = mod.default;
|
|
@@ -20734,6 +21032,7 @@ USAGE
|
|
|
20734
21032
|
|
|
20735
21033
|
SUBCOMMANDS
|
|
20736
21034
|
format Read raw test results and generate reports
|
|
21035
|
+
watch Regenerate reports whenever the raw-run file changes (live agent index)
|
|
20737
21036
|
compare Compare two runs and generate a diff report
|
|
20738
21037
|
review Generate an Evidence Review of AI-authored changes (correlate a run to the diff)
|
|
20739
21038
|
list List scenarios from a test run (text table or JSON)
|
|
@@ -20743,9 +21042,10 @@ SUBCOMMANDS
|
|
|
20743
21042
|
publish-jira Publish an ADF JSON file to a Jira issue (as comment or description)
|
|
20744
21043
|
|
|
20745
21044
|
OPTIONS
|
|
20746
|
-
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, story-report-json, or custom names from config (default: html)
|
|
21045
|
+
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, story-report-json, scenario-index-json, behavior-manifest-json, or custom names from config (default: html)
|
|
20747
21046
|
astro Themed Markdown (for Astro docs sites with matching CSS)
|
|
20748
21047
|
confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
|
|
21048
|
+
behavior-manifest-json Agent-readable behavior manifest and debugger warnings
|
|
20749
21049
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
20750
21050
|
cucumber-html Official Cucumber HTML report
|
|
20751
21051
|
markdown Markdown documentation
|
|
@@ -20753,6 +21053,7 @@ OPTIONS
|
|
|
20753
21053
|
cucumber-json Cucumber JSON
|
|
20754
21054
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
20755
21055
|
story-report-json StoryReport v1 JSON (consumed by executable-stories-react and other UI renderers)
|
|
21056
|
+
scenario-index-json Storybook-like scenario index for agents and explorers
|
|
20756
21057
|
--config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
|
|
20757
21058
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
20758
21059
|
--output-dir <dir> Output directory (default: reports)
|
|
@@ -20863,9 +21164,9 @@ async function parseCliArgs(argv) {
|
|
|
20863
21164
|
process.exit(EXIT_SUCCESS);
|
|
20864
21165
|
}
|
|
20865
21166
|
const subcommand = args[0];
|
|
20866
|
-
if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "review" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro" && subcommand !== "publish-confluence" && subcommand !== "publish-jira") {
|
|
21167
|
+
if (subcommand !== "format" && subcommand !== "watch" && subcommand !== "compare" && subcommand !== "review" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro" && subcommand !== "publish-confluence" && subcommand !== "publish-jira") {
|
|
20867
21168
|
console.error(
|
|
20868
|
-
`Unknown subcommand: "${subcommand}". Use "format", "compare", "review", "list", "validate", "init-astro", "publish-confluence", or "publish-jira".`
|
|
21169
|
+
`Unknown subcommand: "${subcommand}". Use "format", "watch", "compare", "review", "list", "validate", "init-astro", "publish-confluence", or "publish-jira".`
|
|
20869
21170
|
);
|
|
20870
21171
|
process.exit(EXIT_USAGE);
|
|
20871
21172
|
}
|
|
@@ -20903,6 +21204,9 @@ async function parseCliArgs(argv) {
|
|
|
20903
21204
|
console.log("");
|
|
20904
21205
|
console.log("Generate story docs with:");
|
|
20905
21206
|
console.log(` executable-stories format run.json --format astro --output-dir ${result.targetDir}/src/content/docs/stories --asset-mode copy`);
|
|
21207
|
+
console.log("");
|
|
21208
|
+
console.log("Generate the Storybook-like scenario explorer data with:");
|
|
21209
|
+
console.log(` executable-stories format run.json --format story-report-json --output-dir ${result.targetDir}/public/stories --output-name story-report`);
|
|
20906
21210
|
process.exit(EXIT_SUCCESS);
|
|
20907
21211
|
} catch (err) {
|
|
20908
21212
|
console.error(`Error: ${err.message}`);
|
|
@@ -21004,7 +21308,7 @@ async function parseCliArgs(argv) {
|
|
|
21004
21308
|
}
|
|
21005
21309
|
const pluginConfig = await loadConfig(values["config"]);
|
|
21006
21310
|
const customFormatterNames = new Set(Object.keys(pluginConfig.formatters ?? {}));
|
|
21007
|
-
const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html", "story-report-json"]);
|
|
21311
|
+
const builtInFormats = /* @__PURE__ */ new Set(["astro", "behavior-manifest-json", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html", "scenario-index-json", "story-report-json"]);
|
|
21008
21312
|
const formatStr = values.format;
|
|
21009
21313
|
const allRequestedFormats = formatStr.split(",").map((f) => f.trim());
|
|
21010
21314
|
const builtInRequested = allRequestedFormats.filter((f) => builtInFormats.has(f));
|
|
@@ -21012,7 +21316,7 @@ async function parseCliArgs(argv) {
|
|
|
21012
21316
|
const unknownFormats = allRequestedFormats.filter((f) => !builtInFormats.has(f) && !customFormatterNames.has(f));
|
|
21013
21317
|
if (unknownFormats.length > 0) {
|
|
21014
21318
|
const knownCustom = customFormatterNames.size > 0 ? `, ${[...customFormatterNames].join(", ")}` : "";
|
|
21015
|
-
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, story-report-json${knownCustom}.`);
|
|
21319
|
+
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, behavior-manifest-json, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, scenario-index-json, story-report-json${knownCustom}.`);
|
|
21016
21320
|
process.exit(EXIT_USAGE);
|
|
21017
21321
|
}
|
|
21018
21322
|
const formats = builtInRequested;
|
|
@@ -21164,27 +21468,27 @@ async function readInput(args) {
|
|
|
21164
21468
|
if (args.stdin) {
|
|
21165
21469
|
return readStdin();
|
|
21166
21470
|
}
|
|
21167
|
-
const filePath =
|
|
21168
|
-
if (!
|
|
21471
|
+
const filePath = path10.resolve(args.inputFile);
|
|
21472
|
+
if (!fs9.existsSync(filePath)) {
|
|
21169
21473
|
console.error(`Error: File not found: ${filePath}`);
|
|
21170
21474
|
process.exit(EXIT_USAGE);
|
|
21171
21475
|
}
|
|
21172
|
-
return
|
|
21476
|
+
return fs9.readFileSync(filePath, "utf8");
|
|
21173
21477
|
}
|
|
21174
21478
|
function readFileInput(filePath) {
|
|
21175
|
-
const resolved =
|
|
21176
|
-
if (!
|
|
21479
|
+
const resolved = path10.resolve(filePath);
|
|
21480
|
+
if (!fs9.existsSync(resolved)) {
|
|
21177
21481
|
console.error(`Error: File not found: ${resolved}`);
|
|
21178
21482
|
process.exit(EXIT_USAGE);
|
|
21179
21483
|
}
|
|
21180
|
-
return
|
|
21484
|
+
return fs9.readFileSync(resolved, "utf8");
|
|
21181
21485
|
}
|
|
21182
21486
|
function readStdin() {
|
|
21183
|
-
return new Promise((
|
|
21487
|
+
return new Promise((resolve9, reject) => {
|
|
21184
21488
|
const chunks = [];
|
|
21185
21489
|
process.stdin.setEncoding("utf8");
|
|
21186
21490
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
21187
|
-
process.stdin.on("end", () =>
|
|
21491
|
+
process.stdin.on("end", () => resolve9(chunks.join("")));
|
|
21188
21492
|
process.stdin.on("error", reject);
|
|
21189
21493
|
});
|
|
21190
21494
|
}
|
|
@@ -21310,14 +21614,14 @@ function tryNormalizeRunFromText(text2, args) {
|
|
|
21310
21614
|
}
|
|
21311
21615
|
}
|
|
21312
21616
|
function listBaselineCandidates(currentFile, args) {
|
|
21313
|
-
const baselineDir =
|
|
21314
|
-
const currentResolved =
|
|
21315
|
-
if (!
|
|
21617
|
+
const baselineDir = path10.resolve(args.baselineDir ?? path10.dirname(currentFile));
|
|
21618
|
+
const currentResolved = path10.resolve(currentFile);
|
|
21619
|
+
if (!fs9.existsSync(baselineDir)) {
|
|
21316
21620
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
21317
21621
|
process.exit(EXIT_USAGE);
|
|
21318
21622
|
}
|
|
21319
|
-
const entries =
|
|
21320
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
21623
|
+
const entries = fs9.readdirSync(baselineDir, { withFileTypes: true });
|
|
21624
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path10.join(baselineDir, entry.name)).filter((candidate) => path10.resolve(candidate) !== currentResolved).filter(
|
|
21321
21625
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
21322
21626
|
);
|
|
21323
21627
|
}
|
|
@@ -21325,14 +21629,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
21325
21629
|
const candidates = listBaselineCandidates(currentFile, args);
|
|
21326
21630
|
const comparable = [];
|
|
21327
21631
|
for (const candidate of candidates) {
|
|
21328
|
-
const run = tryNormalizeRunFromText(
|
|
21632
|
+
const run = tryNormalizeRunFromText(fs9.readFileSync(candidate, "utf8"), args);
|
|
21329
21633
|
if (run) {
|
|
21330
21634
|
comparable.push({ file: candidate, run });
|
|
21331
21635
|
}
|
|
21332
21636
|
}
|
|
21333
21637
|
if (comparable.length === 0) {
|
|
21334
21638
|
console.error(
|
|
21335
|
-
`Error: no compatible baseline files found in ${
|
|
21639
|
+
`Error: no compatible baseline files found in ${path10.resolve(args.baselineDir ?? path10.dirname(currentFile))}.`
|
|
21336
21640
|
);
|
|
21337
21641
|
process.exit(EXIT_USAGE);
|
|
21338
21642
|
}
|
|
@@ -21409,6 +21713,24 @@ async function main() {
|
|
|
21409
21713
|
console.log(output);
|
|
21410
21714
|
process.exit(EXIT_SUCCESS);
|
|
21411
21715
|
}
|
|
21716
|
+
if (args.subcommand === "watch") {
|
|
21717
|
+
if (!args.inputFile) {
|
|
21718
|
+
console.error("Error: watch requires an input file (the raw-run JSON the framework writes).");
|
|
21719
|
+
process.exit(EXIT_USAGE);
|
|
21720
|
+
}
|
|
21721
|
+
console.log(
|
|
21722
|
+
`Watching ${args.inputFile} \u2192 regenerating [${args.formats.join(", ")}] into ${args.outputDir}/ (Ctrl+C to stop)`
|
|
21723
|
+
);
|
|
21724
|
+
startWatch({
|
|
21725
|
+
input: args.inputFile,
|
|
21726
|
+
outputDir: args.outputDir,
|
|
21727
|
+
outputName: args.outputName,
|
|
21728
|
+
formats: args.formats,
|
|
21729
|
+
inputType: args.inputType === "canonical" ? "canonical" : "raw",
|
|
21730
|
+
synthesize: args.synthesizeStories
|
|
21731
|
+
});
|
|
21732
|
+
return;
|
|
21733
|
+
}
|
|
21412
21734
|
const text2 = await readInput(args);
|
|
21413
21735
|
if (args.inputType === "ndjson") {
|
|
21414
21736
|
if (args.subcommand === "validate") {
|
|
@@ -21452,9 +21774,9 @@ async function main() {
|
|
|
21452
21774
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
21453
21775
|
}
|
|
21454
21776
|
if (args.emitCanonical) {
|
|
21455
|
-
const outPath =
|
|
21456
|
-
|
|
21457
|
-
|
|
21777
|
+
const outPath = path10.resolve(args.emitCanonical);
|
|
21778
|
+
fs9.mkdirSync(path10.dirname(outPath), { recursive: true });
|
|
21779
|
+
fs9.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
21458
21780
|
}
|
|
21459
21781
|
try {
|
|
21460
21782
|
const result = await generateReports(run, args);
|
|
@@ -21511,9 +21833,9 @@ ${msg}`);
|
|
|
21511
21833
|
}
|
|
21512
21834
|
const run = data;
|
|
21513
21835
|
if (args.emitCanonical) {
|
|
21514
|
-
const outPath =
|
|
21515
|
-
|
|
21516
|
-
|
|
21836
|
+
const outPath = path10.resolve(args.emitCanonical);
|
|
21837
|
+
fs9.mkdirSync(path10.dirname(outPath), { recursive: true });
|
|
21838
|
+
fs9.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
21517
21839
|
}
|
|
21518
21840
|
try {
|
|
21519
21841
|
const result = await generateReports(run, args);
|
|
@@ -21569,9 +21891,9 @@ ${msg}`);
|
|
|
21569
21891
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
21570
21892
|
}
|
|
21571
21893
|
if (args.emitCanonical) {
|
|
21572
|
-
const outPath =
|
|
21573
|
-
|
|
21574
|
-
|
|
21894
|
+
const outPath = path10.resolve(args.emitCanonical);
|
|
21895
|
+
fs9.mkdirSync(path10.dirname(outPath), { recursive: true });
|
|
21896
|
+
fs9.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
21575
21897
|
}
|
|
21576
21898
|
try {
|
|
21577
21899
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
@@ -21596,9 +21918,9 @@ function runCustomFormatters(run, customRequested, formatters, args) {
|
|
|
21596
21918
|
const ext = formatter.fileExtension ?? formatName;
|
|
21597
21919
|
const baseName = args.outputName ?? "report";
|
|
21598
21920
|
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
21599
|
-
const filepath =
|
|
21600
|
-
|
|
21601
|
-
|
|
21921
|
+
const filepath = path10.join(outputDir, filename);
|
|
21922
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
21923
|
+
fs9.writeFileSync(filepath, content, "utf8");
|
|
21602
21924
|
console.log(`Generated: ${filepath}`);
|
|
21603
21925
|
} catch (err) {
|
|
21604
21926
|
console.error(`Error running custom formatter "${formatName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -21648,13 +21970,13 @@ async function dispatchNotifications(run, args) {
|
|
|
21648
21970
|
}
|
|
21649
21971
|
function runHistoryPipeline(run, args) {
|
|
21650
21972
|
if (!args.historyFile) return;
|
|
21651
|
-
const historyPath =
|
|
21973
|
+
const historyPath = path10.resolve(args.historyFile);
|
|
21652
21974
|
const store = loadHistory(
|
|
21653
21975
|
{ filePath: historyPath },
|
|
21654
21976
|
{
|
|
21655
21977
|
readFile: (p) => {
|
|
21656
21978
|
try {
|
|
21657
|
-
return
|
|
21979
|
+
return fs9.readFileSync(p, "utf8");
|
|
21658
21980
|
} catch {
|
|
21659
21981
|
return void 0;
|
|
21660
21982
|
}
|
|
@@ -21667,11 +21989,11 @@ function runHistoryPipeline(run, args) {
|
|
|
21667
21989
|
run,
|
|
21668
21990
|
maxRuns: args.maxHistoryRuns
|
|
21669
21991
|
});
|
|
21670
|
-
const dir =
|
|
21671
|
-
|
|
21992
|
+
const dir = path10.dirname(historyPath);
|
|
21993
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
21672
21994
|
saveHistory(
|
|
21673
21995
|
{ filePath: historyPath, store: updated },
|
|
21674
|
-
{ writeFile: (p, content) =>
|
|
21996
|
+
{ writeFile: (p, content) => fs9.writeFileSync(p, content, "utf8") }
|
|
21675
21997
|
);
|
|
21676
21998
|
let metricsCount = 0;
|
|
21677
21999
|
for (const testId of Object.keys(updated.tests)) {
|
|
@@ -21818,11 +22140,11 @@ function writeReviewReport(review, args) {
|
|
|
21818
22140
|
const outputDir = args.outputDir ?? "reports";
|
|
21819
22141
|
const baseName = args.outputName ?? "evidence-review";
|
|
21820
22142
|
const suffix = args.outputNameTimestamp ? `-${Math.floor(review.run.startedAtMs / 1e3)}` : "";
|
|
21821
|
-
|
|
21822
|
-
const mdPath =
|
|
21823
|
-
const htmlPath =
|
|
21824
|
-
|
|
21825
|
-
|
|
22143
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
22144
|
+
const mdPath = path10.join(outputDir, `${baseName}${suffix}.md`);
|
|
22145
|
+
const htmlPath = path10.join(outputDir, `${baseName}${suffix}.html`);
|
|
22146
|
+
fs9.writeFileSync(mdPath, markdown, "utf8");
|
|
22147
|
+
fs9.writeFileSync(htmlPath, html, "utf8");
|
|
21826
22148
|
return [mdPath, htmlPath];
|
|
21827
22149
|
}
|
|
21828
22150
|
function evaluateReviewGate(review, args) {
|
|
@@ -21868,9 +22190,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
21868
22190
|
function printCompareResult(result, args, startMs) {
|
|
21869
22191
|
const durationMs = Date.now() - startMs;
|
|
21870
22192
|
if (result.prSummary && args.prSummaryFile) {
|
|
21871
|
-
const outputPath =
|
|
21872
|
-
|
|
21873
|
-
|
|
22193
|
+
const outputPath = path10.resolve(args.prSummaryFile);
|
|
22194
|
+
fs9.mkdirSync(path10.dirname(outputPath), { recursive: true });
|
|
22195
|
+
fs9.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
21874
22196
|
}
|
|
21875
22197
|
if (args.jsonSummary) {
|
|
21876
22198
|
console.log(
|
|
@@ -21961,7 +22283,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
21961
22283
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
21962
22284
|
process.exit(EXIT_USAGE);
|
|
21963
22285
|
}
|
|
21964
|
-
if (!
|
|
22286
|
+
if (!fs9.existsSync(inputFile)) {
|
|
21965
22287
|
console.error(`Error: file not found: ${inputFile}`);
|
|
21966
22288
|
process.exit(EXIT_USAGE);
|
|
21967
22289
|
}
|
|
@@ -21989,7 +22311,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
21989
22311
|
console.error("Error: --title is required when creating a new page");
|
|
21990
22312
|
process.exit(EXIT_USAGE);
|
|
21991
22313
|
}
|
|
21992
|
-
const adf =
|
|
22314
|
+
const adf = fs9.readFileSync(path10.resolve(inputFile), "utf8");
|
|
21993
22315
|
if (dryRun) {
|
|
21994
22316
|
console.log(
|
|
21995
22317
|
JSON.stringify(
|
|
@@ -22068,7 +22390,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
22068
22390
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
22069
22391
|
process.exit(EXIT_USAGE);
|
|
22070
22392
|
}
|
|
22071
|
-
if (!
|
|
22393
|
+
if (!fs9.existsSync(inputFile)) {
|
|
22072
22394
|
console.error(`Error: file not found: ${inputFile}`);
|
|
22073
22395
|
process.exit(EXIT_USAGE);
|
|
22074
22396
|
}
|
|
@@ -22095,7 +22417,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
22095
22417
|
process.exit(EXIT_USAGE);
|
|
22096
22418
|
}
|
|
22097
22419
|
const mode = modeRaw;
|
|
22098
|
-
const adf =
|
|
22420
|
+
const adf = fs9.readFileSync(path10.resolve(inputFile), "utf8");
|
|
22099
22421
|
if (dryRun) {
|
|
22100
22422
|
console.log(
|
|
22101
22423
|
JSON.stringify(
|