executable-stories-formatters 0.14.0 → 0.15.1
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 +346 -167
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +8 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -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
|
@@ -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 fs16 from "fs";
|
|
6
|
+
import * as path17 from "path";
|
|
7
7
|
|
|
8
8
|
// src/validation/schema-validator.ts
|
|
9
9
|
import Ajv from "ajv/dist/2020.js";
|
|
@@ -588,17 +588,17 @@ function validateRawRun(data) {
|
|
|
588
588
|
return { valid: true, errors: [] };
|
|
589
589
|
}
|
|
590
590
|
const errors = (validate.errors ?? []).map((err) => {
|
|
591
|
-
const
|
|
591
|
+
const path18 = err.instancePath || "/";
|
|
592
592
|
const message = err.message ?? "unknown error";
|
|
593
593
|
if (err.keyword === "additionalProperties") {
|
|
594
594
|
const extra = err.params.additionalProperty;
|
|
595
|
-
return `${
|
|
595
|
+
return `${path18}: ${message} \u2014 '${extra}'`;
|
|
596
596
|
}
|
|
597
597
|
if (err.keyword === "enum") {
|
|
598
598
|
const allowed = err.params.allowedValues;
|
|
599
|
-
return `${
|
|
599
|
+
return `${path18}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
|
|
600
600
|
}
|
|
601
|
-
return `${
|
|
601
|
+
return `${path18}: ${message}`;
|
|
602
602
|
});
|
|
603
603
|
return { valid: false, errors };
|
|
604
604
|
}
|
|
@@ -15864,7 +15864,8 @@ var MarkdownFormatter = class {
|
|
|
15864
15864
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
15865
15865
|
customRenderers: options.customRenderers,
|
|
15866
15866
|
scenarioAnchor: options.scenarioAnchor,
|
|
15867
|
-
scenarioBadge: options.scenarioBadge
|
|
15867
|
+
scenarioBadge: options.scenarioBadge,
|
|
15868
|
+
scenarioNoteLink: options.scenarioNoteLink
|
|
15868
15869
|
};
|
|
15869
15870
|
}
|
|
15870
15871
|
/**
|
|
@@ -16075,6 +16076,10 @@ var MarkdownFormatter = class {
|
|
|
16075
16076
|
if (badge2) {
|
|
16076
16077
|
lines.push(badge2);
|
|
16077
16078
|
}
|
|
16079
|
+
const noteLink = this.options.scenarioNoteLink?.(tc);
|
|
16080
|
+
if (noteLink) {
|
|
16081
|
+
lines.push(noteLink);
|
|
16082
|
+
}
|
|
16078
16083
|
if (this.options.includeSourceLinks && this.options.permalinkBaseUrl && tc.sourceFile !== "unknown") {
|
|
16079
16084
|
const permalink = this.buildPermalink(tc);
|
|
16080
16085
|
lines.push(`Source: [${tc.sourceFile}](${permalink})`);
|
|
@@ -16490,14 +16495,14 @@ var TraceabilityMatrixFormatter = class {
|
|
|
16490
16495
|
lines.push("");
|
|
16491
16496
|
lines.push(`Status: ${renderRequirementStatus(req.status)}`);
|
|
16492
16497
|
if (req.covers.length > 0) {
|
|
16493
|
-
lines.push(`Covers: ${req.covers.map((
|
|
16498
|
+
lines.push(`Covers: ${req.covers.map((path18) => `\`${path18}\``).join(", ")}`);
|
|
16494
16499
|
}
|
|
16495
16500
|
lines.push("");
|
|
16496
16501
|
lines.push("| Status | Scenario | Source | Covers |");
|
|
16497
16502
|
lines.push("| --- | --- | --- | --- |");
|
|
16498
16503
|
for (const scenario of req.scenarios) {
|
|
16499
16504
|
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
16500
|
-
const covers = scenario.covers.length > 0 ? scenario.covers.map((
|
|
16505
|
+
const covers = scenario.covers.length > 0 ? scenario.covers.map((path18) => `\`${path18}\``).join(", ") : "";
|
|
16501
16506
|
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
|
|
16502
16507
|
}
|
|
16503
16508
|
lines.push("");
|
|
@@ -16599,8 +16604,8 @@ function extractFeatureName(testCases, uri) {
|
|
|
16599
16604
|
return tc.titlePath[0];
|
|
16600
16605
|
}
|
|
16601
16606
|
}
|
|
16602
|
-
const
|
|
16603
|
-
return
|
|
16607
|
+
const basename5 = uri.replace(/^.*[\\/]/, "").replace(/\.[^.]+$/, "");
|
|
16608
|
+
return basename5.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
16604
16609
|
}
|
|
16605
16610
|
function synthesizeFeature(uri, testCases) {
|
|
16606
16611
|
const featureName = extractFeatureName(testCases, uri);
|
|
@@ -17212,8 +17217,8 @@ function extractDocAttachments(step) {
|
|
|
17212
17217
|
}
|
|
17213
17218
|
return attachments;
|
|
17214
17219
|
}
|
|
17215
|
-
function guessMediaType(
|
|
17216
|
-
const lower =
|
|
17220
|
+
function guessMediaType(path18) {
|
|
17221
|
+
const lower = path18.toLowerCase();
|
|
17217
17222
|
if (lower.endsWith(".png")) return "image/png";
|
|
17218
17223
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
17219
17224
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -20747,18 +20752,18 @@ function deriveChangeType(tags) {
|
|
|
20747
20752
|
}
|
|
20748
20753
|
return "unknown";
|
|
20749
20754
|
}
|
|
20750
|
-
function extensionOf(
|
|
20751
|
-
const base =
|
|
20755
|
+
function extensionOf(path18) {
|
|
20756
|
+
const base = path18.split("/").pop() ?? path18;
|
|
20752
20757
|
const dot = base.lastIndexOf(".");
|
|
20753
20758
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
20754
20759
|
}
|
|
20755
|
-
function isTestFile(
|
|
20756
|
-
return TEST_INFIX.test(
|
|
20760
|
+
function isTestFile(path18) {
|
|
20761
|
+
return TEST_INFIX.test(path18);
|
|
20757
20762
|
}
|
|
20758
|
-
function isReviewableSource(
|
|
20759
|
-
if (isTestFile(
|
|
20760
|
-
if (
|
|
20761
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
20763
|
+
function isReviewableSource(path18) {
|
|
20764
|
+
if (isTestFile(path18)) return false;
|
|
20765
|
+
if (path18.endsWith(".d.ts")) return false;
|
|
20766
|
+
return CODE_EXTENSIONS.has(extensionOf(path18));
|
|
20762
20767
|
}
|
|
20763
20768
|
function testBaseKey(testFile) {
|
|
20764
20769
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -20862,7 +20867,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
20862
20867
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
20863
20868
|
const key = testBaseKey(testCase.sourceFile);
|
|
20864
20869
|
const coversFiles = changedSourcePaths.filter(
|
|
20865
|
-
(
|
|
20870
|
+
(path18) => sourceBaseKey(path18) === key
|
|
20866
20871
|
);
|
|
20867
20872
|
return {
|
|
20868
20873
|
id: testCase.id,
|
|
@@ -21727,7 +21732,8 @@ var ReportGenerator = class {
|
|
|
21727
21732
|
traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
|
|
21728
21733
|
customRenderers: options.astro?.markdown?.customRenderers,
|
|
21729
21734
|
scenarioAnchor: options.astro?.markdown?.scenarioAnchor,
|
|
21730
|
-
scenarioBadge: options.astro?.markdown?.scenarioBadge
|
|
21735
|
+
scenarioBadge: options.astro?.markdown?.scenarioBadge,
|
|
21736
|
+
scenarioNoteLink: options.astro?.markdown?.scenarioNoteLink
|
|
21731
21737
|
}
|
|
21732
21738
|
},
|
|
21733
21739
|
assetMode: options.assetMode ?? "none",
|
|
@@ -21981,6 +21987,9 @@ import { fileURLToPath } from "url";
|
|
|
21981
21987
|
var __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
21982
21988
|
var FRAMEWORK_DIRS = ["src/components", "src/lib", "src/styles", "src/pages"];
|
|
21983
21989
|
var FRAMEWORK_FILES = ["tsconfig.json"];
|
|
21990
|
+
function isScaffoldedAstroSite(dir) {
|
|
21991
|
+
return fs9.existsSync(path10.join(dir, "astro.config.mjs"));
|
|
21992
|
+
}
|
|
21984
21993
|
function initAstro(options = {}) {
|
|
21985
21994
|
const targetDir = options.targetDir ?? "./story-docs";
|
|
21986
21995
|
const force = options.force ?? false;
|
|
@@ -22006,7 +22015,7 @@ function initAstro(options = {}) {
|
|
|
22006
22015
|
return { targetDir };
|
|
22007
22016
|
}
|
|
22008
22017
|
function updateFrameworkFiles(templateDir, targetDir) {
|
|
22009
|
-
if (!
|
|
22018
|
+
if (!isScaffoldedAstroSite(targetDir)) {
|
|
22010
22019
|
throw new Error(
|
|
22011
22020
|
`"${targetDir}" does not look like a scaffolded docs site. Run init-astro (without --update) first.`
|
|
22012
22021
|
);
|
|
@@ -22051,7 +22060,8 @@ function copyDirRecursive(src, dest, onFile, baseSrc = src) {
|
|
|
22051
22060
|
const entries = fs9.readdirSync(src, { withFileTypes: true });
|
|
22052
22061
|
for (const entry of entries) {
|
|
22053
22062
|
const srcPath = path10.join(src, entry.name);
|
|
22054
|
-
const
|
|
22063
|
+
const destName = entry.name === "gitignore" ? ".gitignore" : entry.name;
|
|
22064
|
+
const destPath = path10.join(dest, destName);
|
|
22055
22065
|
if (entry.isDirectory()) {
|
|
22056
22066
|
copyDirRecursive(srcPath, destPath, onFile, baseSrc);
|
|
22057
22067
|
} else {
|
|
@@ -22064,7 +22074,13 @@ function copyDirRecursive(src, dest, onFile, baseSrc = src) {
|
|
|
22064
22074
|
// src/scaffold-doc.ts
|
|
22065
22075
|
import * as fs10 from "fs";
|
|
22066
22076
|
import * as path11 from "path";
|
|
22067
|
-
var TEMPLATES = [
|
|
22077
|
+
var TEMPLATES = [
|
|
22078
|
+
"adr",
|
|
22079
|
+
"runbook",
|
|
22080
|
+
"decision-log",
|
|
22081
|
+
"incident",
|
|
22082
|
+
"scenario-note"
|
|
22083
|
+
];
|
|
22068
22084
|
function slugify3(input) {
|
|
22069
22085
|
return input.toLowerCase().trim().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
|
|
22070
22086
|
}
|
|
@@ -22193,6 +22209,30 @@ _How it was fixed._
|
|
|
22193
22209
|
|
|
22194
22210
|
- [ ] Add a regression story and link it in \`verifiedBy\` so a silent recurrence
|
|
22195
22211
|
becomes a failing badge.
|
|
22212
|
+
`
|
|
22213
|
+
},
|
|
22214
|
+
"scenario-note": {
|
|
22215
|
+
subdir: "notes",
|
|
22216
|
+
filename: (_slug, ctx) => ctx.scenarioId ?? ctx.slug,
|
|
22217
|
+
content: (ctx) => `---
|
|
22218
|
+
title: 'Business context \u2014 ${ctx.name}'
|
|
22219
|
+
description: 'Stakeholder context for ${ctx.name}'
|
|
22220
|
+
scenarioId: ${ctx.scenarioId}
|
|
22221
|
+
# Link this note back to the scenario it explains so the badge and explorer stay aligned.
|
|
22222
|
+
verifiedBy: [${ctx.scenarioId}]
|
|
22223
|
+
---
|
|
22224
|
+
|
|
22225
|
+
This page is hand-written commentary for a generated scenario. It is never
|
|
22226
|
+
overwritten by \`build-docs\`.
|
|
22227
|
+
|
|
22228
|
+
## Why this behavior matters
|
|
22229
|
+
|
|
22230
|
+
_Describe the business rule, policy, customer promise, or operational nuance._
|
|
22231
|
+
|
|
22232
|
+
## Caveats
|
|
22233
|
+
|
|
22234
|
+
- _What readers should know when this scenario passes_
|
|
22235
|
+
- _Any assumptions, exclusions, or follow-up links_
|
|
22196
22236
|
`
|
|
22197
22237
|
}
|
|
22198
22238
|
};
|
|
@@ -22211,10 +22251,15 @@ function scaffoldDoc(options) {
|
|
|
22211
22251
|
const today = options.today ?? /* @__PURE__ */ new Date();
|
|
22212
22252
|
const name = (options.name ?? "").trim() || defaultName(template);
|
|
22213
22253
|
const slug2 = slugify3(name);
|
|
22254
|
+
const scenarioId = normalizeScenarioId(options.scenarioId);
|
|
22214
22255
|
const dir = path11.join(baseDir, spec.subdir);
|
|
22256
|
+
if (template === "scenario-note" && !scenarioId) {
|
|
22257
|
+
throw new Error(`Template "scenario-note" requires --scenario-id.`);
|
|
22258
|
+
}
|
|
22215
22259
|
const ctx = {
|
|
22216
22260
|
name,
|
|
22217
22261
|
slug: slug2,
|
|
22262
|
+
scenarioId,
|
|
22218
22263
|
isoDate: isoDate(today),
|
|
22219
22264
|
seq: nextSeq(dir)
|
|
22220
22265
|
};
|
|
@@ -22239,6 +22284,8 @@ function defaultName(template) {
|
|
|
22239
22284
|
return "Decisions";
|
|
22240
22285
|
case "incident":
|
|
22241
22286
|
return "Untitled incident";
|
|
22287
|
+
case "scenario-note":
|
|
22288
|
+
return "Untitled scenario note";
|
|
22242
22289
|
}
|
|
22243
22290
|
}
|
|
22244
22291
|
function titleFor2(template, ctx) {
|
|
@@ -22251,12 +22298,43 @@ function titleFor2(template, ctx) {
|
|
|
22251
22298
|
return `Decision log \u2014 ${ctx.name}`;
|
|
22252
22299
|
case "incident":
|
|
22253
22300
|
return `Incident \u2014 ${ctx.name}`;
|
|
22301
|
+
case "scenario-note":
|
|
22302
|
+
return `Business context \u2014 ${ctx.name}`;
|
|
22254
22303
|
}
|
|
22255
22304
|
}
|
|
22305
|
+
function normalizeScenarioId(input) {
|
|
22306
|
+
const value = input?.trim();
|
|
22307
|
+
if (!value) return void 0;
|
|
22308
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
22309
|
+
throw new Error(`scenarioId must not contain path separators.`);
|
|
22310
|
+
}
|
|
22311
|
+
return value;
|
|
22312
|
+
}
|
|
22256
22313
|
|
|
22257
22314
|
// src/check-links.ts
|
|
22315
|
+
import * as fs12 from "fs";
|
|
22316
|
+
import * as path13 from "path";
|
|
22317
|
+
|
|
22318
|
+
// src/utils/markdown-files.ts
|
|
22258
22319
|
import * as fs11 from "fs";
|
|
22259
22320
|
import * as path12 from "path";
|
|
22321
|
+
function collectMarkdownFiles(target) {
|
|
22322
|
+
if (!fs11.existsSync(target)) return [];
|
|
22323
|
+
if (fs11.statSync(target).isFile()) return [target];
|
|
22324
|
+
const out = [];
|
|
22325
|
+
const walk = (dir) => {
|
|
22326
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
22327
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
22328
|
+
const full = path12.join(dir, entry.name);
|
|
22329
|
+
if (entry.isDirectory()) walk(full);
|
|
22330
|
+
else if (/\.mdx?$/u.test(entry.name)) out.push(full);
|
|
22331
|
+
}
|
|
22332
|
+
};
|
|
22333
|
+
walk(target);
|
|
22334
|
+
return out;
|
|
22335
|
+
}
|
|
22336
|
+
|
|
22337
|
+
// src/check-links.ts
|
|
22260
22338
|
function stripCode(markdown) {
|
|
22261
22339
|
let out = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
22262
22340
|
out = out.replace(/(`+)(?:(?!\1).)+\1/g, "");
|
|
@@ -22286,34 +22364,19 @@ function classifyLink(link2) {
|
|
|
22286
22364
|
function resolutionCandidates(fromFile, link2) {
|
|
22287
22365
|
const withoutAnchor = link2.split("#")[0];
|
|
22288
22366
|
if (!withoutAnchor) return [];
|
|
22289
|
-
const base =
|
|
22367
|
+
const base = path13.resolve(path13.dirname(fromFile), withoutAnchor);
|
|
22290
22368
|
const candidates = [base];
|
|
22291
|
-
if (!
|
|
22369
|
+
if (!path13.extname(base)) {
|
|
22292
22370
|
candidates.push(`${base}.md`, `${base}.mdx`);
|
|
22293
|
-
candidates.push(
|
|
22371
|
+
candidates.push(path13.join(base, "index.md"), path13.join(base, "index.mdx"));
|
|
22294
22372
|
}
|
|
22295
22373
|
return candidates;
|
|
22296
22374
|
}
|
|
22297
22375
|
function resolvesOnDisk(fromFile, link2) {
|
|
22298
22376
|
return resolutionCandidates(fromFile, link2).some(
|
|
22299
|
-
(candidate) =>
|
|
22377
|
+
(candidate) => fs12.existsSync(candidate) && fs12.statSync(candidate).isFile()
|
|
22300
22378
|
);
|
|
22301
22379
|
}
|
|
22302
|
-
function collectDocFiles(target) {
|
|
22303
|
-
const stat = fs11.statSync(target);
|
|
22304
|
-
if (stat.isFile()) return [target];
|
|
22305
|
-
const out = [];
|
|
22306
|
-
const walk = (dir) => {
|
|
22307
|
-
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
22308
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
22309
|
-
const full = path12.join(dir, entry.name);
|
|
22310
|
-
if (entry.isDirectory()) walk(full);
|
|
22311
|
-
else if (/\.mdx?$/.test(entry.name)) out.push(full);
|
|
22312
|
-
}
|
|
22313
|
-
};
|
|
22314
|
-
walk(target);
|
|
22315
|
-
return out;
|
|
22316
|
-
}
|
|
22317
22380
|
async function isExternalAlive(url, timeoutMs) {
|
|
22318
22381
|
const attempt = async (method) => {
|
|
22319
22382
|
const controller = new AbortController();
|
|
@@ -22337,17 +22400,17 @@ async function isExternalAlive(url, timeoutMs) {
|
|
|
22337
22400
|
}
|
|
22338
22401
|
async function checkLinks(options) {
|
|
22339
22402
|
const { target, checkExternal = false, externalTimeoutMs = 8e3 } = options;
|
|
22340
|
-
if (!
|
|
22403
|
+
if (!fs12.existsSync(target)) {
|
|
22341
22404
|
throw new Error(`Path not found: ${target}`);
|
|
22342
22405
|
}
|
|
22343
|
-
const files =
|
|
22406
|
+
const files = collectMarkdownFiles(target);
|
|
22344
22407
|
const broken = [];
|
|
22345
22408
|
let linksChecked = 0;
|
|
22346
22409
|
let externalChecked = 0;
|
|
22347
22410
|
let skipped = 0;
|
|
22348
22411
|
const externalCache = /* @__PURE__ */ new Map();
|
|
22349
22412
|
for (const file of files) {
|
|
22350
|
-
const content =
|
|
22413
|
+
const content = fs12.readFileSync(file, "utf8");
|
|
22351
22414
|
for (const link2 of extractLinks(content)) {
|
|
22352
22415
|
const kind = classifyLink(link2);
|
|
22353
22416
|
if (kind === "anchor" || kind === "mail" || kind === "root") {
|
|
@@ -22403,8 +22466,8 @@ function formatLinkReport(report) {
|
|
|
22403
22466
|
}
|
|
22404
22467
|
|
|
22405
22468
|
// src/import-openapi.ts
|
|
22406
|
-
import * as
|
|
22407
|
-
import * as
|
|
22469
|
+
import * as fs13 from "fs";
|
|
22470
|
+
import * as path14 from "path";
|
|
22408
22471
|
import { parse as parseYamlString } from "yaml";
|
|
22409
22472
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
|
|
22410
22473
|
function parseYaml(raw, specPath) {
|
|
@@ -22417,9 +22480,9 @@ function parseYaml(raw, specPath) {
|
|
|
22417
22480
|
}
|
|
22418
22481
|
}
|
|
22419
22482
|
function parseSpec(specPath) {
|
|
22420
|
-
if (!
|
|
22421
|
-
const raw =
|
|
22422
|
-
const ext =
|
|
22483
|
+
if (!fs13.existsSync(specPath)) throw new Error(`Spec not found: ${specPath}`);
|
|
22484
|
+
const raw = fs13.readFileSync(specPath, "utf8");
|
|
22485
|
+
const ext = path14.extname(specPath).toLowerCase();
|
|
22423
22486
|
if (ext === ".json") return JSON.parse(raw);
|
|
22424
22487
|
if (ext === ".yaml" || ext === ".yml") return parseYaml(raw, specPath);
|
|
22425
22488
|
try {
|
|
@@ -22450,8 +22513,8 @@ function extractEndpoints(spec) {
|
|
|
22450
22513
|
}
|
|
22451
22514
|
function loadScenarios(runFile) {
|
|
22452
22515
|
if (!runFile) return [];
|
|
22453
|
-
if (!
|
|
22454
|
-
const report = JSON.parse(
|
|
22516
|
+
if (!fs13.existsSync(runFile)) throw new Error(`Run file not found: ${runFile}`);
|
|
22517
|
+
const report = JSON.parse(fs13.readFileSync(runFile, "utf8"));
|
|
22455
22518
|
return (report.features ?? []).flatMap((f) => f.scenarios ?? []);
|
|
22456
22519
|
}
|
|
22457
22520
|
function endpointRefs(endpoint) {
|
|
@@ -22558,25 +22621,25 @@ async function importOpenApi(options) {
|
|
|
22558
22621
|
list.push(item);
|
|
22559
22622
|
groups.set(item.endpoint.tag, list);
|
|
22560
22623
|
}
|
|
22561
|
-
const outputDir = options.outputDir ??
|
|
22562
|
-
if (
|
|
22563
|
-
const entries =
|
|
22624
|
+
const outputDir = options.outputDir ?? path14.join("src", "content", "docs", "api");
|
|
22625
|
+
if (fs13.existsSync(outputDir) && !options.force) {
|
|
22626
|
+
const entries = fs13.readdirSync(outputDir);
|
|
22564
22627
|
if (entries.length > 0) {
|
|
22565
22628
|
throw new Error(`Output directory "${outputDir}" is not empty. Use --force to overwrite.`);
|
|
22566
22629
|
}
|
|
22567
22630
|
}
|
|
22568
|
-
|
|
22631
|
+
fs13.mkdirSync(outputDir, { recursive: true });
|
|
22569
22632
|
const coveredCount = coverage.filter((c) => c.status === "covered").length;
|
|
22570
22633
|
const uncoveredCount = coverage.filter((c) => c.status === "uncovered").length;
|
|
22571
|
-
|
|
22572
|
-
|
|
22634
|
+
fs13.writeFileSync(
|
|
22635
|
+
path14.join(outputDir, "index.mdx"),
|
|
22573
22636
|
renderIndex(groups, hasRun, { endpointCount: endpoints.length, coveredCount, uncoveredCount }),
|
|
22574
22637
|
"utf8"
|
|
22575
22638
|
);
|
|
22576
22639
|
for (const [tag, rows] of groups) {
|
|
22577
|
-
const dir =
|
|
22578
|
-
|
|
22579
|
-
|
|
22640
|
+
const dir = path14.join(outputDir, slug(tag));
|
|
22641
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
22642
|
+
fs13.writeFileSync(path14.join(dir, "index.mdx"), renderTagPage(tag, rows, hasRun), "utf8");
|
|
22580
22643
|
}
|
|
22581
22644
|
return {
|
|
22582
22645
|
outputDir,
|
|
@@ -22588,8 +22651,8 @@ async function importOpenApi(options) {
|
|
|
22588
22651
|
}
|
|
22589
22652
|
|
|
22590
22653
|
// src/build-docs.ts
|
|
22591
|
-
import * as
|
|
22592
|
-
import * as
|
|
22654
|
+
import * as fs15 from "fs";
|
|
22655
|
+
import * as path16 from "path";
|
|
22593
22656
|
|
|
22594
22657
|
// src/scenario-links.ts
|
|
22595
22658
|
function scenarioAnchor(title) {
|
|
@@ -22685,6 +22748,58 @@ ${body.join("\n")}
|
|
|
22685
22748
|
`;
|
|
22686
22749
|
}
|
|
22687
22750
|
|
|
22751
|
+
// src/notes-index.ts
|
|
22752
|
+
import * as fs14 from "fs";
|
|
22753
|
+
import * as path15 from "path";
|
|
22754
|
+
import { slug as githubSlug } from "github-slugger";
|
|
22755
|
+
import { parse as parseYaml2 } from "yaml";
|
|
22756
|
+
function buildScenarioNotesIndex(notesDir) {
|
|
22757
|
+
const entries = collectMarkdownFiles(notesDir).map((filePath) => readScenarioNote(filePath, notesDir)).filter((entry) => entry !== null).sort((a, b) => a.slug.localeCompare(b.slug));
|
|
22758
|
+
return {
|
|
22759
|
+
schemaVersion: "1.0",
|
|
22760
|
+
notes: entries
|
|
22761
|
+
};
|
|
22762
|
+
}
|
|
22763
|
+
function writeNotesIndex(index, outPath) {
|
|
22764
|
+
fs14.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
22765
|
+
fs14.writeFileSync(outPath, JSON.stringify(index, null, 2), "utf8");
|
|
22766
|
+
return index;
|
|
22767
|
+
}
|
|
22768
|
+
function notesByScenarioId(index) {
|
|
22769
|
+
const map = /* @__PURE__ */ new Map();
|
|
22770
|
+
for (const note of index.notes) {
|
|
22771
|
+
if (!map.has(note.scenarioId)) map.set(note.scenarioId, note);
|
|
22772
|
+
}
|
|
22773
|
+
return map;
|
|
22774
|
+
}
|
|
22775
|
+
function noteHref(note) {
|
|
22776
|
+
return `/notes/${note.slug}/`;
|
|
22777
|
+
}
|
|
22778
|
+
function noteLinkMarkdown(note) {
|
|
22779
|
+
return `[Business context \u2192](${noteHref(note)})`;
|
|
22780
|
+
}
|
|
22781
|
+
function readScenarioNote(filePath, notesDir) {
|
|
22782
|
+
const relative5 = path15.relative(notesDir, filePath);
|
|
22783
|
+
const stem = relative5.replace(/\.(?:md|mdx)$/u, "");
|
|
22784
|
+
const frontmatter = parseFrontmatter(fs14.readFileSync(filePath, "utf8"));
|
|
22785
|
+
const scenarioId = typeof frontmatter.scenarioId === "string" && frontmatter.scenarioId.trim().length > 0 ? frontmatter.scenarioId.trim() : path15.basename(stem);
|
|
22786
|
+
const title = typeof frontmatter.title === "string" && frontmatter.title.trim().length > 0 ? frontmatter.title.trim() : `Business context \u2014 ${scenarioId}`;
|
|
22787
|
+
return {
|
|
22788
|
+
scenarioId,
|
|
22789
|
+
slug: toRouteSlug(stem),
|
|
22790
|
+
title
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22793
|
+
function parseFrontmatter(source) {
|
|
22794
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/u.exec(source);
|
|
22795
|
+
if (!match) return {};
|
|
22796
|
+
const parsed = parseYaml2(match[1]);
|
|
22797
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
22798
|
+
}
|
|
22799
|
+
function toRouteSlug(stem) {
|
|
22800
|
+
return stem.split(path15.sep).map((segment) => githubSlug(segment)).join("/").replace(/\/index$/u, "");
|
|
22801
|
+
}
|
|
22802
|
+
|
|
22688
22803
|
// src/overview-page.ts
|
|
22689
22804
|
var AUDIENCE_CARDS = [
|
|
22690
22805
|
{
|
|
@@ -22712,11 +22827,12 @@ function yamlScalar3(value) {
|
|
|
22712
22827
|
}
|
|
22713
22828
|
return value;
|
|
22714
22829
|
}
|
|
22715
|
-
function renderOverviewPage(links) {
|
|
22830
|
+
function renderOverviewPage(links, notesIndex) {
|
|
22716
22831
|
const all = Object.values(links.scenarios);
|
|
22717
22832
|
const total = all.length;
|
|
22718
22833
|
const passed = all.filter((s) => s.status === "passed").length;
|
|
22719
22834
|
const failed = all.filter((s) => s.status === "failed").length;
|
|
22835
|
+
const notesById = notesByScenarioId(notesIndex ?? { schemaVersion: "1.0", notes: [] });
|
|
22720
22836
|
const frontmatter = [
|
|
22721
22837
|
"---",
|
|
22722
22838
|
"title: Stories",
|
|
@@ -22740,7 +22856,9 @@ function renderOverviewPage(links) {
|
|
|
22740
22856
|
body.push(`## ${card.icon} ${card.label} (${scenarios.length} \u2014 ${counts})`, "");
|
|
22741
22857
|
body.push(`${card.blurb}`, "");
|
|
22742
22858
|
for (const s of scenariosSorted(scenarios)) {
|
|
22743
|
-
|
|
22859
|
+
const note = notesById.get(s.id);
|
|
22860
|
+
const noteSuffix = note ? ` \xB7 ${noteLinkMarkdown(note)}` : "";
|
|
22861
|
+
body.push(`- ${STATUS_ICON[s.status] ?? "\u2022"} [${s.title}](${s.deepLink})${noteSuffix}`);
|
|
22744
22862
|
}
|
|
22745
22863
|
body.push("");
|
|
22746
22864
|
}
|
|
@@ -22782,22 +22900,22 @@ var BuildDocsError = class extends Error {
|
|
|
22782
22900
|
};
|
|
22783
22901
|
var isRemote = (p) => /^(?:https?:|data:)/i.test(p);
|
|
22784
22902
|
function bundleExplorerAssets(reportPath, assetsDir, baseUrl = "/stories/assets") {
|
|
22785
|
-
if (!
|
|
22786
|
-
const report = JSON.parse(
|
|
22903
|
+
if (!fs15.existsSync(reportPath)) return 0;
|
|
22904
|
+
const report = JSON.parse(fs15.readFileSync(reportPath, "utf8"));
|
|
22787
22905
|
let copied = 0;
|
|
22788
22906
|
const bundle = (value) => {
|
|
22789
|
-
const rel = copyAsset(
|
|
22907
|
+
const rel = copyAsset(path16.resolve(value), assetsDir);
|
|
22790
22908
|
copied++;
|
|
22791
|
-
return `${baseUrl}/${
|
|
22909
|
+
return `${baseUrl}/${path16.basename(rel)}`;
|
|
22792
22910
|
};
|
|
22793
22911
|
const visit = (entries) => {
|
|
22794
22912
|
for (const entry of entries ?? []) {
|
|
22795
22913
|
const e = entry;
|
|
22796
22914
|
if (e.kind === "screenshot" || e.kind === "video" || e.kind === "html") {
|
|
22797
|
-
if (typeof e.path === "string" && !isRemote(e.path) &&
|
|
22915
|
+
if (typeof e.path === "string" && !isRemote(e.path) && fs15.existsSync(e.path)) {
|
|
22798
22916
|
e.path = bundle(e.path);
|
|
22799
22917
|
}
|
|
22800
|
-
if (typeof e.poster === "string" && !isRemote(e.poster) &&
|
|
22918
|
+
if (typeof e.poster === "string" && !isRemote(e.poster) && fs15.existsSync(e.poster)) {
|
|
22801
22919
|
e.poster = bundle(e.poster);
|
|
22802
22920
|
}
|
|
22803
22921
|
}
|
|
@@ -22810,7 +22928,7 @@ function bundleExplorerAssets(reportPath, assetsDir, baseUrl = "/stories/assets"
|
|
|
22810
22928
|
}
|
|
22811
22929
|
}
|
|
22812
22930
|
if (copied > 0) {
|
|
22813
|
-
|
|
22931
|
+
fs15.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
|
|
22814
22932
|
}
|
|
22815
22933
|
return copied;
|
|
22816
22934
|
}
|
|
@@ -22819,50 +22937,69 @@ var CHANGE_BADGE = {
|
|
|
22819
22937
|
fixed: "\u2705 **Fixed** _since last run_",
|
|
22820
22938
|
regressed: "\u26A0\uFE0F **Regressed** _since last run_"
|
|
22821
22939
|
};
|
|
22940
|
+
function scenarioKey(sourceFile, title) {
|
|
22941
|
+
return `${sourceFile}\0${title}`;
|
|
22942
|
+
}
|
|
22822
22943
|
function changeBadgeLookup(diff) {
|
|
22823
22944
|
if (!diff) return void 0;
|
|
22824
22945
|
const byKey = /* @__PURE__ */ new Map();
|
|
22825
22946
|
for (const s of diff.scenarios) {
|
|
22826
22947
|
const badge2 = CHANGE_BADGE[s.kind];
|
|
22827
|
-
if (badge2) byKey.set(
|
|
22948
|
+
if (badge2) byKey.set(scenarioKey(s.sourceFile, s.title), badge2);
|
|
22828
22949
|
}
|
|
22829
22950
|
if (byKey.size === 0) return void 0;
|
|
22830
|
-
return (tc) => byKey.get(
|
|
22951
|
+
return (tc) => byKey.get(scenarioKey(tc.sourceFile, tc.story.scenario));
|
|
22831
22952
|
}
|
|
22832
22953
|
function readStoryReport(reportPath) {
|
|
22833
|
-
if (!
|
|
22954
|
+
if (!fs15.existsSync(reportPath)) return null;
|
|
22834
22955
|
try {
|
|
22835
|
-
return JSON.parse(
|
|
22956
|
+
return JSON.parse(fs15.readFileSync(reportPath, "utf8"));
|
|
22836
22957
|
} catch {
|
|
22837
22958
|
return null;
|
|
22838
22959
|
}
|
|
22839
22960
|
}
|
|
22961
|
+
function noteLinkLookup(report, notesIndex) {
|
|
22962
|
+
if (!report) return void 0;
|
|
22963
|
+
const noteById = notesByScenarioId(notesIndex);
|
|
22964
|
+
if (noteById.size === 0) return void 0;
|
|
22965
|
+
const idByKey = /* @__PURE__ */ new Map();
|
|
22966
|
+
for (const feature of report.features) {
|
|
22967
|
+
for (const scenario of feature.scenarios) {
|
|
22968
|
+
idByKey.set(scenarioKey(feature.sourceFile, scenario.title), scenario.id);
|
|
22969
|
+
}
|
|
22970
|
+
}
|
|
22971
|
+
return (tc) => {
|
|
22972
|
+
const id = idByKey.get(scenarioKey(tc.sourceFile, tc.story.scenario));
|
|
22973
|
+
const note = id ? noteById.get(id) : void 0;
|
|
22974
|
+
return note ? noteLinkMarkdown(note) : void 0;
|
|
22975
|
+
};
|
|
22976
|
+
}
|
|
22840
22977
|
function writeScenarioLinks(reportPath, outDir, options = {}) {
|
|
22841
22978
|
const report = readStoryReport(reportPath);
|
|
22842
22979
|
if (!report) return null;
|
|
22843
22980
|
const index = buildScenarioLinks(report, { audienceSplit: options.audienceSplit });
|
|
22844
|
-
|
|
22845
|
-
|
|
22981
|
+
fs15.writeFileSync(
|
|
22982
|
+
path16.join(outDir, "scenario-links.json"),
|
|
22846
22983
|
JSON.stringify(index, null, 2),
|
|
22847
22984
|
"utf8"
|
|
22848
22985
|
);
|
|
22849
22986
|
return index;
|
|
22850
22987
|
}
|
|
22851
22988
|
function clearGeneratedPages(dir) {
|
|
22852
|
-
if (!
|
|
22853
|
-
for (const entry of
|
|
22854
|
-
const full =
|
|
22989
|
+
if (!fs15.existsSync(dir)) return;
|
|
22990
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
22991
|
+
const full = path16.join(dir, entry.name);
|
|
22855
22992
|
if (entry.isDirectory()) {
|
|
22856
22993
|
clearGeneratedPages(full);
|
|
22857
|
-
if (
|
|
22994
|
+
if (fs15.readdirSync(full).length === 0) fs15.rmdirSync(full);
|
|
22858
22995
|
} else if (/\.mdx?$/.test(entry.name)) {
|
|
22859
|
-
|
|
22996
|
+
fs15.rmSync(full);
|
|
22860
22997
|
}
|
|
22861
22998
|
}
|
|
22862
22999
|
}
|
|
22863
23000
|
function loadCanonicalRun(rawRunPath, synthesize) {
|
|
22864
23001
|
try {
|
|
22865
|
-
const data = JSON.parse(
|
|
23002
|
+
const data = JSON.parse(fs15.readFileSync(path16.resolve(rawRunPath), "utf8"));
|
|
22866
23003
|
if (data.schemaVersion !== 1) {
|
|
22867
23004
|
throw new BuildDocsError(`Unsupported schemaVersion ${data.schemaVersion}. Supported: 1.`, "schema");
|
|
22868
23005
|
}
|
|
@@ -22885,12 +23022,19 @@ ${schemaResult.errors.map((e) => ` ${e}`).join("\n")}`,
|
|
|
22885
23022
|
}
|
|
22886
23023
|
}
|
|
22887
23024
|
async function buildDocs(options) {
|
|
22888
|
-
const siteDir =
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
23025
|
+
const siteDir = path16.resolve(options.siteDir);
|
|
23026
|
+
if (!isScaffoldedAstroSite(siteDir)) {
|
|
23027
|
+
throw new BuildDocsError(
|
|
23028
|
+
`"${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>.`,
|
|
23029
|
+
"usage"
|
|
23030
|
+
);
|
|
23031
|
+
}
|
|
23032
|
+
const storiesPublicDir = path16.join(siteDir, "public", "stories");
|
|
23033
|
+
const assetsDir = path16.join(storiesPublicDir, "assets");
|
|
23034
|
+
const storyPagesDir = path16.join(siteDir, "src", "content", "docs", "stories");
|
|
23035
|
+
const notesDir = path16.join(siteDir, "src", "content", "docs", "notes");
|
|
23036
|
+
const apiDir = path16.join(siteDir, "src", "content", "docs", "api");
|
|
23037
|
+
const reportPath = path16.join(storiesPublicDir, "story-report.json");
|
|
22894
23038
|
const canonical = loadCanonicalRun(options.rawRunPath, options.synthesizeStories ?? true);
|
|
22895
23039
|
try {
|
|
22896
23040
|
await new ReportGenerator({
|
|
@@ -22898,9 +23042,10 @@ async function buildDocs(options) {
|
|
|
22898
23042
|
outputDir: storiesPublicDir,
|
|
22899
23043
|
outputName: "story-report"
|
|
22900
23044
|
}).generate(canonical);
|
|
23045
|
+
const currentReport = readStoryReport(reportPath);
|
|
22901
23046
|
let diff;
|
|
22902
23047
|
if (options.baselinePath) {
|
|
22903
|
-
const baselineResolved =
|
|
23048
|
+
const baselineResolved = path16.resolve(options.baselinePath);
|
|
22904
23049
|
const baseline = readStoryReport(baselineResolved);
|
|
22905
23050
|
if (!baseline) {
|
|
22906
23051
|
throw new BuildDocsError(
|
|
@@ -22908,10 +23053,11 @@ async function buildDocs(options) {
|
|
|
22908
23053
|
"input"
|
|
22909
23054
|
);
|
|
22910
23055
|
}
|
|
22911
|
-
|
|
22912
|
-
if (current) diff = diffStoryReports(baseline, current);
|
|
23056
|
+
if (currentReport) diff = diffStoryReports(baseline, currentReport);
|
|
22913
23057
|
}
|
|
22914
23058
|
const scenarioBadge = changeBadgeLookup(diff);
|
|
23059
|
+
const notesIndex = buildScenarioNotesIndex(notesDir);
|
|
23060
|
+
const scenarioNoteLink = noteLinkLookup(currentReport, notesIndex);
|
|
22915
23061
|
clearGeneratedPages(storyPagesDir);
|
|
22916
23062
|
const genPages = (run, outDir) => new ReportGenerator({
|
|
22917
23063
|
formats: ["astro"],
|
|
@@ -22925,7 +23071,8 @@ async function buildDocs(options) {
|
|
|
22925
23071
|
markdown: {
|
|
22926
23072
|
// Emit the same anchor scenario-links.json points at, so fragments resolve.
|
|
22927
23073
|
scenarioAnchor: (tc) => scenarioAnchor(tc.story.scenario),
|
|
22928
|
-
scenarioBadge
|
|
23074
|
+
scenarioBadge,
|
|
23075
|
+
scenarioNoteLink
|
|
22929
23076
|
}
|
|
22930
23077
|
}
|
|
22931
23078
|
}).generate(run);
|
|
@@ -22936,7 +23083,7 @@ async function buildDocs(options) {
|
|
|
22936
23083
|
const sub = partitioned[audience];
|
|
22937
23084
|
audiences[audience] = sub.testCases.length;
|
|
22938
23085
|
if (sub.testCases.length === 0) continue;
|
|
22939
|
-
await genPages(sub,
|
|
23086
|
+
await genPages(sub, path16.join(storyPagesDir, audience));
|
|
22940
23087
|
}
|
|
22941
23088
|
} else {
|
|
22942
23089
|
await genPages(canonical, storyPagesDir);
|
|
@@ -22946,35 +23093,37 @@ async function buildDocs(options) {
|
|
|
22946
23093
|
audienceSplit: options.audienceSplit ?? false
|
|
22947
23094
|
});
|
|
22948
23095
|
const scenarioLinks = linksIndex ? Object.keys(linksIndex.scenarios).length : 0;
|
|
23096
|
+
writeNotesIndex(notesIndex, path16.join(storiesPublicDir, "notes-index.json"));
|
|
23097
|
+
const notesIndexed = notesIndex.notes.length;
|
|
22949
23098
|
if (linksIndex) {
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
renderOverviewPage(linksIndex),
|
|
23099
|
+
fs15.writeFileSync(
|
|
23100
|
+
path16.join(storyPagesDir, "index.md"),
|
|
23101
|
+
renderOverviewPage(linksIndex, notesIndex),
|
|
22953
23102
|
"utf8"
|
|
22954
23103
|
);
|
|
22955
23104
|
}
|
|
22956
|
-
const changesJsonPath =
|
|
22957
|
-
const changesMdPath =
|
|
23105
|
+
const changesJsonPath = path16.join(storiesPublicDir, "changes.json");
|
|
23106
|
+
const changesMdPath = path16.join(storyPagesDir, "changes.md");
|
|
22958
23107
|
let changes;
|
|
22959
23108
|
if (diff && linksIndex) {
|
|
22960
|
-
|
|
22961
|
-
|
|
23109
|
+
fs15.writeFileSync(changesJsonPath, JSON.stringify(diff, null, 2), "utf8");
|
|
23110
|
+
fs15.writeFileSync(changesMdPath, renderChangesPage(diff, linksIndex), "utf8");
|
|
22962
23111
|
changes = diff.summary;
|
|
22963
23112
|
} else {
|
|
22964
|
-
|
|
22965
|
-
|
|
23113
|
+
fs15.rmSync(changesJsonPath, { force: true });
|
|
23114
|
+
fs15.rmSync(changesMdPath, { force: true });
|
|
22966
23115
|
}
|
|
22967
23116
|
let apiPages = 0;
|
|
22968
23117
|
if (options.openapiPath) {
|
|
22969
23118
|
const res = await importOpenApi({
|
|
22970
|
-
specPath:
|
|
23119
|
+
specPath: path16.resolve(options.openapiPath),
|
|
22971
23120
|
outputDir: apiDir,
|
|
22972
23121
|
runFile: reportPath,
|
|
22973
23122
|
force: true
|
|
22974
23123
|
});
|
|
22975
23124
|
apiPages = res.pageCount;
|
|
22976
23125
|
}
|
|
22977
|
-
return { siteDir, bundledAssets, apiPages, audiences, scenarioLinks, changes };
|
|
23126
|
+
return { siteDir, bundledAssets, apiPages, audiences, scenarioLinks, notesIndexed, changes };
|
|
22978
23127
|
} catch (err) {
|
|
22979
23128
|
if (err instanceof BuildDocsError) throw err;
|
|
22980
23129
|
throw new BuildDocsError(`Generation failed: ${err.message}`, "generation");
|
|
@@ -22982,11 +23131,11 @@ async function buildDocs(options) {
|
|
|
22982
23131
|
}
|
|
22983
23132
|
|
|
22984
23133
|
// src/config.ts
|
|
22985
|
-
import { existsSync as
|
|
23134
|
+
import { existsSync as existsSync13 } from "fs";
|
|
22986
23135
|
import { resolve as resolve10 } from "path";
|
|
22987
23136
|
async function loadConfig(configPath) {
|
|
22988
23137
|
const resolved = configPath ? resolve10(configPath) : resolve10(process.cwd(), "executable-stories.config.js");
|
|
22989
|
-
if (!
|
|
23138
|
+
if (!existsSync13(resolved)) return {};
|
|
22990
23139
|
const mod = await import(resolved);
|
|
22991
23140
|
const config = mod.default;
|
|
22992
23141
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
@@ -23013,6 +23162,7 @@ executable-stories \u2014 Generate reports from test results JSON.
|
|
|
23013
23162
|
USAGE
|
|
23014
23163
|
executable-stories format <file> [options]
|
|
23015
23164
|
executable-stories format --stdin [options]
|
|
23165
|
+
executable-stories watch <raw-run.json> [options]
|
|
23016
23166
|
executable-stories compare <baseline-file> <current-file> [options]
|
|
23017
23167
|
executable-stories gate-release <dev-run.json> <rc-run.json> [options]
|
|
23018
23168
|
executable-stories review <file> --changed-files <path> [options]
|
|
@@ -23023,6 +23173,7 @@ USAGE
|
|
|
23023
23173
|
executable-stories validate <file>
|
|
23024
23174
|
executable-stories validate --stdin
|
|
23025
23175
|
executable-stories init-astro [directory]
|
|
23176
|
+
executable-stories build-docs <raw-run.json> [--site-dir <dir>] [options]
|
|
23026
23177
|
executable-stories new <template> "<name>" [options]
|
|
23027
23178
|
executable-stories check-links <dir> [options]
|
|
23028
23179
|
executable-stories import-openapi <spec> [options]
|
|
@@ -23044,7 +23195,8 @@ SUBCOMMANDS
|
|
|
23044
23195
|
triage Discovery worklist for agent loops: failing scenarios, regressions first, each with the code it covers
|
|
23045
23196
|
validate Validate a JSON file against the schema (no output generated)
|
|
23046
23197
|
init-astro Scaffold an Astro docs site for story output (Starlight with themed CSS)
|
|
23047
|
-
|
|
23198
|
+
build-docs Build the living-docs site: one page per story file + Explorer data (auto-pickup, prunes deleted stories)
|
|
23199
|
+
new Scaffold a docs page from a template (adr, runbook, decision-log, incident, scenario-note)
|
|
23048
23200
|
check-links Scan docs for broken internal/external links (CI-friendly exit code)
|
|
23049
23201
|
import-openapi Generate API doc pages from an OpenAPI spec, linked to verifying stories
|
|
23050
23202
|
publish-confluence Publish an ADF JSON file to a Confluence page via REST API
|
|
@@ -23053,7 +23205,7 @@ SUBCOMMANDS
|
|
|
23053
23205
|
|
|
23054
23206
|
OPTIONS
|
|
23055
23207
|
--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)
|
|
23056
|
-
astro Themed Markdown (for
|
|
23208
|
+
astro Themed Markdown primitive (single aggregated page; for a full site use "build-docs")
|
|
23057
23209
|
confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
|
|
23058
23210
|
behavior-manifest-json Agent-readable behavior manifest and debugger warnings
|
|
23059
23211
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
@@ -23174,6 +23326,20 @@ DEPLOY
|
|
|
23174
23326
|
INIT-ASTRO
|
|
23175
23327
|
executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
|
|
23176
23328
|
--force Overwrite existing directory
|
|
23329
|
+
--update Refresh framework files only (keeps your content + config)
|
|
23330
|
+
|
|
23331
|
+
BUILD-DOCS
|
|
23332
|
+
Build the multi-page living-docs site from a raw run: one Astro page per story
|
|
23333
|
+
file plus Explorer data (scenario-links.json, story-report.json). Auto-pickup \u2014
|
|
23334
|
+
a new *.story.test.ts becomes a new page on the next run; deleting a story
|
|
23335
|
+
prunes its page. This is the headline living-docs flow; "format --format astro"
|
|
23336
|
+
is a low-level primitive that emits a single aggregated page, not a site.
|
|
23337
|
+
|
|
23338
|
+
executable-stories build-docs <raw-run.json> [--site-dir <dir>]
|
|
23339
|
+
--site-dir <dir> Target site dir (default: a scaffolded init-astro site)
|
|
23340
|
+
--openapi <spec> Link generated API pages to verifying stories
|
|
23341
|
+
--baseline <prev-report> Diff against a prior story-report.json for change markers
|
|
23342
|
+
--audience-split Split pages by audience (business vs technical)
|
|
23177
23343
|
|
|
23178
23344
|
PUBLISH-CONFLUENCE
|
|
23179
23345
|
executable-stories publish-confluence <file.adf.json> [options]
|
|
@@ -23280,12 +23446,17 @@ async function parseCliArgs(argv) {
|
|
|
23280
23446
|
console.log("To change theme, edit astro.config.mjs customCss array.");
|
|
23281
23447
|
console.log("");
|
|
23282
23448
|
console.log("Next steps:");
|
|
23283
|
-
console.log(` cd ${result.targetDir}`);
|
|
23284
|
-
console.log("
|
|
23285
|
-
console.log("
|
|
23286
|
-
console.log("");
|
|
23287
|
-
console.log("
|
|
23288
|
-
console.log(
|
|
23449
|
+
console.log(` 1. cd ${result.targetDir} && pnpm install # or npm install`);
|
|
23450
|
+
console.log(" 2. In your TEST project, add the StoryReporter with a rawRunPath, e.g.");
|
|
23451
|
+
console.log(" StoryReporter({ rawRunPath: 'reports/raw-run.json' })");
|
|
23452
|
+
console.log(" (this is what writes the raw run that build-docs reads)");
|
|
23453
|
+
console.log(" 3. Run your tests to produce reports/raw-run.json:");
|
|
23454
|
+
console.log(" pnpm test");
|
|
23455
|
+
console.log(" 4. Build the living-docs site (story pages, explorer data, API pages):");
|
|
23456
|
+
console.log(
|
|
23457
|
+
` executable-stories build-docs reports/raw-run.json --site-dir ${result.targetDir} [--openapi spec.json]`
|
|
23458
|
+
);
|
|
23459
|
+
console.log(` 5. Preview it: cd ${result.targetDir} && pnpm dev`);
|
|
23289
23460
|
console.log("");
|
|
23290
23461
|
console.log("Later, pull template/design improvements without losing your content:");
|
|
23291
23462
|
console.log(` executable-stories init-astro ${result.targetDir} --update`);
|
|
@@ -23583,20 +23754,20 @@ async function readInput(args) {
|
|
|
23583
23754
|
if (args.stdin) {
|
|
23584
23755
|
return readStdin();
|
|
23585
23756
|
}
|
|
23586
|
-
const filePath =
|
|
23587
|
-
if (!
|
|
23757
|
+
const filePath = path17.resolve(args.inputFile);
|
|
23758
|
+
if (!fs16.existsSync(filePath)) {
|
|
23588
23759
|
console.error(`Error: File not found: ${filePath}`);
|
|
23589
23760
|
process.exit(EXIT_USAGE);
|
|
23590
23761
|
}
|
|
23591
|
-
return
|
|
23762
|
+
return fs16.readFileSync(filePath, "utf8");
|
|
23592
23763
|
}
|
|
23593
23764
|
function readFileInput(filePath) {
|
|
23594
|
-
const resolved =
|
|
23595
|
-
if (!
|
|
23765
|
+
const resolved = path17.resolve(filePath);
|
|
23766
|
+
if (!fs16.existsSync(resolved)) {
|
|
23596
23767
|
console.error(`Error: File not found: ${resolved}`);
|
|
23597
23768
|
process.exit(EXIT_USAGE);
|
|
23598
23769
|
}
|
|
23599
|
-
return
|
|
23770
|
+
return fs16.readFileSync(resolved, "utf8");
|
|
23600
23771
|
}
|
|
23601
23772
|
function readStdin() {
|
|
23602
23773
|
return new Promise((resolve12, reject) => {
|
|
@@ -23729,14 +23900,14 @@ function tryNormalizeRunFromText(text2, args) {
|
|
|
23729
23900
|
}
|
|
23730
23901
|
}
|
|
23731
23902
|
function listBaselineCandidates(currentFile, args) {
|
|
23732
|
-
const baselineDir =
|
|
23733
|
-
const currentResolved =
|
|
23734
|
-
if (!
|
|
23903
|
+
const baselineDir = path17.resolve(args.baselineDir ?? path17.dirname(currentFile));
|
|
23904
|
+
const currentResolved = path17.resolve(currentFile);
|
|
23905
|
+
if (!fs16.existsSync(baselineDir)) {
|
|
23735
23906
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
23736
23907
|
process.exit(EXIT_USAGE);
|
|
23737
23908
|
}
|
|
23738
|
-
const entries =
|
|
23739
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
23909
|
+
const entries = fs16.readdirSync(baselineDir, { withFileTypes: true });
|
|
23910
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path17.join(baselineDir, entry.name)).filter((candidate) => path17.resolve(candidate) !== currentResolved).filter(
|
|
23740
23911
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
23741
23912
|
);
|
|
23742
23913
|
}
|
|
@@ -23744,14 +23915,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
23744
23915
|
const candidates = listBaselineCandidates(currentFile, args);
|
|
23745
23916
|
const comparable = [];
|
|
23746
23917
|
for (const candidate of candidates) {
|
|
23747
|
-
const run = tryNormalizeRunFromText(
|
|
23918
|
+
const run = tryNormalizeRunFromText(fs16.readFileSync(candidate, "utf8"), args);
|
|
23748
23919
|
if (run) {
|
|
23749
23920
|
comparable.push({ file: candidate, run });
|
|
23750
23921
|
}
|
|
23751
23922
|
}
|
|
23752
23923
|
if (comparable.length === 0) {
|
|
23753
23924
|
console.error(
|
|
23754
|
-
`Error: no compatible baseline files found in ${
|
|
23925
|
+
`Error: no compatible baseline files found in ${path17.resolve(args.baselineDir ?? path17.dirname(currentFile))}.`
|
|
23755
23926
|
);
|
|
23756
23927
|
process.exit(EXIT_USAGE);
|
|
23757
23928
|
}
|
|
@@ -23996,9 +24167,9 @@ async function main() {
|
|
|
23996
24167
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
23997
24168
|
}
|
|
23998
24169
|
if (args.emitCanonical) {
|
|
23999
|
-
const outPath =
|
|
24000
|
-
|
|
24001
|
-
|
|
24170
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24171
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24172
|
+
fs16.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
24002
24173
|
}
|
|
24003
24174
|
try {
|
|
24004
24175
|
const result = await generateReports(run, args);
|
|
@@ -24055,9 +24226,9 @@ ${msg}`);
|
|
|
24055
24226
|
}
|
|
24056
24227
|
const run = data;
|
|
24057
24228
|
if (args.emitCanonical) {
|
|
24058
|
-
const outPath =
|
|
24059
|
-
|
|
24060
|
-
|
|
24229
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24230
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24231
|
+
fs16.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
24061
24232
|
}
|
|
24062
24233
|
try {
|
|
24063
24234
|
const result = await generateReports(run, args);
|
|
@@ -24113,9 +24284,9 @@ ${msg}`);
|
|
|
24113
24284
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
24114
24285
|
}
|
|
24115
24286
|
if (args.emitCanonical) {
|
|
24116
|
-
const outPath =
|
|
24117
|
-
|
|
24118
|
-
|
|
24287
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24288
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24289
|
+
fs16.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
24119
24290
|
}
|
|
24120
24291
|
try {
|
|
24121
24292
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
@@ -24140,9 +24311,9 @@ function runCustomFormatters(run, customRequested, formatters, args) {
|
|
|
24140
24311
|
const ext = formatter.fileExtension ?? formatName;
|
|
24141
24312
|
const baseName = args.outputName ?? "report";
|
|
24142
24313
|
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
24143
|
-
const filepath =
|
|
24144
|
-
|
|
24145
|
-
|
|
24314
|
+
const filepath = path17.join(outputDir, filename);
|
|
24315
|
+
fs16.mkdirSync(outputDir, { recursive: true });
|
|
24316
|
+
fs16.writeFileSync(filepath, content, "utf8");
|
|
24146
24317
|
console.log(`Generated: ${filepath}`);
|
|
24147
24318
|
} catch (err) {
|
|
24148
24319
|
console.error(`Error running custom formatter "${formatName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24192,13 +24363,13 @@ async function dispatchNotifications(run, args) {
|
|
|
24192
24363
|
}
|
|
24193
24364
|
function runHistoryPipeline(run, args) {
|
|
24194
24365
|
if (!args.historyFile) return;
|
|
24195
|
-
const historyPath =
|
|
24366
|
+
const historyPath = path17.resolve(args.historyFile);
|
|
24196
24367
|
const store = loadHistory(
|
|
24197
24368
|
{ filePath: historyPath },
|
|
24198
24369
|
{
|
|
24199
24370
|
readFile: (p) => {
|
|
24200
24371
|
try {
|
|
24201
|
-
return
|
|
24372
|
+
return fs16.readFileSync(p, "utf8");
|
|
24202
24373
|
} catch {
|
|
24203
24374
|
return void 0;
|
|
24204
24375
|
}
|
|
@@ -24211,11 +24382,11 @@ function runHistoryPipeline(run, args) {
|
|
|
24211
24382
|
run,
|
|
24212
24383
|
maxRuns: args.maxHistoryRuns
|
|
24213
24384
|
});
|
|
24214
|
-
const dir =
|
|
24215
|
-
|
|
24385
|
+
const dir = path17.dirname(historyPath);
|
|
24386
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
24216
24387
|
saveHistory(
|
|
24217
24388
|
{ filePath: historyPath, store: updated },
|
|
24218
|
-
{ writeFile: (p, content) =>
|
|
24389
|
+
{ writeFile: (p, content) => fs16.writeFileSync(p, content, "utf8") }
|
|
24219
24390
|
);
|
|
24220
24391
|
let metricsCount = 0;
|
|
24221
24392
|
for (const testId of Object.keys(updated.tests)) {
|
|
@@ -24363,11 +24534,11 @@ function writeReviewReport(review, args) {
|
|
|
24363
24534
|
const outputDir = args.outputDir ?? "reports";
|
|
24364
24535
|
const baseName = args.outputName ?? "evidence-review";
|
|
24365
24536
|
const suffix = args.outputNameTimestamp ? `-${Math.floor(review.run.startedAtMs / 1e3)}` : "";
|
|
24366
|
-
|
|
24367
|
-
const mdPath =
|
|
24368
|
-
const htmlPath =
|
|
24369
|
-
|
|
24370
|
-
|
|
24537
|
+
fs16.mkdirSync(outputDir, { recursive: true });
|
|
24538
|
+
const mdPath = path17.join(outputDir, `${baseName}${suffix}.md`);
|
|
24539
|
+
const htmlPath = path17.join(outputDir, `${baseName}${suffix}.html`);
|
|
24540
|
+
fs16.writeFileSync(mdPath, markdown, "utf8");
|
|
24541
|
+
fs16.writeFileSync(htmlPath, html, "utf8");
|
|
24371
24542
|
return [mdPath, htmlPath];
|
|
24372
24543
|
}
|
|
24373
24544
|
function evaluateReviewGate(review, args) {
|
|
@@ -24413,9 +24584,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
24413
24584
|
function printCompareResult(result, args, startMs) {
|
|
24414
24585
|
const durationMs = Date.now() - startMs;
|
|
24415
24586
|
if (result.prSummary && args.prSummaryFile) {
|
|
24416
|
-
const outputPath =
|
|
24417
|
-
|
|
24418
|
-
|
|
24587
|
+
const outputPath = path17.resolve(args.prSummaryFile);
|
|
24588
|
+
fs16.mkdirSync(path17.dirname(outputPath), { recursive: true });
|
|
24589
|
+
fs16.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
24419
24590
|
}
|
|
24420
24591
|
if (args.jsonSummary) {
|
|
24421
24592
|
console.log(
|
|
@@ -24444,13 +24615,13 @@ function printCompareResult(result, args, startMs) {
|
|
|
24444
24615
|
}
|
|
24445
24616
|
}
|
|
24446
24617
|
function loadReleasePolicy(policyPath) {
|
|
24447
|
-
const resolved =
|
|
24448
|
-
if (!
|
|
24618
|
+
const resolved = path17.resolve(policyPath);
|
|
24619
|
+
if (!fs16.existsSync(resolved)) {
|
|
24449
24620
|
console.error(`Error: release policy file not found: ${resolved}`);
|
|
24450
24621
|
process.exit(EXIT_USAGE);
|
|
24451
24622
|
}
|
|
24452
24623
|
try {
|
|
24453
|
-
const raw = JSON.parse(
|
|
24624
|
+
const raw = JSON.parse(fs16.readFileSync(resolved, "utf8"));
|
|
24454
24625
|
return {
|
|
24455
24626
|
allowedOmissions: Array.isArray(raw.allowedOmissions) ? raw.allowedOmissions : [],
|
|
24456
24627
|
allowedRegressions: Array.isArray(raw.allowedRegressions) ? raw.allowedRegressions : [],
|
|
@@ -24554,7 +24725,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24554
24725
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
24555
24726
|
process.exit(EXIT_USAGE);
|
|
24556
24727
|
}
|
|
24557
|
-
if (!
|
|
24728
|
+
if (!fs16.existsSync(inputFile)) {
|
|
24558
24729
|
console.error(`Error: file not found: ${inputFile}`);
|
|
24559
24730
|
process.exit(EXIT_USAGE);
|
|
24560
24731
|
}
|
|
@@ -24582,7 +24753,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24582
24753
|
console.error("Error: --title is required when creating a new page");
|
|
24583
24754
|
process.exit(EXIT_USAGE);
|
|
24584
24755
|
}
|
|
24585
|
-
const adf =
|
|
24756
|
+
const adf = fs16.readFileSync(path17.resolve(inputFile), "utf8");
|
|
24586
24757
|
if (dryRun) {
|
|
24587
24758
|
console.log(
|
|
24588
24759
|
JSON.stringify(
|
|
@@ -24661,7 +24832,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24661
24832
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
24662
24833
|
process.exit(EXIT_USAGE);
|
|
24663
24834
|
}
|
|
24664
|
-
if (!
|
|
24835
|
+
if (!fs16.existsSync(inputFile)) {
|
|
24665
24836
|
console.error(`Error: file not found: ${inputFile}`);
|
|
24666
24837
|
process.exit(EXIT_USAGE);
|
|
24667
24838
|
}
|
|
@@ -24688,7 +24859,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24688
24859
|
process.exit(EXIT_USAGE);
|
|
24689
24860
|
}
|
|
24690
24861
|
const mode = modeRaw;
|
|
24691
|
-
const adf =
|
|
24862
|
+
const adf = fs16.readFileSync(path17.resolve(inputFile), "utf8");
|
|
24692
24863
|
if (dryRun) {
|
|
24693
24864
|
console.log(
|
|
24694
24865
|
JSON.stringify(
|
|
@@ -24732,14 +24903,20 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24732
24903
|
function runNew(rawArgs) {
|
|
24733
24904
|
const { values, positionals } = parseArgs({
|
|
24734
24905
|
args: rawArgs,
|
|
24735
|
-
options: {
|
|
24906
|
+
options: {
|
|
24907
|
+
dir: { type: "string" },
|
|
24908
|
+
force: { type: "boolean", default: false },
|
|
24909
|
+
"scenario-id": { type: "string" }
|
|
24910
|
+
},
|
|
24736
24911
|
allowPositionals: true,
|
|
24737
24912
|
strict: true
|
|
24738
24913
|
});
|
|
24739
24914
|
const template = positionals[0];
|
|
24740
24915
|
const name = positionals.slice(1).join(" ");
|
|
24741
24916
|
if (!template) {
|
|
24742
|
-
console.error(
|
|
24917
|
+
console.error(
|
|
24918
|
+
`Usage: executable-stories new <template> "<name>" [--dir <docs-dir>] [--scenario-id <id>] [--force]`
|
|
24919
|
+
);
|
|
24743
24920
|
console.error(`Templates: ${TEMPLATES.join(", ")}`);
|
|
24744
24921
|
return EXIT_USAGE;
|
|
24745
24922
|
}
|
|
@@ -24747,6 +24924,7 @@ function runNew(rawArgs) {
|
|
|
24747
24924
|
const result = scaffoldDoc({
|
|
24748
24925
|
template,
|
|
24749
24926
|
name,
|
|
24927
|
+
scenarioId: values["scenario-id"],
|
|
24750
24928
|
baseDir: values.dir,
|
|
24751
24929
|
force: values.force
|
|
24752
24930
|
});
|
|
@@ -24849,6 +25027,7 @@ async function runBuildDocs(rawArgs) {
|
|
|
24849
25027
|
console.log(`\u2713 Living docs generated in ${result.siteDir}`);
|
|
24850
25028
|
console.log(` \u2022 Explorer data \u2192 public/stories/story-report.json`);
|
|
24851
25029
|
console.log(` \u2022 Deep links \u2192 public/stories/scenario-links.json (${result.scenarioLinks})`);
|
|
25030
|
+
console.log(` \u2022 Note links \u2192 public/stories/notes-index.json (${result.notesIndexed})`);
|
|
24852
25031
|
if (audienceSplit) {
|
|
24853
25032
|
console.log(
|
|
24854
25033
|
` \u2022 Story pages \u2192 src/content/docs/stories/{engineer,stakeholder} (engineer: ${result.audiences.engineer}, stakeholder: ${result.audiences.stakeholder})`
|
|
@@ -24868,7 +25047,7 @@ async function runBuildDocs(rawArgs) {
|
|
|
24868
25047
|
` \u2022 What's changed \u2192 src/content/docs/stories/changes.md (+${c.added} added, ${c.regressed} regressed, ${c.fixed} fixed, ${c.removed} removed)`
|
|
24869
25048
|
);
|
|
24870
25049
|
}
|
|
24871
|
-
const rel =
|
|
25050
|
+
const rel = path17.relative(process.cwd(), result.siteDir) || ".";
|
|
24872
25051
|
console.log(`
|
|
24873
25052
|
Preview: cd ${rel} && npm run dev`);
|
|
24874
25053
|
return EXIT_SUCCESS;
|