executable-stories-formatters 0.15.0 → 0.16.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/cli.js +238 -64
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +197 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +197 -55
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/schemas/README.md +1 -1
- package/templates/astro-starlight/astro.config.mjs +57 -0
- package/templates/astro-starlight/gitignore +14 -0
- package/templates/astro-starlight/package.json +20 -0
- package/templates/astro-starlight/public/stories/assets/.gitkeep +0 -0
- package/templates/astro-starlight/public/stories/notes-index.json +4 -0
- package/templates/astro-starlight/public/stories/story-report.json +17 -0
- package/templates/astro-starlight/src/components/ApiOperations.astro +366 -0
- package/templates/astro-starlight/src/components/Checklist.astro +15 -0
- package/templates/astro-starlight/src/components/HealthDashboard.astro +171 -0
- package/templates/astro-starlight/src/components/PageTitle.astro +53 -0
- package/templates/astro-starlight/src/components/VerifiedBy.astro +281 -0
- package/templates/astro-starlight/src/components/VerifiedStep.astro +91 -0
- package/templates/astro-starlight/src/content/docs/examples/example-adr.mdx +45 -0
- package/templates/astro-starlight/src/content/docs/guides/behavior-portal.mdx +41 -0
- package/templates/astro-starlight/src/content/docs/guides/writing-docs.mdx +49 -0
- package/templates/astro-starlight/src/content/docs/index.mdx +49 -0
- package/templates/astro-starlight/src/content/docs/stories/.gitkeep +0 -0
- package/templates/astro-starlight/src/content.config.ts +18 -0
- package/templates/astro-starlight/src/lib/config.ts +50 -0
- package/templates/astro-starlight/src/lib/render-doc-entry.ts +154 -0
- package/templates/astro-starlight/src/lib/report-health.ts +61 -0
- package/templates/astro-starlight/src/lib/verification.ts +247 -0
- package/templates/astro-starlight/src/pages/explorer/explorer.css +729 -0
- package/templates/astro-starlight/src/pages/explorer/index.astro +404 -0
- package/templates/astro-starlight/src/styles/global.css +293 -0
- package/templates/astro-starlight/src/styles/themes/corporate.css +83 -0
- package/templates/astro-starlight/src/styles/themes/dashboard.css +76 -0
- package/templates/astro-starlight/src/styles/themes/default.css +86 -0
- package/templates/astro-starlight/src/styles/themes/minimal.css +87 -0
- package/templates/astro-starlight/src/styles/themes/playful.css +77 -0
- package/templates/astro-starlight/src/styles/themes/terminal.css +77 -0
- package/templates/astro-starlight/tsconfig.json +13 -0
package/dist/cli.js
CHANGED
|
@@ -702,6 +702,41 @@ function generateFeatureId(uri) {
|
|
|
702
702
|
function generateScenarioId(featureId, scenarioName) {
|
|
703
703
|
return `${featureId};${slugify(scenarioName)}`;
|
|
704
704
|
}
|
|
705
|
+
function normalizeText(text2) {
|
|
706
|
+
return text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
707
|
+
}
|
|
708
|
+
function tokenize(text2) {
|
|
709
|
+
const normalized = normalizeText(text2);
|
|
710
|
+
return normalized.length === 0 ? [] : normalized.split(" ");
|
|
711
|
+
}
|
|
712
|
+
function behaviourFingerprint(input) {
|
|
713
|
+
const steps = input.steps.map((step) => `${step.keyword.toLowerCase()}:${normalizeText(step.text)}`).join("\n");
|
|
714
|
+
const covers = [...input.covers ?? []].map((path18) => path18.trim()).filter(Boolean).sort().join(",");
|
|
715
|
+
if (steps.length === 0 && covers.length === 0) return "";
|
|
716
|
+
return createHash("sha1").update(`${steps}\0${covers}`).digest("hex").slice(0, 16);
|
|
717
|
+
}
|
|
718
|
+
function jaccard(a, b) {
|
|
719
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
720
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
721
|
+
let intersection = 0;
|
|
722
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
723
|
+
return intersection / (a.size + b.size - intersection);
|
|
724
|
+
}
|
|
725
|
+
function stepTokens(steps) {
|
|
726
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
727
|
+
for (const step of steps) {
|
|
728
|
+
for (const token of tokenize(step.text)) tokens.add(token);
|
|
729
|
+
}
|
|
730
|
+
return tokens;
|
|
731
|
+
}
|
|
732
|
+
function behaviourSimilarity(a, b) {
|
|
733
|
+
const aSteps = stepTokens(a.steps);
|
|
734
|
+
const bSteps = stepTokens(b.steps);
|
|
735
|
+
if (aSteps.size === 0 || bSteps.size === 0) return 0;
|
|
736
|
+
const stepScore = jaccard(aSteps, bSteps);
|
|
737
|
+
const titleScore = jaccard(new Set(tokenize(a.scenario)), new Set(tokenize(b.scenario)));
|
|
738
|
+
return stepScore * 0.7 + titleScore * 0.3;
|
|
739
|
+
}
|
|
705
740
|
|
|
706
741
|
// src/converters/acl/steps.ts
|
|
707
742
|
function deriveStepResults(steps, scenarioStatus, error) {
|
|
@@ -17401,6 +17436,10 @@ function titleFor(kind) {
|
|
|
17401
17436
|
return "Added";
|
|
17402
17437
|
case "removed":
|
|
17403
17438
|
return "Removed";
|
|
17439
|
+
case "renamed":
|
|
17440
|
+
return "Renamed";
|
|
17441
|
+
case "moved":
|
|
17442
|
+
return "Moved";
|
|
17404
17443
|
case "changed":
|
|
17405
17444
|
return "Changed";
|
|
17406
17445
|
default:
|
|
@@ -17449,6 +17488,8 @@ function createPrCommentSummary(diff, maxScenarios = 10) {
|
|
|
17449
17488
|
addSection(lines, diff, "fixed", maxScenarios);
|
|
17450
17489
|
addSection(lines, diff, "added", maxScenarios);
|
|
17451
17490
|
addSection(lines, diff, "removed", maxScenarios);
|
|
17491
|
+
addSection(lines, diff, "renamed", maxScenarios);
|
|
17492
|
+
addSection(lines, diff, "moved", maxScenarios);
|
|
17452
17493
|
addSection(lines, diff, "changed", maxScenarios);
|
|
17453
17494
|
return lines.join("\n").trimEnd();
|
|
17454
17495
|
}
|
|
@@ -17525,8 +17566,10 @@ function sortDiffs(scenarios) {
|
|
|
17525
17566
|
fixed: 1,
|
|
17526
17567
|
added: 2,
|
|
17527
17568
|
removed: 3,
|
|
17528
|
-
|
|
17529
|
-
|
|
17569
|
+
renamed: 4,
|
|
17570
|
+
moved: 5,
|
|
17571
|
+
changed: 6,
|
|
17572
|
+
unchanged: 7
|
|
17530
17573
|
};
|
|
17531
17574
|
return [...scenarios].sort((a, b) => {
|
|
17532
17575
|
if (rank[a.kind] !== rank[b.kind]) {
|
|
@@ -17541,69 +17584,108 @@ function sortDiffs(scenarios) {
|
|
|
17541
17584
|
return a.scenario.localeCompare(b.scenario);
|
|
17542
17585
|
});
|
|
17543
17586
|
}
|
|
17587
|
+
var SIMILARITY_THRESHOLD = 0.75;
|
|
17588
|
+
function identityInput(tc) {
|
|
17589
|
+
return {
|
|
17590
|
+
scenario: tc.story.scenario,
|
|
17591
|
+
sourceFile: tc.sourceFile,
|
|
17592
|
+
steps: tc.story.steps,
|
|
17593
|
+
covers: tc.story.covers
|
|
17594
|
+
};
|
|
17595
|
+
}
|
|
17596
|
+
function changedFieldsOf(flags) {
|
|
17597
|
+
return Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
|
|
17598
|
+
}
|
|
17599
|
+
function allChangedFlags(errorMessage) {
|
|
17600
|
+
return {
|
|
17601
|
+
status: true,
|
|
17602
|
+
steps: true,
|
|
17603
|
+
docs: true,
|
|
17604
|
+
tags: true,
|
|
17605
|
+
tickets: true,
|
|
17606
|
+
source: true,
|
|
17607
|
+
duration: true,
|
|
17608
|
+
attachments: true,
|
|
17609
|
+
error: Boolean(errorMessage),
|
|
17610
|
+
titlePath: true
|
|
17611
|
+
};
|
|
17612
|
+
}
|
|
17613
|
+
function matchIdentities(removed, added) {
|
|
17614
|
+
const pairs = [];
|
|
17615
|
+
const remainingRemoved = new Set(removed);
|
|
17616
|
+
const remainingAdded = new Set(added);
|
|
17617
|
+
const removedByFp = /* @__PURE__ */ new Map();
|
|
17618
|
+
const addedByFp = /* @__PURE__ */ new Map();
|
|
17619
|
+
for (const tc of remainingRemoved) {
|
|
17620
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17621
|
+
if (fp === "") continue;
|
|
17622
|
+
(removedByFp.get(fp) ?? removedByFp.set(fp, []).get(fp)).push(tc);
|
|
17623
|
+
}
|
|
17624
|
+
for (const tc of remainingAdded) {
|
|
17625
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17626
|
+
if (fp === "") continue;
|
|
17627
|
+
(addedByFp.get(fp) ?? addedByFp.set(fp, []).get(fp)).push(tc);
|
|
17628
|
+
}
|
|
17629
|
+
for (const [fp, removedGroup] of removedByFp) {
|
|
17630
|
+
const addedGroup = addedByFp.get(fp);
|
|
17631
|
+
if (removedGroup.length === 1 && addedGroup && addedGroup.length === 1) {
|
|
17632
|
+
pairs.push({ before: removedGroup[0], after: addedGroup[0], confidence: 1, matchedBy: "fingerprint" });
|
|
17633
|
+
remainingRemoved.delete(removedGroup[0]);
|
|
17634
|
+
remainingAdded.delete(addedGroup[0]);
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
for (const before of [...remainingRemoved]) {
|
|
17638
|
+
let best;
|
|
17639
|
+
let bestScore = 0;
|
|
17640
|
+
let tied = false;
|
|
17641
|
+
for (const after of remainingAdded) {
|
|
17642
|
+
const score = behaviourSimilarity(identityInput(before), identityInput(after));
|
|
17643
|
+
if (score > bestScore) {
|
|
17644
|
+
bestScore = score;
|
|
17645
|
+
best = after;
|
|
17646
|
+
tied = false;
|
|
17647
|
+
} else if (score === bestScore) {
|
|
17648
|
+
tied = true;
|
|
17649
|
+
}
|
|
17650
|
+
}
|
|
17651
|
+
if (best && !tied && bestScore >= SIMILARITY_THRESHOLD) {
|
|
17652
|
+
pairs.push({ before, after: best, confidence: Math.round(bestScore * 100) / 100, matchedBy: "similarity" });
|
|
17653
|
+
remainingRemoved.delete(before);
|
|
17654
|
+
remainingAdded.delete(best);
|
|
17655
|
+
}
|
|
17656
|
+
}
|
|
17657
|
+
return {
|
|
17658
|
+
pairs,
|
|
17659
|
+
unmatchedRemoved: [...remainingRemoved],
|
|
17660
|
+
unmatchedAdded: [...remainingAdded]
|
|
17661
|
+
};
|
|
17662
|
+
}
|
|
17663
|
+
function identityKind(before, after) {
|
|
17664
|
+
const titleChanged = before.story.scenario !== after.story.scenario;
|
|
17665
|
+
const fileChanged = before.sourceFile !== after.sourceFile;
|
|
17666
|
+
return fileChanged && !titleChanged ? "moved" : "renamed";
|
|
17667
|
+
}
|
|
17544
17668
|
function diffRuns(baseline, current) {
|
|
17545
17669
|
const baselineById = new Map(baseline.testCases.map((tc) => [tc.id, tc]));
|
|
17546
17670
|
const currentById = new Map(current.testCases.map((tc) => [tc.id, tc]));
|
|
17547
17671
|
const ids = /* @__PURE__ */ new Set([...baselineById.keys(), ...currentById.keys()]);
|
|
17548
17672
|
const scenarios = [];
|
|
17673
|
+
const removedCases = [];
|
|
17674
|
+
const addedCases = [];
|
|
17549
17675
|
for (const id of ids) {
|
|
17550
17676
|
const before = baselineById.get(id);
|
|
17551
17677
|
const after = currentById.get(id);
|
|
17552
17678
|
if (!before && after) {
|
|
17553
|
-
|
|
17554
|
-
status: true,
|
|
17555
|
-
steps: true,
|
|
17556
|
-
docs: true,
|
|
17557
|
-
tags: true,
|
|
17558
|
-
tickets: true,
|
|
17559
|
-
source: true,
|
|
17560
|
-
duration: true,
|
|
17561
|
-
attachments: true,
|
|
17562
|
-
error: Boolean(after.errorMessage),
|
|
17563
|
-
titlePath: true
|
|
17564
|
-
};
|
|
17565
|
-
scenarios.push({
|
|
17566
|
-
kind: "added",
|
|
17567
|
-
id,
|
|
17568
|
-
scenario: after.story.scenario,
|
|
17569
|
-
sourceFile: after.sourceFile,
|
|
17570
|
-
sourceLine: after.sourceLine,
|
|
17571
|
-
current: toScenarioSnapshot(after),
|
|
17572
|
-
flags: flags2,
|
|
17573
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
17574
|
-
});
|
|
17679
|
+
addedCases.push(after);
|
|
17575
17680
|
continue;
|
|
17576
17681
|
}
|
|
17577
17682
|
if (before && !after) {
|
|
17578
|
-
|
|
17579
|
-
status: true,
|
|
17580
|
-
steps: true,
|
|
17581
|
-
docs: true,
|
|
17582
|
-
tags: true,
|
|
17583
|
-
tickets: true,
|
|
17584
|
-
source: true,
|
|
17585
|
-
duration: true,
|
|
17586
|
-
attachments: true,
|
|
17587
|
-
error: Boolean(before.errorMessage),
|
|
17588
|
-
titlePath: true
|
|
17589
|
-
};
|
|
17590
|
-
scenarios.push({
|
|
17591
|
-
kind: "removed",
|
|
17592
|
-
id,
|
|
17593
|
-
scenario: before.story.scenario,
|
|
17594
|
-
sourceFile: before.sourceFile,
|
|
17595
|
-
sourceLine: before.sourceLine,
|
|
17596
|
-
baseline: toScenarioSnapshot(before),
|
|
17597
|
-
flags: flags2,
|
|
17598
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
17599
|
-
});
|
|
17600
|
-
continue;
|
|
17601
|
-
}
|
|
17602
|
-
if (!before || !after) {
|
|
17683
|
+
removedCases.push(before);
|
|
17603
17684
|
continue;
|
|
17604
17685
|
}
|
|
17686
|
+
if (!before || !after) continue;
|
|
17605
17687
|
const flags = buildFlags(before, after);
|
|
17606
|
-
const changedFields =
|
|
17688
|
+
const changedFields = changedFieldsOf(flags);
|
|
17607
17689
|
const kind = getPrimaryKind(before, after, changedFields.length > 0);
|
|
17608
17690
|
scenarios.push({
|
|
17609
17691
|
kind,
|
|
@@ -17618,12 +17700,59 @@ function diffRuns(baseline, current) {
|
|
|
17618
17700
|
durationDeltaMs: after.durationMs - before.durationMs
|
|
17619
17701
|
});
|
|
17620
17702
|
}
|
|
17703
|
+
const { pairs, unmatchedRemoved, unmatchedAdded } = matchIdentities(removedCases, addedCases);
|
|
17704
|
+
for (const { before, after, confidence, matchedBy } of pairs) {
|
|
17705
|
+
const flags = buildFlags(before, after);
|
|
17706
|
+
scenarios.push({
|
|
17707
|
+
kind: identityKind(before, after),
|
|
17708
|
+
id: after.id,
|
|
17709
|
+
previousId: before.id,
|
|
17710
|
+
scenario: after.story.scenario,
|
|
17711
|
+
sourceFile: after.sourceFile,
|
|
17712
|
+
sourceLine: after.sourceLine,
|
|
17713
|
+
baseline: toScenarioSnapshot(before),
|
|
17714
|
+
current: toScenarioSnapshot(after),
|
|
17715
|
+
flags,
|
|
17716
|
+
changedFields: changedFieldsOf(flags),
|
|
17717
|
+
durationDeltaMs: after.durationMs - before.durationMs,
|
|
17718
|
+
matchConfidence: confidence,
|
|
17719
|
+
matchedBy
|
|
17720
|
+
});
|
|
17721
|
+
}
|
|
17722
|
+
for (const after of unmatchedAdded) {
|
|
17723
|
+
const flags = allChangedFlags(after.errorMessage);
|
|
17724
|
+
scenarios.push({
|
|
17725
|
+
kind: "added",
|
|
17726
|
+
id: after.id,
|
|
17727
|
+
scenario: after.story.scenario,
|
|
17728
|
+
sourceFile: after.sourceFile,
|
|
17729
|
+
sourceLine: after.sourceLine,
|
|
17730
|
+
current: toScenarioSnapshot(after),
|
|
17731
|
+
flags,
|
|
17732
|
+
changedFields: changedFieldsOf(flags)
|
|
17733
|
+
});
|
|
17734
|
+
}
|
|
17735
|
+
for (const before of unmatchedRemoved) {
|
|
17736
|
+
const flags = allChangedFlags(before.errorMessage);
|
|
17737
|
+
scenarios.push({
|
|
17738
|
+
kind: "removed",
|
|
17739
|
+
id: before.id,
|
|
17740
|
+
scenario: before.story.scenario,
|
|
17741
|
+
sourceFile: before.sourceFile,
|
|
17742
|
+
sourceLine: before.sourceLine,
|
|
17743
|
+
baseline: toScenarioSnapshot(before),
|
|
17744
|
+
flags,
|
|
17745
|
+
changedFields: changedFieldsOf(flags)
|
|
17746
|
+
});
|
|
17747
|
+
}
|
|
17621
17748
|
const sorted = sortDiffs(scenarios);
|
|
17622
17749
|
const summary = {
|
|
17623
17750
|
totalBaseline: baseline.testCases.length,
|
|
17624
17751
|
totalCurrent: current.testCases.length,
|
|
17625
17752
|
added: sorted.filter((s) => s.kind === "added").length,
|
|
17626
17753
|
removed: sorted.filter((s) => s.kind === "removed").length,
|
|
17754
|
+
renamed: sorted.filter((s) => s.kind === "renamed").length,
|
|
17755
|
+
moved: sorted.filter((s) => s.kind === "moved").length,
|
|
17627
17756
|
changed: sorted.filter((s) => s.kind === "changed").length,
|
|
17628
17757
|
regressed: sorted.filter((s) => s.kind === "regressed").length,
|
|
17629
17758
|
fixed: sorted.filter((s) => s.kind === "fixed").length,
|
|
@@ -17651,6 +17780,10 @@ function statusLabel(kind) {
|
|
|
17651
17780
|
return "Added";
|
|
17652
17781
|
case "removed":
|
|
17653
17782
|
return "Removed";
|
|
17783
|
+
case "renamed":
|
|
17784
|
+
return "Renamed";
|
|
17785
|
+
case "moved":
|
|
17786
|
+
return "Moved";
|
|
17654
17787
|
case "changed":
|
|
17655
17788
|
return "Changed";
|
|
17656
17789
|
default:
|
|
@@ -17976,6 +18109,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
|
|
|
17976
18109
|
<div class="summary-card"><strong>${diff.summary.fixed}</strong><span>Fixed</span></div>
|
|
17977
18110
|
<div class="summary-card"><strong>${diff.summary.added}</strong><span>Added</span></div>
|
|
17978
18111
|
<div class="summary-card"><strong>${diff.summary.removed}</strong><span>Removed</span></div>
|
|
18112
|
+
<div class="summary-card"><strong>${diff.summary.renamed}</strong><span>Renamed</span></div>
|
|
18113
|
+
<div class="summary-card"><strong>${diff.summary.moved}</strong><span>Moved</span></div>
|
|
17979
18114
|
<div class="summary-card"><strong>${diff.summary.changed}</strong><span>Changed</span></div>
|
|
17980
18115
|
<div class="summary-card"><strong>${diff.summary.unchanged}</strong><span>Unchanged</span></div>
|
|
17981
18116
|
</section>
|
|
@@ -18036,6 +18171,10 @@ function formatStatus(kind) {
|
|
|
18036
18171
|
return "Added";
|
|
18037
18172
|
case "removed":
|
|
18038
18173
|
return "Removed";
|
|
18174
|
+
case "renamed":
|
|
18175
|
+
return "Renamed";
|
|
18176
|
+
case "moved":
|
|
18177
|
+
return "Moved";
|
|
18039
18178
|
case "changed":
|
|
18040
18179
|
return "Changed";
|
|
18041
18180
|
default:
|
|
@@ -18188,13 +18327,13 @@ var RunDiffMarkdownFormatter = class {
|
|
|
18188
18327
|
lines.push("No regressions or fixes detected. Remaining changes are neutral.");
|
|
18189
18328
|
}
|
|
18190
18329
|
lines.push("");
|
|
18191
|
-
lines.push("| Added | Removed | Regressed | Fixed | Changed | Unchanged |");
|
|
18192
|
-
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
18330
|
+
lines.push("| Added | Removed | Renamed | Moved | Regressed | Fixed | Changed | Unchanged |");
|
|
18331
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
18193
18332
|
lines.push(
|
|
18194
|
-
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
18333
|
+
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.renamed} | ${diff.summary.moved} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
18195
18334
|
);
|
|
18196
18335
|
lines.push("");
|
|
18197
|
-
for (const kind of ["regressed", "fixed", "added", "removed", "changed"]) {
|
|
18336
|
+
for (const kind of ["regressed", "fixed", "added", "removed", "renamed", "moved", "changed"]) {
|
|
18198
18337
|
const scenarios = diff.scenarios.filter((scenario) => scenario.kind === kind);
|
|
18199
18338
|
if (scenarios.length === 0) continue;
|
|
18200
18339
|
lines.push(`## ${formatStatus(kind)} (${scenarios.length})`);
|
|
@@ -19143,7 +19282,10 @@ function diffStoryReports(baseline, current) {
|
|
|
19143
19282
|
};
|
|
19144
19283
|
});
|
|
19145
19284
|
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
19146
|
-
for (const s of scenarios)
|
|
19285
|
+
for (const s of scenarios) {
|
|
19286
|
+
if (s.kind === "renamed" || s.kind === "moved") continue;
|
|
19287
|
+
summary[s.kind] += 1;
|
|
19288
|
+
}
|
|
19147
19289
|
return { schemaVersion: "1.0", summary, scenarios };
|
|
19148
19290
|
}
|
|
19149
19291
|
|
|
@@ -21987,6 +22129,9 @@ import { fileURLToPath } from "url";
|
|
|
21987
22129
|
var __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
21988
22130
|
var FRAMEWORK_DIRS = ["src/components", "src/lib", "src/styles", "src/pages"];
|
|
21989
22131
|
var FRAMEWORK_FILES = ["tsconfig.json"];
|
|
22132
|
+
function isScaffoldedAstroSite(dir) {
|
|
22133
|
+
return fs9.existsSync(path10.join(dir, "astro.config.mjs"));
|
|
22134
|
+
}
|
|
21990
22135
|
function initAstro(options = {}) {
|
|
21991
22136
|
const targetDir = options.targetDir ?? "./story-docs";
|
|
21992
22137
|
const force = options.force ?? false;
|
|
@@ -22012,7 +22157,7 @@ function initAstro(options = {}) {
|
|
|
22012
22157
|
return { targetDir };
|
|
22013
22158
|
}
|
|
22014
22159
|
function updateFrameworkFiles(templateDir, targetDir) {
|
|
22015
|
-
if (!
|
|
22160
|
+
if (!isScaffoldedAstroSite(targetDir)) {
|
|
22016
22161
|
throw new Error(
|
|
22017
22162
|
`"${targetDir}" does not look like a scaffolded docs site. Run init-astro (without --update) first.`
|
|
22018
22163
|
);
|
|
@@ -22057,7 +22202,8 @@ function copyDirRecursive(src, dest, onFile, baseSrc = src) {
|
|
|
22057
22202
|
const entries = fs9.readdirSync(src, { withFileTypes: true });
|
|
22058
22203
|
for (const entry of entries) {
|
|
22059
22204
|
const srcPath = path10.join(src, entry.name);
|
|
22060
|
-
const
|
|
22205
|
+
const destName = entry.name === "gitignore" ? ".gitignore" : entry.name;
|
|
22206
|
+
const destPath = path10.join(dest, destName);
|
|
22061
22207
|
if (entry.isDirectory()) {
|
|
22062
22208
|
copyDirRecursive(srcPath, destPath, onFile, baseSrc);
|
|
22063
22209
|
} else {
|
|
@@ -23019,6 +23165,12 @@ ${schemaResult.errors.map((e) => ` ${e}`).join("\n")}`,
|
|
|
23019
23165
|
}
|
|
23020
23166
|
async function buildDocs(options) {
|
|
23021
23167
|
const siteDir = path16.resolve(options.siteDir);
|
|
23168
|
+
if (!isScaffoldedAstroSite(siteDir)) {
|
|
23169
|
+
throw new BuildDocsError(
|
|
23170
|
+
`"${siteDir}" is not a scaffolded Astro docs site (no astro.config.mjs). Run "executable-stories init-astro <dir>" first, then pass it with --site-dir <dir>.`,
|
|
23171
|
+
"usage"
|
|
23172
|
+
);
|
|
23173
|
+
}
|
|
23022
23174
|
const storiesPublicDir = path16.join(siteDir, "public", "stories");
|
|
23023
23175
|
const assetsDir = path16.join(storiesPublicDir, "assets");
|
|
23024
23176
|
const storyPagesDir = path16.join(siteDir, "src", "content", "docs", "stories");
|
|
@@ -23152,6 +23304,7 @@ executable-stories \u2014 Generate reports from test results JSON.
|
|
|
23152
23304
|
USAGE
|
|
23153
23305
|
executable-stories format <file> [options]
|
|
23154
23306
|
executable-stories format --stdin [options]
|
|
23307
|
+
executable-stories watch <raw-run.json> [options]
|
|
23155
23308
|
executable-stories compare <baseline-file> <current-file> [options]
|
|
23156
23309
|
executable-stories gate-release <dev-run.json> <rc-run.json> [options]
|
|
23157
23310
|
executable-stories review <file> --changed-files <path> [options]
|
|
@@ -23162,6 +23315,7 @@ USAGE
|
|
|
23162
23315
|
executable-stories validate <file>
|
|
23163
23316
|
executable-stories validate --stdin
|
|
23164
23317
|
executable-stories init-astro [directory]
|
|
23318
|
+
executable-stories build-docs <raw-run.json> [--site-dir <dir>] [options]
|
|
23165
23319
|
executable-stories new <template> "<name>" [options]
|
|
23166
23320
|
executable-stories check-links <dir> [options]
|
|
23167
23321
|
executable-stories import-openapi <spec> [options]
|
|
@@ -23183,6 +23337,7 @@ SUBCOMMANDS
|
|
|
23183
23337
|
triage Discovery worklist for agent loops: failing scenarios, regressions first, each with the code it covers
|
|
23184
23338
|
validate Validate a JSON file against the schema (no output generated)
|
|
23185
23339
|
init-astro Scaffold an Astro docs site for story output (Starlight with themed CSS)
|
|
23340
|
+
build-docs Build the living-docs site: one page per story file + Explorer data (auto-pickup, prunes deleted stories)
|
|
23186
23341
|
new Scaffold a docs page from a template (adr, runbook, decision-log, incident, scenario-note)
|
|
23187
23342
|
check-links Scan docs for broken internal/external links (CI-friendly exit code)
|
|
23188
23343
|
import-openapi Generate API doc pages from an OpenAPI spec, linked to verifying stories
|
|
@@ -23192,7 +23347,7 @@ SUBCOMMANDS
|
|
|
23192
23347
|
|
|
23193
23348
|
OPTIONS
|
|
23194
23349
|
--format <formats> Comma-separated formats: html, markdown, release-manifest, traceability-matrix, 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)
|
|
23195
|
-
astro Themed Markdown (for
|
|
23350
|
+
astro Themed Markdown primitive (single aggregated page; for a full site use "build-docs")
|
|
23196
23351
|
confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
|
|
23197
23352
|
behavior-manifest-json Agent-readable behavior manifest and debugger warnings
|
|
23198
23353
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
@@ -23313,6 +23468,20 @@ DEPLOY
|
|
|
23313
23468
|
INIT-ASTRO
|
|
23314
23469
|
executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
|
|
23315
23470
|
--force Overwrite existing directory
|
|
23471
|
+
--update Refresh framework files only (keeps your content + config)
|
|
23472
|
+
|
|
23473
|
+
BUILD-DOCS
|
|
23474
|
+
Build the multi-page living-docs site from a raw run: one Astro page per story
|
|
23475
|
+
file plus Explorer data (scenario-links.json, story-report.json). Auto-pickup \u2014
|
|
23476
|
+
a new *.story.test.ts becomes a new page on the next run; deleting a story
|
|
23477
|
+
prunes its page. This is the headline living-docs flow; "format --format astro"
|
|
23478
|
+
is a low-level primitive that emits a single aggregated page, not a site.
|
|
23479
|
+
|
|
23480
|
+
executable-stories build-docs <raw-run.json> [--site-dir <dir>]
|
|
23481
|
+
--site-dir <dir> Target site dir (default: a scaffolded init-astro site)
|
|
23482
|
+
--openapi <spec> Link generated API pages to verifying stories
|
|
23483
|
+
--baseline <prev-report> Diff against a prior story-report.json for change markers
|
|
23484
|
+
--audience-split Split pages by audience (business vs technical)
|
|
23316
23485
|
|
|
23317
23486
|
PUBLISH-CONFLUENCE
|
|
23318
23487
|
executable-stories publish-confluence <file.adf.json> [options]
|
|
@@ -23419,12 +23588,17 @@ async function parseCliArgs(argv) {
|
|
|
23419
23588
|
console.log("To change theme, edit astro.config.mjs customCss array.");
|
|
23420
23589
|
console.log("");
|
|
23421
23590
|
console.log("Next steps:");
|
|
23422
|
-
console.log(` cd ${result.targetDir}`);
|
|
23423
|
-
console.log("
|
|
23424
|
-
console.log("
|
|
23425
|
-
console.log("");
|
|
23426
|
-
console.log("
|
|
23427
|
-
console.log(
|
|
23591
|
+
console.log(` 1. cd ${result.targetDir} && pnpm install # or npm install`);
|
|
23592
|
+
console.log(" 2. In your TEST project, add the StoryReporter with a rawRunPath, e.g.");
|
|
23593
|
+
console.log(" StoryReporter({ rawRunPath: 'reports/raw-run.json' })");
|
|
23594
|
+
console.log(" (this is what writes the raw run that build-docs reads)");
|
|
23595
|
+
console.log(" 3. Run your tests to produce reports/raw-run.json:");
|
|
23596
|
+
console.log(" pnpm test");
|
|
23597
|
+
console.log(" 4. Build the living-docs site (story pages, explorer data, API pages):");
|
|
23598
|
+
console.log(
|
|
23599
|
+
` executable-stories build-docs reports/raw-run.json --site-dir ${result.targetDir} [--openapi spec.json]`
|
|
23600
|
+
);
|
|
23601
|
+
console.log(` 5. Preview it: cd ${result.targetDir} && pnpm dev`);
|
|
23428
23602
|
console.log("");
|
|
23429
23603
|
console.log("Later, pull template/design improvements without losing your content:");
|
|
23430
23604
|
console.log(` executable-stories init-astro ${result.targetDir} --update`);
|