executable-stories-formatters 0.13.0 → 0.15.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 +706 -236
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +49 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +49 -52
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
5
|
+
import * as 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
|
}
|
|
@@ -1237,7 +1237,7 @@ var CucumberJsonFormatter = class {
|
|
|
1237
1237
|
/**
|
|
1238
1238
|
* Build a single step.
|
|
1239
1239
|
*/
|
|
1240
|
-
buildStep(step, result,
|
|
1240
|
+
buildStep(step, result, line2, index, attachments, isLastStep, hasFailedStep) {
|
|
1241
1241
|
const keyword = this.options.keywordSpacing ? `${step.keyword} ` : step.keyword;
|
|
1242
1242
|
const stepResult = this.buildStepResult(result);
|
|
1243
1243
|
const embeddings = this.buildEmbeddings(attachments, result, isLastStep, hasFailedStep);
|
|
@@ -1245,7 +1245,7 @@ var CucumberJsonFormatter = class {
|
|
|
1245
1245
|
embeddings.push(...screenshotEmbeddings);
|
|
1246
1246
|
const jsonStep = {
|
|
1247
1247
|
keyword,
|
|
1248
|
-
line,
|
|
1248
|
+
line: line2,
|
|
1249
1249
|
name: step.text,
|
|
1250
1250
|
result: stepResult
|
|
1251
1251
|
};
|
|
@@ -15792,8 +15792,8 @@ var JUnitFormatter = class {
|
|
|
15792
15792
|
case "section": {
|
|
15793
15793
|
const lines = [];
|
|
15794
15794
|
lines.push(`${indent}${entry.title}:`);
|
|
15795
|
-
for (const
|
|
15796
|
-
lines.push(`${indent} ${
|
|
15795
|
+
for (const line2 of entry.markdown.split("\n")) {
|
|
15796
|
+
lines.push(`${indent} ${line2}`);
|
|
15797
15797
|
}
|
|
15798
15798
|
return lines.join("\n");
|
|
15799
15799
|
}
|
|
@@ -15802,8 +15802,8 @@ var JUnitFormatter = class {
|
|
|
15802
15802
|
if (entry.title) {
|
|
15803
15803
|
lines.push(`${indent}${entry.title}:`);
|
|
15804
15804
|
}
|
|
15805
|
-
for (const
|
|
15806
|
-
lines.push(`${indent} ${
|
|
15805
|
+
for (const line2 of entry.code.split("\n")) {
|
|
15806
|
+
lines.push(`${indent} ${line2}`);
|
|
15807
15807
|
}
|
|
15808
15808
|
return lines.join("\n");
|
|
15809
15809
|
}
|
|
@@ -15815,8 +15815,8 @@ var JUnitFormatter = class {
|
|
|
15815
15815
|
const dataStr = JSON.stringify(entry.data, null, 2);
|
|
15816
15816
|
const lines = [];
|
|
15817
15817
|
lines.push(`${indent}[${entry.type}]:`);
|
|
15818
|
-
for (const
|
|
15819
|
-
lines.push(`${indent} ${
|
|
15818
|
+
for (const line2 of dataStr.split("\n")) {
|
|
15819
|
+
lines.push(`${indent} ${line2}`);
|
|
15820
15820
|
}
|
|
15821
15821
|
return lines.join("\n");
|
|
15822
15822
|
}
|
|
@@ -15862,7 +15862,10 @@ var MarkdownFormatter = class {
|
|
|
15862
15862
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
15863
15863
|
traceUrlTemplate: options.traceUrlTemplate,
|
|
15864
15864
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
15865
|
-
customRenderers: options.customRenderers
|
|
15865
|
+
customRenderers: options.customRenderers,
|
|
15866
|
+
scenarioAnchor: options.scenarioAnchor,
|
|
15867
|
+
scenarioBadge: options.scenarioBadge,
|
|
15868
|
+
scenarioNoteLink: options.scenarioNoteLink
|
|
15866
15869
|
};
|
|
15867
15870
|
}
|
|
15868
15871
|
/**
|
|
@@ -16049,6 +16052,11 @@ var MarkdownFormatter = class {
|
|
|
16049
16052
|
* Render a single scenario.
|
|
16050
16053
|
*/
|
|
16051
16054
|
renderScenario(lines, tc) {
|
|
16055
|
+
const anchorId = this.options.scenarioAnchor?.(tc);
|
|
16056
|
+
if (anchorId) {
|
|
16057
|
+
lines.push(`<a id="${anchorId}"></a>`);
|
|
16058
|
+
lines.push("");
|
|
16059
|
+
}
|
|
16052
16060
|
if (this.options.customRenderers?.renderScenarioHeader) {
|
|
16053
16061
|
const custom = this.options.customRenderers.renderScenarioHeader(tc);
|
|
16054
16062
|
if (custom !== null) {
|
|
@@ -16064,6 +16072,14 @@ var MarkdownFormatter = class {
|
|
|
16064
16072
|
icon = this.getStatusIcon(tc.status) + " ";
|
|
16065
16073
|
}
|
|
16066
16074
|
lines.push(`${headingPrefix} ${icon}${tc.story.scenario}`);
|
|
16075
|
+
const badge2 = this.options.scenarioBadge?.(tc);
|
|
16076
|
+
if (badge2) {
|
|
16077
|
+
lines.push(badge2);
|
|
16078
|
+
}
|
|
16079
|
+
const noteLink = this.options.scenarioNoteLink?.(tc);
|
|
16080
|
+
if (noteLink) {
|
|
16081
|
+
lines.push(noteLink);
|
|
16082
|
+
}
|
|
16067
16083
|
if (this.options.includeSourceLinks && this.options.permalinkBaseUrl && tc.sourceFile !== "unknown") {
|
|
16068
16084
|
const permalink = this.buildPermalink(tc);
|
|
16069
16085
|
lines.push(`Source: [${tc.sourceFile}](${permalink})`);
|
|
@@ -16138,8 +16154,8 @@ var MarkdownFormatter = class {
|
|
|
16138
16154
|
buildPermalink(tc) {
|
|
16139
16155
|
const base = this.options.permalinkBaseUrl.replace(/\/$/, "");
|
|
16140
16156
|
const file = tc.sourceFile;
|
|
16141
|
-
const
|
|
16142
|
-
return `${base}/${file}${
|
|
16157
|
+
const line2 = tc.sourceLine > 0 ? `#L${tc.sourceLine}` : "";
|
|
16158
|
+
return `${base}/${file}${line2}`;
|
|
16143
16159
|
}
|
|
16144
16160
|
/**
|
|
16145
16161
|
* Render a step.
|
|
@@ -16207,8 +16223,8 @@ var MarkdownFormatter = class {
|
|
|
16207
16223
|
lines.push(`${indent}`);
|
|
16208
16224
|
}
|
|
16209
16225
|
lines.push(`${indent}\`\`\`${entry.lang ?? ""}`);
|
|
16210
|
-
for (const
|
|
16211
|
-
lines.push(`${indent}${
|
|
16226
|
+
for (const line2 of (entry.content ?? "").split("\n")) {
|
|
16227
|
+
lines.push(`${indent}${line2}`);
|
|
16212
16228
|
}
|
|
16213
16229
|
lines.push(`${indent}\`\`\``);
|
|
16214
16230
|
lines.push(`${indent}`);
|
|
@@ -16231,8 +16247,8 @@ var MarkdownFormatter = class {
|
|
|
16231
16247
|
case "section":
|
|
16232
16248
|
lines.push(`${indent}**${entry.title}**`);
|
|
16233
16249
|
lines.push(`${indent}`);
|
|
16234
|
-
for (const
|
|
16235
|
-
lines.push(`${indent}${
|
|
16250
|
+
for (const line2 of (entry.markdown ?? "").split("\n")) {
|
|
16251
|
+
lines.push(`${indent}${line2}`);
|
|
16236
16252
|
}
|
|
16237
16253
|
lines.push(`${indent}`);
|
|
16238
16254
|
break;
|
|
@@ -16241,8 +16257,8 @@ var MarkdownFormatter = class {
|
|
|
16241
16257
|
lines.push(`${indent}**${entry.title}**`);
|
|
16242
16258
|
}
|
|
16243
16259
|
lines.push(`${indent}\`\`\`mermaid`);
|
|
16244
|
-
for (const
|
|
16245
|
-
lines.push(`${indent}${
|
|
16260
|
+
for (const line2 of (entry.code ?? "").split("\n")) {
|
|
16261
|
+
lines.push(`${indent}${line2}`);
|
|
16246
16262
|
}
|
|
16247
16263
|
lines.push(`${indent}\`\`\``);
|
|
16248
16264
|
break;
|
|
@@ -16272,8 +16288,8 @@ var MarkdownFormatter = class {
|
|
|
16272
16288
|
lines.push(`${indent}<summary>${htmlLabel}</summary>`);
|
|
16273
16289
|
lines.push(`${indent}`);
|
|
16274
16290
|
lines.push(`${indent}\`\`\`html`);
|
|
16275
|
-
for (const
|
|
16276
|
-
lines.push(`${indent}${
|
|
16291
|
+
for (const line2 of (entry.content ?? "").split("\n")) {
|
|
16292
|
+
lines.push(`${indent}${line2}`);
|
|
16277
16293
|
}
|
|
16278
16294
|
lines.push(`${indent}\`\`\``);
|
|
16279
16295
|
lines.push(`${indent}`);
|
|
@@ -16295,8 +16311,8 @@ var MarkdownFormatter = class {
|
|
|
16295
16311
|
lines.push(`${indent}**[${entry.type}]**`);
|
|
16296
16312
|
lines.push(`${indent}`);
|
|
16297
16313
|
lines.push(`${indent}\`\`\`json`);
|
|
16298
|
-
for (const
|
|
16299
|
-
lines.push(`${indent}${
|
|
16314
|
+
for (const line2 of JSON.stringify(entry.data ?? null, null, 2).split("\n")) {
|
|
16315
|
+
lines.push(`${indent}${line2}`);
|
|
16300
16316
|
}
|
|
16301
16317
|
lines.push(`${indent}\`\`\``);
|
|
16302
16318
|
lines.push(`${indent}`);
|
|
@@ -16479,14 +16495,14 @@ var TraceabilityMatrixFormatter = class {
|
|
|
16479
16495
|
lines.push("");
|
|
16480
16496
|
lines.push(`Status: ${renderRequirementStatus(req.status)}`);
|
|
16481
16497
|
if (req.covers.length > 0) {
|
|
16482
|
-
lines.push(`Covers: ${req.covers.map((
|
|
16498
|
+
lines.push(`Covers: ${req.covers.map((path18) => `\`${path18}\``).join(", ")}`);
|
|
16483
16499
|
}
|
|
16484
16500
|
lines.push("");
|
|
16485
16501
|
lines.push("| Status | Scenario | Source | Covers |");
|
|
16486
16502
|
lines.push("| --- | --- | --- | --- |");
|
|
16487
16503
|
for (const scenario of req.scenarios) {
|
|
16488
16504
|
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
16489
|
-
const covers = scenario.covers.length > 0 ? scenario.covers.map((
|
|
16505
|
+
const covers = scenario.covers.length > 0 ? scenario.covers.map((path18) => `\`${path18}\``).join(", ") : "";
|
|
16490
16506
|
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
|
|
16491
16507
|
}
|
|
16492
16508
|
lines.push("");
|
|
@@ -16588,8 +16604,8 @@ function extractFeatureName(testCases, uri) {
|
|
|
16588
16604
|
return tc.titlePath[0];
|
|
16589
16605
|
}
|
|
16590
16606
|
}
|
|
16591
|
-
const
|
|
16592
|
-
return
|
|
16607
|
+
const basename5 = uri.replace(/^.*[\\/]/, "").replace(/\.[^.]+$/, "");
|
|
16608
|
+
return basename5.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
16593
16609
|
}
|
|
16594
16610
|
function synthesizeFeature(uri, testCases) {
|
|
16595
16611
|
const featureName = extractFeatureName(testCases, uri);
|
|
@@ -16807,60 +16823,60 @@ function buildStepArguments(step, stepLine) {
|
|
|
16807
16823
|
}
|
|
16808
16824
|
return {};
|
|
16809
16825
|
}
|
|
16810
|
-
function docEntryToDocString(doc,
|
|
16826
|
+
function docEntryToDocString(doc, line2) {
|
|
16811
16827
|
switch (doc.kind) {
|
|
16812
16828
|
case "code":
|
|
16813
16829
|
return {
|
|
16814
|
-
location: { line },
|
|
16830
|
+
location: { line: line2 },
|
|
16815
16831
|
mediaType: doc.lang,
|
|
16816
16832
|
content: doc.content,
|
|
16817
16833
|
delimiter: '"""'
|
|
16818
16834
|
};
|
|
16819
16835
|
case "note":
|
|
16820
16836
|
return {
|
|
16821
|
-
location: { line },
|
|
16837
|
+
location: { line: line2 },
|
|
16822
16838
|
mediaType: "text/plain",
|
|
16823
16839
|
content: doc.text,
|
|
16824
16840
|
delimiter: '"""'
|
|
16825
16841
|
};
|
|
16826
16842
|
case "section":
|
|
16827
16843
|
return {
|
|
16828
|
-
location: { line },
|
|
16844
|
+
location: { line: line2 },
|
|
16829
16845
|
mediaType: "text/markdown",
|
|
16830
16846
|
content: doc.markdown,
|
|
16831
16847
|
delimiter: '"""'
|
|
16832
16848
|
};
|
|
16833
16849
|
case "mermaid":
|
|
16834
16850
|
return {
|
|
16835
|
-
location: { line },
|
|
16851
|
+
location: { line: line2 },
|
|
16836
16852
|
mediaType: "text/x-mermaid",
|
|
16837
16853
|
content: doc.code,
|
|
16838
16854
|
delimiter: '"""'
|
|
16839
16855
|
};
|
|
16840
16856
|
case "kv":
|
|
16841
16857
|
return {
|
|
16842
|
-
location: { line },
|
|
16858
|
+
location: { line: line2 },
|
|
16843
16859
|
mediaType: "text/plain",
|
|
16844
16860
|
content: `${doc.label}: ${typeof doc.value === "string" ? doc.value : JSON.stringify(doc.value)}`,
|
|
16845
16861
|
delimiter: '"""'
|
|
16846
16862
|
};
|
|
16847
16863
|
case "link":
|
|
16848
16864
|
return {
|
|
16849
|
-
location: { line },
|
|
16865
|
+
location: { line: line2 },
|
|
16850
16866
|
mediaType: "text/markdown",
|
|
16851
16867
|
content: `[${doc.label}](${doc.url})`,
|
|
16852
16868
|
delimiter: '"""'
|
|
16853
16869
|
};
|
|
16854
16870
|
case "custom":
|
|
16855
16871
|
return {
|
|
16856
|
-
location: { line },
|
|
16872
|
+
location: { line: line2 },
|
|
16857
16873
|
mediaType: "application/json",
|
|
16858
16874
|
content: JSON.stringify(doc.data, null, 2),
|
|
16859
16875
|
delimiter: '"""'
|
|
16860
16876
|
};
|
|
16861
16877
|
case "tag":
|
|
16862
16878
|
return {
|
|
16863
|
-
location: { line },
|
|
16879
|
+
location: { line: line2 },
|
|
16864
16880
|
mediaType: "text/plain",
|
|
16865
16881
|
content: doc.names.map((n) => `@${n}`).join(" "),
|
|
16866
16882
|
delimiter: '"""'
|
|
@@ -16870,18 +16886,18 @@ function docEntryToDocString(doc, line) {
|
|
|
16870
16886
|
return void 0;
|
|
16871
16887
|
}
|
|
16872
16888
|
}
|
|
16873
|
-
function buildDataTable(table2,
|
|
16889
|
+
function buildDataTable(table2, line2) {
|
|
16874
16890
|
const rows = [];
|
|
16875
16891
|
rows.push({
|
|
16876
|
-
location: { line },
|
|
16892
|
+
location: { line: line2 },
|
|
16877
16893
|
cells: table2.columns.map((col) => ({
|
|
16878
|
-
location: { line },
|
|
16894
|
+
location: { line: line2 },
|
|
16879
16895
|
value: col
|
|
16880
16896
|
})),
|
|
16881
16897
|
id: ""
|
|
16882
16898
|
});
|
|
16883
16899
|
for (let r = 0; r < table2.rows.length; r++) {
|
|
16884
|
-
const rowLine =
|
|
16900
|
+
const rowLine = line2 + 1 + r;
|
|
16885
16901
|
rows.push({
|
|
16886
16902
|
location: { line: rowLine },
|
|
16887
16903
|
cells: table2.rows[r].map((cell) => ({
|
|
@@ -16892,7 +16908,7 @@ function buildDataTable(table2, line) {
|
|
|
16892
16908
|
});
|
|
16893
16909
|
}
|
|
16894
16910
|
return {
|
|
16895
|
-
location: { line },
|
|
16911
|
+
location: { line: line2 },
|
|
16896
16912
|
rows
|
|
16897
16913
|
};
|
|
16898
16914
|
}
|
|
@@ -17201,8 +17217,8 @@ function extractDocAttachments(step) {
|
|
|
17201
17217
|
}
|
|
17202
17218
|
return attachments;
|
|
17203
17219
|
}
|
|
17204
|
-
function guessMediaType(
|
|
17205
|
-
const lower =
|
|
17220
|
+
function guessMediaType(path18) {
|
|
17221
|
+
const lower = path18.toLowerCase();
|
|
17206
17222
|
if (lower.endsWith(".png")) return "image/png";
|
|
17207
17223
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
17208
17224
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -18444,17 +18460,17 @@ ${body}`;
|
|
|
18444
18460
|
return humanizeSourceFile([...sourceFiles][0]) || this.title;
|
|
18445
18461
|
}
|
|
18446
18462
|
buildFrontmatter(run) {
|
|
18447
|
-
const
|
|
18463
|
+
const badge2 = _AstroFormatter.computeBadge(run.testCases);
|
|
18448
18464
|
const count2 = run.testCases.length;
|
|
18449
|
-
const description = `${count2} scenario${count2 !== 1 ? "s" : ""} \u2014 ${
|
|
18465
|
+
const description = `${count2} scenario${count2 !== 1 ? "s" : ""} \u2014 ${badge2.text.toLowerCase()}`;
|
|
18450
18466
|
const lines = [
|
|
18451
18467
|
"---",
|
|
18452
18468
|
`title: ${yamlScalar(this.deriveTitle(run))}`,
|
|
18453
18469
|
`description: ${description}`,
|
|
18454
18470
|
"sidebar:",
|
|
18455
18471
|
" badge:",
|
|
18456
|
-
` text: ${
|
|
18457
|
-
` variant: ${
|
|
18472
|
+
` text: ${badge2.text}`,
|
|
18473
|
+
` variant: ${badge2.variant}`,
|
|
18458
18474
|
"---"
|
|
18459
18475
|
];
|
|
18460
18476
|
return lines.join("\n");
|
|
@@ -18910,12 +18926,16 @@ function groupBy7(items, keyFn) {
|
|
|
18910
18926
|
import * as fs5 from "fs";
|
|
18911
18927
|
import * as path6 from "path";
|
|
18912
18928
|
var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
|
|
18913
|
-
function
|
|
18929
|
+
function isRemoteRef(src) {
|
|
18914
18930
|
const trimmed = src.trim();
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18931
|
+
return SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
|
|
18932
|
+
}
|
|
18933
|
+
function isAbsoluteRef(src) {
|
|
18934
|
+
const trimmed = src.trim();
|
|
18935
|
+
return path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed);
|
|
18936
|
+
}
|
|
18937
|
+
function isRelativeLocalPath(src) {
|
|
18938
|
+
return !isRemoteRef(src) && !isAbsoluteRef(src);
|
|
18919
18939
|
}
|
|
18920
18940
|
function stripCodeContent(markdown) {
|
|
18921
18941
|
let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
@@ -18931,21 +18951,21 @@ function scanMarkdownAssets(markdown) {
|
|
|
18931
18951
|
let match;
|
|
18932
18952
|
while ((match = mdImageRe.exec(stripped)) !== null) {
|
|
18933
18953
|
const src = match[1].trim();
|
|
18934
|
-
if (
|
|
18954
|
+
if (!isRemoteRef(src)) {
|
|
18935
18955
|
found.add(src);
|
|
18936
18956
|
}
|
|
18937
18957
|
}
|
|
18938
18958
|
const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
|
|
18939
18959
|
while ((match = htmlSrcRe.exec(stripped)) !== null) {
|
|
18940
18960
|
const src = match[1].trim();
|
|
18941
|
-
if (
|
|
18961
|
+
if (!isRemoteRef(src)) {
|
|
18942
18962
|
found.add(src);
|
|
18943
18963
|
}
|
|
18944
18964
|
}
|
|
18945
18965
|
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
18946
18966
|
while ((match = posterRe.exec(stripped)) !== null) {
|
|
18947
18967
|
const src = match[1].trim();
|
|
18948
|
-
if (
|
|
18968
|
+
if (!isRemoteRef(src)) {
|
|
18949
18969
|
found.add(src);
|
|
18950
18970
|
}
|
|
18951
18971
|
}
|
|
@@ -18971,48 +18991,21 @@ function isCode(segment) {
|
|
|
18971
18991
|
const trimmed = segment.trimStart();
|
|
18972
18992
|
return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
|
|
18973
18993
|
}
|
|
18994
|
+
function resolveRewrite(trimmed, assetsBaseUrl, pathMap) {
|
|
18995
|
+
if (isRemoteRef(trimmed)) return null;
|
|
18996
|
+
if (pathMap) {
|
|
18997
|
+
const mapped = pathMap.get(trimmed);
|
|
18998
|
+
return mapped === void 0 ? null : `${assetsBaseUrl}/${mapped}`;
|
|
18999
|
+
}
|
|
19000
|
+
if (!isRelativeLocalPath(trimmed)) return null;
|
|
19001
|
+
return `${assetsBaseUrl}/${trimmed}`;
|
|
19002
|
+
}
|
|
18974
19003
|
function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
18980
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18981
|
-
if (pathMap) {
|
|
18982
|
-
const mapped = pathMap.get(trimmed);
|
|
18983
|
-
if (mapped === void 0) return full;
|
|
18984
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18985
|
-
}
|
|
18986
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18987
|
-
}
|
|
18988
|
-
);
|
|
18989
|
-
result = result.replace(
|
|
18990
|
-
/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18991
|
-
(full, pre, src, post) => {
|
|
18992
|
-
const trimmed = src.trim();
|
|
18993
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18994
|
-
if (pathMap) {
|
|
18995
|
-
const mapped = pathMap.get(trimmed);
|
|
18996
|
-
if (mapped === void 0) return full;
|
|
18997
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18998
|
-
}
|
|
18999
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
19000
|
-
}
|
|
19001
|
-
);
|
|
19002
|
-
result = result.replace(
|
|
19003
|
-
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
19004
|
-
(full, pre, src, post) => {
|
|
19005
|
-
const trimmed = src.trim();
|
|
19006
|
-
if (!isLocalPath(trimmed)) return full;
|
|
19007
|
-
if (pathMap) {
|
|
19008
|
-
const mapped = pathMap.get(trimmed);
|
|
19009
|
-
if (mapped === void 0) return full;
|
|
19010
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
19011
|
-
}
|
|
19012
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
19013
|
-
}
|
|
19014
|
-
);
|
|
19015
|
-
return result;
|
|
19004
|
+
const rewrite = (full, pre, src, post) => {
|
|
19005
|
+
const target = resolveRewrite(src.trim(), assetsBaseUrl, pathMap);
|
|
19006
|
+
return target === null ? full : `${pre}${target}${post}`;
|
|
19007
|
+
};
|
|
19008
|
+
return prose.replace(/(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g, rewrite).replace(/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi, rewrite).replace(/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi, rewrite);
|
|
19016
19009
|
}
|
|
19017
19010
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
19018
19011
|
return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
|
|
@@ -19029,8 +19022,9 @@ function copyMarkdownAssets(options) {
|
|
|
19029
19022
|
const pathMap = /* @__PURE__ */ new Map();
|
|
19030
19023
|
const missing = [];
|
|
19031
19024
|
for (const ref of refs) {
|
|
19032
|
-
const absPath = path6.resolve(markdownDir, ref);
|
|
19025
|
+
const absPath = isAbsoluteRef(ref) ? ref : path6.resolve(markdownDir, ref);
|
|
19033
19026
|
if (!fs5.existsSync(absPath)) {
|
|
19027
|
+
if (isAbsoluteRef(ref)) continue;
|
|
19034
19028
|
if (!allowMissing) {
|
|
19035
19029
|
throw new Error(`Asset not found: ${absPath}`);
|
|
19036
19030
|
}
|
|
@@ -19112,6 +19106,47 @@ function startWatch(options, deps = {}) {
|
|
|
19112
19106
|
};
|
|
19113
19107
|
}
|
|
19114
19108
|
|
|
19109
|
+
// src/behavior-diff.ts
|
|
19110
|
+
function classifyStatusChange(baseline, current) {
|
|
19111
|
+
if (baseline === void 0) return "added";
|
|
19112
|
+
if (current === void 0) return "removed";
|
|
19113
|
+
if (baseline === current) return "unchanged";
|
|
19114
|
+
if (baseline === "passed" && current === "failed") return "regressed";
|
|
19115
|
+
if (baseline === "failed" && current === "passed") return "fixed";
|
|
19116
|
+
return "changed";
|
|
19117
|
+
}
|
|
19118
|
+
function scenarioMap(report) {
|
|
19119
|
+
const map = /* @__PURE__ */ new Map();
|
|
19120
|
+
for (const feature of report.features) {
|
|
19121
|
+
for (const scenario of feature.scenarios) {
|
|
19122
|
+
map.set(scenario.id, { scenario, sourceFile: feature.sourceFile });
|
|
19123
|
+
}
|
|
19124
|
+
}
|
|
19125
|
+
return map;
|
|
19126
|
+
}
|
|
19127
|
+
function diffStoryReports(baseline, current) {
|
|
19128
|
+
const base = scenarioMap(baseline);
|
|
19129
|
+
const curr = scenarioMap(current);
|
|
19130
|
+
const ids = [.../* @__PURE__ */ new Set([...base.keys(), ...curr.keys()])];
|
|
19131
|
+
const scenarios = ids.map((id) => {
|
|
19132
|
+
const b = base.get(id);
|
|
19133
|
+
const c = curr.get(id);
|
|
19134
|
+
const kind = classifyStatusChange(b?.scenario.status, c?.scenario.status);
|
|
19135
|
+
const meta = c ?? b;
|
|
19136
|
+
return {
|
|
19137
|
+
id,
|
|
19138
|
+
title: meta.scenario.title,
|
|
19139
|
+
sourceFile: meta.sourceFile,
|
|
19140
|
+
kind,
|
|
19141
|
+
baselineStatus: b?.scenario.status,
|
|
19142
|
+
currentStatus: c?.scenario.status
|
|
19143
|
+
};
|
|
19144
|
+
});
|
|
19145
|
+
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
19146
|
+
for (const s of scenarios) summary[s.kind] += 1;
|
|
19147
|
+
return { schemaVersion: "1.0", summary, scenarios };
|
|
19148
|
+
}
|
|
19149
|
+
|
|
19115
19150
|
// src/publishers/confluence.ts
|
|
19116
19151
|
function parseAdf(adf) {
|
|
19117
19152
|
let parsed;
|
|
@@ -19344,7 +19379,7 @@ async function updateDescription(issueKey, base, adf, headers, fetchFn) {
|
|
|
19344
19379
|
// src/converters/ndjson-parser.ts
|
|
19345
19380
|
function parseNdjson(ndjson) {
|
|
19346
19381
|
const lines = ndjson.trim().split("\n").filter(Boolean);
|
|
19347
|
-
const envelopes = lines.map((
|
|
19382
|
+
const envelopes = lines.map((line2) => JSON.parse(line2));
|
|
19348
19383
|
return parseEnvelopes(envelopes);
|
|
19349
19384
|
}
|
|
19350
19385
|
function parseEnvelopes(envelopes) {
|
|
@@ -20717,18 +20752,18 @@ function deriveChangeType(tags) {
|
|
|
20717
20752
|
}
|
|
20718
20753
|
return "unknown";
|
|
20719
20754
|
}
|
|
20720
|
-
function extensionOf(
|
|
20721
|
-
const base =
|
|
20755
|
+
function extensionOf(path18) {
|
|
20756
|
+
const base = path18.split("/").pop() ?? path18;
|
|
20722
20757
|
const dot = base.lastIndexOf(".");
|
|
20723
20758
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
20724
20759
|
}
|
|
20725
|
-
function isTestFile(
|
|
20726
|
-
return TEST_INFIX.test(
|
|
20760
|
+
function isTestFile(path18) {
|
|
20761
|
+
return TEST_INFIX.test(path18);
|
|
20727
20762
|
}
|
|
20728
|
-
function isReviewableSource(
|
|
20729
|
-
if (isTestFile(
|
|
20730
|
-
if (
|
|
20731
|
-
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));
|
|
20732
20767
|
}
|
|
20733
20768
|
function testBaseKey(testFile) {
|
|
20734
20769
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -20832,7 +20867,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
20832
20867
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
20833
20868
|
const key = testBaseKey(testCase.sourceFile);
|
|
20834
20869
|
const coversFiles = changedSourcePaths.filter(
|
|
20835
|
-
(
|
|
20870
|
+
(path18) => sourceBaseKey(path18) === key
|
|
20836
20871
|
);
|
|
20837
20872
|
return {
|
|
20838
20873
|
id: testCase.id,
|
|
@@ -21695,7 +21730,10 @@ var ReportGenerator = class {
|
|
|
21695
21730
|
permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
|
|
21696
21731
|
ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
|
|
21697
21732
|
traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
|
|
21698
|
-
customRenderers: options.astro?.markdown?.customRenderers
|
|
21733
|
+
customRenderers: options.astro?.markdown?.customRenderers,
|
|
21734
|
+
scenarioAnchor: options.astro?.markdown?.scenarioAnchor,
|
|
21735
|
+
scenarioBadge: options.astro?.markdown?.scenarioBadge,
|
|
21736
|
+
scenarioNoteLink: options.astro?.markdown?.scenarioNoteLink
|
|
21699
21737
|
}
|
|
21700
21738
|
},
|
|
21701
21739
|
assetMode: options.assetMode ?? "none",
|
|
@@ -22032,7 +22070,13 @@ function copyDirRecursive(src, dest, onFile, baseSrc = src) {
|
|
|
22032
22070
|
// src/scaffold-doc.ts
|
|
22033
22071
|
import * as fs10 from "fs";
|
|
22034
22072
|
import * as path11 from "path";
|
|
22035
|
-
var TEMPLATES = [
|
|
22073
|
+
var TEMPLATES = [
|
|
22074
|
+
"adr",
|
|
22075
|
+
"runbook",
|
|
22076
|
+
"decision-log",
|
|
22077
|
+
"incident",
|
|
22078
|
+
"scenario-note"
|
|
22079
|
+
];
|
|
22036
22080
|
function slugify3(input) {
|
|
22037
22081
|
return input.toLowerCase().trim().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
|
|
22038
22082
|
}
|
|
@@ -22161,6 +22205,30 @@ _How it was fixed._
|
|
|
22161
22205
|
|
|
22162
22206
|
- [ ] Add a regression story and link it in \`verifiedBy\` so a silent recurrence
|
|
22163
22207
|
becomes a failing badge.
|
|
22208
|
+
`
|
|
22209
|
+
},
|
|
22210
|
+
"scenario-note": {
|
|
22211
|
+
subdir: "notes",
|
|
22212
|
+
filename: (_slug, ctx) => ctx.scenarioId ?? ctx.slug,
|
|
22213
|
+
content: (ctx) => `---
|
|
22214
|
+
title: 'Business context \u2014 ${ctx.name}'
|
|
22215
|
+
description: 'Stakeholder context for ${ctx.name}'
|
|
22216
|
+
scenarioId: ${ctx.scenarioId}
|
|
22217
|
+
# Link this note back to the scenario it explains so the badge and explorer stay aligned.
|
|
22218
|
+
verifiedBy: [${ctx.scenarioId}]
|
|
22219
|
+
---
|
|
22220
|
+
|
|
22221
|
+
This page is hand-written commentary for a generated scenario. It is never
|
|
22222
|
+
overwritten by \`build-docs\`.
|
|
22223
|
+
|
|
22224
|
+
## Why this behavior matters
|
|
22225
|
+
|
|
22226
|
+
_Describe the business rule, policy, customer promise, or operational nuance._
|
|
22227
|
+
|
|
22228
|
+
## Caveats
|
|
22229
|
+
|
|
22230
|
+
- _What readers should know when this scenario passes_
|
|
22231
|
+
- _Any assumptions, exclusions, or follow-up links_
|
|
22164
22232
|
`
|
|
22165
22233
|
}
|
|
22166
22234
|
};
|
|
@@ -22179,10 +22247,15 @@ function scaffoldDoc(options) {
|
|
|
22179
22247
|
const today = options.today ?? /* @__PURE__ */ new Date();
|
|
22180
22248
|
const name = (options.name ?? "").trim() || defaultName(template);
|
|
22181
22249
|
const slug2 = slugify3(name);
|
|
22250
|
+
const scenarioId = normalizeScenarioId(options.scenarioId);
|
|
22182
22251
|
const dir = path11.join(baseDir, spec.subdir);
|
|
22252
|
+
if (template === "scenario-note" && !scenarioId) {
|
|
22253
|
+
throw new Error(`Template "scenario-note" requires --scenario-id.`);
|
|
22254
|
+
}
|
|
22183
22255
|
const ctx = {
|
|
22184
22256
|
name,
|
|
22185
22257
|
slug: slug2,
|
|
22258
|
+
scenarioId,
|
|
22186
22259
|
isoDate: isoDate(today),
|
|
22187
22260
|
seq: nextSeq(dir)
|
|
22188
22261
|
};
|
|
@@ -22207,6 +22280,8 @@ function defaultName(template) {
|
|
|
22207
22280
|
return "Decisions";
|
|
22208
22281
|
case "incident":
|
|
22209
22282
|
return "Untitled incident";
|
|
22283
|
+
case "scenario-note":
|
|
22284
|
+
return "Untitled scenario note";
|
|
22210
22285
|
}
|
|
22211
22286
|
}
|
|
22212
22287
|
function titleFor2(template, ctx) {
|
|
@@ -22219,12 +22294,43 @@ function titleFor2(template, ctx) {
|
|
|
22219
22294
|
return `Decision log \u2014 ${ctx.name}`;
|
|
22220
22295
|
case "incident":
|
|
22221
22296
|
return `Incident \u2014 ${ctx.name}`;
|
|
22297
|
+
case "scenario-note":
|
|
22298
|
+
return `Business context \u2014 ${ctx.name}`;
|
|
22222
22299
|
}
|
|
22223
22300
|
}
|
|
22301
|
+
function normalizeScenarioId(input) {
|
|
22302
|
+
const value = input?.trim();
|
|
22303
|
+
if (!value) return void 0;
|
|
22304
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
22305
|
+
throw new Error(`scenarioId must not contain path separators.`);
|
|
22306
|
+
}
|
|
22307
|
+
return value;
|
|
22308
|
+
}
|
|
22224
22309
|
|
|
22225
22310
|
// src/check-links.ts
|
|
22311
|
+
import * as fs12 from "fs";
|
|
22312
|
+
import * as path13 from "path";
|
|
22313
|
+
|
|
22314
|
+
// src/utils/markdown-files.ts
|
|
22226
22315
|
import * as fs11 from "fs";
|
|
22227
22316
|
import * as path12 from "path";
|
|
22317
|
+
function collectMarkdownFiles(target) {
|
|
22318
|
+
if (!fs11.existsSync(target)) return [];
|
|
22319
|
+
if (fs11.statSync(target).isFile()) return [target];
|
|
22320
|
+
const out = [];
|
|
22321
|
+
const walk = (dir) => {
|
|
22322
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
22323
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
22324
|
+
const full = path12.join(dir, entry.name);
|
|
22325
|
+
if (entry.isDirectory()) walk(full);
|
|
22326
|
+
else if (/\.mdx?$/u.test(entry.name)) out.push(full);
|
|
22327
|
+
}
|
|
22328
|
+
};
|
|
22329
|
+
walk(target);
|
|
22330
|
+
return out;
|
|
22331
|
+
}
|
|
22332
|
+
|
|
22333
|
+
// src/check-links.ts
|
|
22228
22334
|
function stripCode(markdown) {
|
|
22229
22335
|
let out = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
22230
22336
|
out = out.replace(/(`+)(?:(?!\1).)+\1/g, "");
|
|
@@ -22254,34 +22360,19 @@ function classifyLink(link2) {
|
|
|
22254
22360
|
function resolutionCandidates(fromFile, link2) {
|
|
22255
22361
|
const withoutAnchor = link2.split("#")[0];
|
|
22256
22362
|
if (!withoutAnchor) return [];
|
|
22257
|
-
const base =
|
|
22363
|
+
const base = path13.resolve(path13.dirname(fromFile), withoutAnchor);
|
|
22258
22364
|
const candidates = [base];
|
|
22259
|
-
if (!
|
|
22365
|
+
if (!path13.extname(base)) {
|
|
22260
22366
|
candidates.push(`${base}.md`, `${base}.mdx`);
|
|
22261
|
-
candidates.push(
|
|
22367
|
+
candidates.push(path13.join(base, "index.md"), path13.join(base, "index.mdx"));
|
|
22262
22368
|
}
|
|
22263
22369
|
return candidates;
|
|
22264
22370
|
}
|
|
22265
22371
|
function resolvesOnDisk(fromFile, link2) {
|
|
22266
22372
|
return resolutionCandidates(fromFile, link2).some(
|
|
22267
|
-
(candidate) =>
|
|
22373
|
+
(candidate) => fs12.existsSync(candidate) && fs12.statSync(candidate).isFile()
|
|
22268
22374
|
);
|
|
22269
22375
|
}
|
|
22270
|
-
function collectDocFiles(target) {
|
|
22271
|
-
const stat = fs11.statSync(target);
|
|
22272
|
-
if (stat.isFile()) return [target];
|
|
22273
|
-
const out = [];
|
|
22274
|
-
const walk = (dir) => {
|
|
22275
|
-
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
22276
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
22277
|
-
const full = path12.join(dir, entry.name);
|
|
22278
|
-
if (entry.isDirectory()) walk(full);
|
|
22279
|
-
else if (/\.mdx?$/.test(entry.name)) out.push(full);
|
|
22280
|
-
}
|
|
22281
|
-
};
|
|
22282
|
-
walk(target);
|
|
22283
|
-
return out;
|
|
22284
|
-
}
|
|
22285
22376
|
async function isExternalAlive(url, timeoutMs) {
|
|
22286
22377
|
const attempt = async (method) => {
|
|
22287
22378
|
const controller = new AbortController();
|
|
@@ -22305,17 +22396,17 @@ async function isExternalAlive(url, timeoutMs) {
|
|
|
22305
22396
|
}
|
|
22306
22397
|
async function checkLinks(options) {
|
|
22307
22398
|
const { target, checkExternal = false, externalTimeoutMs = 8e3 } = options;
|
|
22308
|
-
if (!
|
|
22399
|
+
if (!fs12.existsSync(target)) {
|
|
22309
22400
|
throw new Error(`Path not found: ${target}`);
|
|
22310
22401
|
}
|
|
22311
|
-
const files =
|
|
22402
|
+
const files = collectMarkdownFiles(target);
|
|
22312
22403
|
const broken = [];
|
|
22313
22404
|
let linksChecked = 0;
|
|
22314
22405
|
let externalChecked = 0;
|
|
22315
22406
|
let skipped = 0;
|
|
22316
22407
|
const externalCache = /* @__PURE__ */ new Map();
|
|
22317
22408
|
for (const file of files) {
|
|
22318
|
-
const content =
|
|
22409
|
+
const content = fs12.readFileSync(file, "utf8");
|
|
22319
22410
|
for (const link2 of extractLinks(content)) {
|
|
22320
22411
|
const kind = classifyLink(link2);
|
|
22321
22412
|
if (kind === "anchor" || kind === "mail" || kind === "root") {
|
|
@@ -22371,8 +22462,8 @@ function formatLinkReport(report) {
|
|
|
22371
22462
|
}
|
|
22372
22463
|
|
|
22373
22464
|
// src/import-openapi.ts
|
|
22374
|
-
import * as
|
|
22375
|
-
import * as
|
|
22465
|
+
import * as fs13 from "fs";
|
|
22466
|
+
import * as path14 from "path";
|
|
22376
22467
|
import { parse as parseYamlString } from "yaml";
|
|
22377
22468
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
|
|
22378
22469
|
function parseYaml(raw, specPath) {
|
|
@@ -22385,9 +22476,9 @@ function parseYaml(raw, specPath) {
|
|
|
22385
22476
|
}
|
|
22386
22477
|
}
|
|
22387
22478
|
function parseSpec(specPath) {
|
|
22388
|
-
if (!
|
|
22389
|
-
const raw =
|
|
22390
|
-
const ext =
|
|
22479
|
+
if (!fs13.existsSync(specPath)) throw new Error(`Spec not found: ${specPath}`);
|
|
22480
|
+
const raw = fs13.readFileSync(specPath, "utf8");
|
|
22481
|
+
const ext = path14.extname(specPath).toLowerCase();
|
|
22391
22482
|
if (ext === ".json") return JSON.parse(raw);
|
|
22392
22483
|
if (ext === ".yaml" || ext === ".yml") return parseYaml(raw, specPath);
|
|
22393
22484
|
try {
|
|
@@ -22418,8 +22509,8 @@ function extractEndpoints(spec) {
|
|
|
22418
22509
|
}
|
|
22419
22510
|
function loadScenarios(runFile) {
|
|
22420
22511
|
if (!runFile) return [];
|
|
22421
|
-
if (!
|
|
22422
|
-
const report = JSON.parse(
|
|
22512
|
+
if (!fs13.existsSync(runFile)) throw new Error(`Run file not found: ${runFile}`);
|
|
22513
|
+
const report = JSON.parse(fs13.readFileSync(runFile, "utf8"));
|
|
22423
22514
|
return (report.features ?? []).flatMap((f) => f.scenarios ?? []);
|
|
22424
22515
|
}
|
|
22425
22516
|
function endpointRefs(endpoint) {
|
|
@@ -22526,25 +22617,25 @@ async function importOpenApi(options) {
|
|
|
22526
22617
|
list.push(item);
|
|
22527
22618
|
groups.set(item.endpoint.tag, list);
|
|
22528
22619
|
}
|
|
22529
|
-
const outputDir = options.outputDir ??
|
|
22530
|
-
if (
|
|
22531
|
-
const entries =
|
|
22620
|
+
const outputDir = options.outputDir ?? path14.join("src", "content", "docs", "api");
|
|
22621
|
+
if (fs13.existsSync(outputDir) && !options.force) {
|
|
22622
|
+
const entries = fs13.readdirSync(outputDir);
|
|
22532
22623
|
if (entries.length > 0) {
|
|
22533
22624
|
throw new Error(`Output directory "${outputDir}" is not empty. Use --force to overwrite.`);
|
|
22534
22625
|
}
|
|
22535
22626
|
}
|
|
22536
|
-
|
|
22627
|
+
fs13.mkdirSync(outputDir, { recursive: true });
|
|
22537
22628
|
const coveredCount = coverage.filter((c) => c.status === "covered").length;
|
|
22538
22629
|
const uncoveredCount = coverage.filter((c) => c.status === "uncovered").length;
|
|
22539
|
-
|
|
22540
|
-
|
|
22630
|
+
fs13.writeFileSync(
|
|
22631
|
+
path14.join(outputDir, "index.mdx"),
|
|
22541
22632
|
renderIndex(groups, hasRun, { endpointCount: endpoints.length, coveredCount, uncoveredCount }),
|
|
22542
22633
|
"utf8"
|
|
22543
22634
|
);
|
|
22544
22635
|
for (const [tag, rows] of groups) {
|
|
22545
|
-
const dir =
|
|
22546
|
-
|
|
22547
|
-
|
|
22636
|
+
const dir = path14.join(outputDir, slug(tag));
|
|
22637
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
22638
|
+
fs13.writeFileSync(path14.join(dir, "index.mdx"), renderTagPage(tag, rows, hasRun), "utf8");
|
|
22548
22639
|
}
|
|
22549
22640
|
return {
|
|
22550
22641
|
outputDir,
|
|
@@ -22556,8 +22647,246 @@ async function importOpenApi(options) {
|
|
|
22556
22647
|
}
|
|
22557
22648
|
|
|
22558
22649
|
// src/build-docs.ts
|
|
22559
|
-
import * as
|
|
22560
|
-
import * as
|
|
22650
|
+
import * as fs15 from "fs";
|
|
22651
|
+
import * as path16 from "path";
|
|
22652
|
+
|
|
22653
|
+
// src/scenario-links.ts
|
|
22654
|
+
function scenarioAnchor(title) {
|
|
22655
|
+
return `scenario-${slugify(title)}`;
|
|
22656
|
+
}
|
|
22657
|
+
function buildScenarioLinks(report, options = {}) {
|
|
22658
|
+
const audienceSplit = options.audienceSplit ?? false;
|
|
22659
|
+
const baseUrl = (options.baseUrl ?? "/stories").replace(/\/$/, "");
|
|
22660
|
+
const scenarios = {};
|
|
22661
|
+
for (const feature of report.features) {
|
|
22662
|
+
const stem = slugify(cleanTestStem(feature.sourceFile));
|
|
22663
|
+
for (const scenario of feature.scenarios) {
|
|
22664
|
+
const audience = deriveAudience(feature.sourceFile, scenario.tags);
|
|
22665
|
+
const url = audienceSplit ? `${baseUrl}/${audience}/${stem}/` : `${baseUrl}/${stem}/`;
|
|
22666
|
+
const anchor = scenarioAnchor(scenario.title);
|
|
22667
|
+
scenarios[scenario.id] = {
|
|
22668
|
+
id: scenario.id,
|
|
22669
|
+
title: scenario.title,
|
|
22670
|
+
audience,
|
|
22671
|
+
status: scenario.status,
|
|
22672
|
+
sourceFile: feature.sourceFile,
|
|
22673
|
+
url,
|
|
22674
|
+
anchor,
|
|
22675
|
+
deepLink: `${url}#${anchor}`
|
|
22676
|
+
};
|
|
22677
|
+
}
|
|
22678
|
+
}
|
|
22679
|
+
return { schemaVersion: "1.0", runId: report.runId, baseUrl, scenarios };
|
|
22680
|
+
}
|
|
22681
|
+
|
|
22682
|
+
// src/changes-page.ts
|
|
22683
|
+
var GROUPS = [
|
|
22684
|
+
{ kind: "regressed", heading: "Regressed", icon: "\u26A0\uFE0F" },
|
|
22685
|
+
{ kind: "removed", heading: "Removed", icon: "\u{1F5D1}\uFE0F" },
|
|
22686
|
+
{ kind: "added", heading: "Added", icon: "\u2728" },
|
|
22687
|
+
{ kind: "fixed", heading: "Fixed", icon: "\u2705" },
|
|
22688
|
+
{ kind: "changed", heading: "Changed", icon: "\u{1F501}" }
|
|
22689
|
+
];
|
|
22690
|
+
function yamlScalar2(value) {
|
|
22691
|
+
if (/[:#[\]{}&*!|>'"%@`]|^[\s-]|\s$/.test(value)) {
|
|
22692
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
22693
|
+
}
|
|
22694
|
+
return value;
|
|
22695
|
+
}
|
|
22696
|
+
function badge(summary) {
|
|
22697
|
+
if (summary.regressed > 0 || summary.removed > 0) return { text: "Regressed", variant: "danger" };
|
|
22698
|
+
if (summary.added > 0 || summary.fixed > 0 || summary.changed > 0)
|
|
22699
|
+
return { text: "Updated", variant: "tip" };
|
|
22700
|
+
return { text: "No changes", variant: "note" };
|
|
22701
|
+
}
|
|
22702
|
+
function line(entry, links) {
|
|
22703
|
+
const link2 = links.scenarios[entry.id];
|
|
22704
|
+
const label = link2 ? `[${entry.title}](${link2.deepLink})` : entry.title;
|
|
22705
|
+
const transition = entry.baselineStatus && entry.currentStatus && entry.baselineStatus !== entry.currentStatus ? ` \u2014 \`${entry.baselineStatus}\` \u2192 \`${entry.currentStatus}\`` : "";
|
|
22706
|
+
return `- ${label} \`${entry.sourceFile}\`${transition}`;
|
|
22707
|
+
}
|
|
22708
|
+
function renderChangesPage(diff, links) {
|
|
22709
|
+
const b = badge(diff.summary);
|
|
22710
|
+
const { added, removed, regressed, fixed, changed } = diff.summary;
|
|
22711
|
+
const totalChanged = added + removed + regressed + fixed + changed;
|
|
22712
|
+
const frontmatter = [
|
|
22713
|
+
"---",
|
|
22714
|
+
"title: What's changed",
|
|
22715
|
+
`description: ${yamlScalar2(
|
|
22716
|
+
totalChanged === 0 ? "No behavioural changes since the baseline" : `${totalChanged} scenario${totalChanged !== 1 ? "s" : ""} changed since the baseline`
|
|
22717
|
+
)}`,
|
|
22718
|
+
"sidebar:",
|
|
22719
|
+
" order: 0",
|
|
22720
|
+
" badge:",
|
|
22721
|
+
` text: ${b.text}`,
|
|
22722
|
+
` variant: ${b.variant}`,
|
|
22723
|
+
"---"
|
|
22724
|
+
].join("\n");
|
|
22725
|
+
const body = [];
|
|
22726
|
+
if (totalChanged === 0) {
|
|
22727
|
+
body.push("No behavioural changes since the baseline run. \u2705");
|
|
22728
|
+
} else {
|
|
22729
|
+
body.push(
|
|
22730
|
+
`**${regressed}** regressed \xB7 **${removed}** removed \xB7 **${added}** added \xB7 **${fixed}** fixed \xB7 **${changed}** changed`,
|
|
22731
|
+
""
|
|
22732
|
+
);
|
|
22733
|
+
for (const group of GROUPS) {
|
|
22734
|
+
const entries = diff.scenarios.filter((s) => s.kind === group.kind);
|
|
22735
|
+
if (entries.length === 0) continue;
|
|
22736
|
+
body.push(`## ${group.icon} ${group.heading} (${entries.length})`, "");
|
|
22737
|
+
for (const entry of entries) body.push(line(entry, links));
|
|
22738
|
+
body.push("");
|
|
22739
|
+
}
|
|
22740
|
+
}
|
|
22741
|
+
return `${frontmatter}
|
|
22742
|
+
|
|
22743
|
+
${body.join("\n")}
|
|
22744
|
+
`;
|
|
22745
|
+
}
|
|
22746
|
+
|
|
22747
|
+
// src/notes-index.ts
|
|
22748
|
+
import * as fs14 from "fs";
|
|
22749
|
+
import * as path15 from "path";
|
|
22750
|
+
import { slug as githubSlug } from "github-slugger";
|
|
22751
|
+
import { parse as parseYaml2 } from "yaml";
|
|
22752
|
+
function buildScenarioNotesIndex(notesDir) {
|
|
22753
|
+
const entries = collectMarkdownFiles(notesDir).map((filePath) => readScenarioNote(filePath, notesDir)).filter((entry) => entry !== null).sort((a, b) => a.slug.localeCompare(b.slug));
|
|
22754
|
+
return {
|
|
22755
|
+
schemaVersion: "1.0",
|
|
22756
|
+
notes: entries
|
|
22757
|
+
};
|
|
22758
|
+
}
|
|
22759
|
+
function writeNotesIndex(index, outPath) {
|
|
22760
|
+
fs14.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
22761
|
+
fs14.writeFileSync(outPath, JSON.stringify(index, null, 2), "utf8");
|
|
22762
|
+
return index;
|
|
22763
|
+
}
|
|
22764
|
+
function notesByScenarioId(index) {
|
|
22765
|
+
const map = /* @__PURE__ */ new Map();
|
|
22766
|
+
for (const note of index.notes) {
|
|
22767
|
+
if (!map.has(note.scenarioId)) map.set(note.scenarioId, note);
|
|
22768
|
+
}
|
|
22769
|
+
return map;
|
|
22770
|
+
}
|
|
22771
|
+
function noteHref(note) {
|
|
22772
|
+
return `/notes/${note.slug}/`;
|
|
22773
|
+
}
|
|
22774
|
+
function noteLinkMarkdown(note) {
|
|
22775
|
+
return `[Business context \u2192](${noteHref(note)})`;
|
|
22776
|
+
}
|
|
22777
|
+
function readScenarioNote(filePath, notesDir) {
|
|
22778
|
+
const relative5 = path15.relative(notesDir, filePath);
|
|
22779
|
+
const stem = relative5.replace(/\.(?:md|mdx)$/u, "");
|
|
22780
|
+
const frontmatter = parseFrontmatter(fs14.readFileSync(filePath, "utf8"));
|
|
22781
|
+
const scenarioId = typeof frontmatter.scenarioId === "string" && frontmatter.scenarioId.trim().length > 0 ? frontmatter.scenarioId.trim() : path15.basename(stem);
|
|
22782
|
+
const title = typeof frontmatter.title === "string" && frontmatter.title.trim().length > 0 ? frontmatter.title.trim() : `Business context \u2014 ${scenarioId}`;
|
|
22783
|
+
return {
|
|
22784
|
+
scenarioId,
|
|
22785
|
+
slug: toRouteSlug(stem),
|
|
22786
|
+
title
|
|
22787
|
+
};
|
|
22788
|
+
}
|
|
22789
|
+
function parseFrontmatter(source) {
|
|
22790
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/u.exec(source);
|
|
22791
|
+
if (!match) return {};
|
|
22792
|
+
const parsed = parseYaml2(match[1]);
|
|
22793
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
22794
|
+
}
|
|
22795
|
+
function toRouteSlug(stem) {
|
|
22796
|
+
return stem.split(path15.sep).map((segment) => githubSlug(segment)).join("/").replace(/\/index$/u, "");
|
|
22797
|
+
}
|
|
22798
|
+
|
|
22799
|
+
// src/overview-page.ts
|
|
22800
|
+
var AUDIENCE_CARDS = [
|
|
22801
|
+
{
|
|
22802
|
+
key: "engineer",
|
|
22803
|
+
label: "Engineer",
|
|
22804
|
+
icon: "\u{1F527}",
|
|
22805
|
+
blurb: "Unit & integration behaviour \u2014 how the system works under the hood."
|
|
22806
|
+
},
|
|
22807
|
+
{
|
|
22808
|
+
key: "stakeholder",
|
|
22809
|
+
label: "Stakeholder",
|
|
22810
|
+
icon: "\u{1F3AC}",
|
|
22811
|
+
blurb: "End-to-end journeys \u2014 what the product does, with video and traces."
|
|
22812
|
+
}
|
|
22813
|
+
];
|
|
22814
|
+
var STATUS_ICON = {
|
|
22815
|
+
passed: "\u2705",
|
|
22816
|
+
failed: "\u274C",
|
|
22817
|
+
skipped: "\u23ED\uFE0F",
|
|
22818
|
+
pending: "\u{1F6A7}"
|
|
22819
|
+
};
|
|
22820
|
+
function yamlScalar3(value) {
|
|
22821
|
+
if (/[:#[\]{}&*!|>'"%@`]|^[\s-]|\s$/.test(value)) {
|
|
22822
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
22823
|
+
}
|
|
22824
|
+
return value;
|
|
22825
|
+
}
|
|
22826
|
+
function renderOverviewPage(links, notesIndex) {
|
|
22827
|
+
const all = Object.values(links.scenarios);
|
|
22828
|
+
const total = all.length;
|
|
22829
|
+
const passed = all.filter((s) => s.status === "passed").length;
|
|
22830
|
+
const failed = all.filter((s) => s.status === "failed").length;
|
|
22831
|
+
const notesById = notesByScenarioId(notesIndex ?? { schemaVersion: "1.0", notes: [] });
|
|
22832
|
+
const frontmatter = [
|
|
22833
|
+
"---",
|
|
22834
|
+
"title: Stories",
|
|
22835
|
+
`description: ${yamlScalar3(
|
|
22836
|
+
`${total} scenario${total !== 1 ? "s" : ""} \u2014 ${passed} passed, ${failed} failed`
|
|
22837
|
+
)}`,
|
|
22838
|
+
"sidebar:",
|
|
22839
|
+
" order: 1",
|
|
22840
|
+
"---"
|
|
22841
|
+
].join("\n");
|
|
22842
|
+
const body = [
|
|
22843
|
+
`**${total}** scenarios \xB7 **${passed}** passed \xB7 **${failed}** failed`,
|
|
22844
|
+
""
|
|
22845
|
+
];
|
|
22846
|
+
for (const card of AUDIENCE_CARDS) {
|
|
22847
|
+
const scenarios = all.filter((s) => s.audience === card.key);
|
|
22848
|
+
if (scenarios.length === 0) continue;
|
|
22849
|
+
const cardPassed = scenarios.filter((s) => s.status === "passed").length;
|
|
22850
|
+
const cardFailed = scenarios.filter((s) => s.status === "failed").length;
|
|
22851
|
+
const counts = cardFailed > 0 ? `${cardPassed} passed, ${cardFailed} failed` : `${cardPassed} passed`;
|
|
22852
|
+
body.push(`## ${card.icon} ${card.label} (${scenarios.length} \u2014 ${counts})`, "");
|
|
22853
|
+
body.push(`${card.blurb}`, "");
|
|
22854
|
+
for (const s of scenariosSorted(scenarios)) {
|
|
22855
|
+
const note = notesById.get(s.id);
|
|
22856
|
+
const noteSuffix = note ? ` \xB7 ${noteLinkMarkdown(note)}` : "";
|
|
22857
|
+
body.push(`- ${STATUS_ICON[s.status] ?? "\u2022"} [${s.title}](${s.deepLink})${noteSuffix}`);
|
|
22858
|
+
}
|
|
22859
|
+
body.push("");
|
|
22860
|
+
}
|
|
22861
|
+
return `${frontmatter}
|
|
22862
|
+
|
|
22863
|
+
${body.join("\n")}
|
|
22864
|
+
`;
|
|
22865
|
+
}
|
|
22866
|
+
function scenariosSorted(scenarios) {
|
|
22867
|
+
return [...scenarios].sort((a, b) => {
|
|
22868
|
+
const aFail = a.status === "failed" ? 0 : 1;
|
|
22869
|
+
const bFail = b.status === "failed" ? 0 : 1;
|
|
22870
|
+
if (aFail !== bFail) return aFail - bFail;
|
|
22871
|
+
return a.title.localeCompare(b.title);
|
|
22872
|
+
});
|
|
22873
|
+
}
|
|
22874
|
+
|
|
22875
|
+
// src/build-docs.ts
|
|
22876
|
+
var AUDIENCES = ["engineer", "stakeholder"];
|
|
22877
|
+
function partitionByAudience(run) {
|
|
22878
|
+
const buckets = {
|
|
22879
|
+
engineer: [],
|
|
22880
|
+
stakeholder: []
|
|
22881
|
+
};
|
|
22882
|
+
for (const tc of run.testCases) {
|
|
22883
|
+
buckets[deriveAudience(tc.sourceFile, tc.tags)].push(tc);
|
|
22884
|
+
}
|
|
22885
|
+
return {
|
|
22886
|
+
engineer: { ...run, testCases: buckets.engineer },
|
|
22887
|
+
stakeholder: { ...run, testCases: buckets.stakeholder }
|
|
22888
|
+
};
|
|
22889
|
+
}
|
|
22561
22890
|
var BuildDocsError = class extends Error {
|
|
22562
22891
|
constructor(message, kind) {
|
|
22563
22892
|
super(message);
|
|
@@ -22567,22 +22896,22 @@ var BuildDocsError = class extends Error {
|
|
|
22567
22896
|
};
|
|
22568
22897
|
var isRemote = (p) => /^(?:https?:|data:)/i.test(p);
|
|
22569
22898
|
function bundleExplorerAssets(reportPath, assetsDir, baseUrl = "/stories/assets") {
|
|
22570
|
-
if (!
|
|
22571
|
-
const report = JSON.parse(
|
|
22899
|
+
if (!fs15.existsSync(reportPath)) return 0;
|
|
22900
|
+
const report = JSON.parse(fs15.readFileSync(reportPath, "utf8"));
|
|
22572
22901
|
let copied = 0;
|
|
22573
22902
|
const bundle = (value) => {
|
|
22574
|
-
const rel = copyAsset(
|
|
22903
|
+
const rel = copyAsset(path16.resolve(value), assetsDir);
|
|
22575
22904
|
copied++;
|
|
22576
|
-
return `${baseUrl}/${
|
|
22905
|
+
return `${baseUrl}/${path16.basename(rel)}`;
|
|
22577
22906
|
};
|
|
22578
22907
|
const visit = (entries) => {
|
|
22579
22908
|
for (const entry of entries ?? []) {
|
|
22580
22909
|
const e = entry;
|
|
22581
22910
|
if (e.kind === "screenshot" || e.kind === "video" || e.kind === "html") {
|
|
22582
|
-
if (typeof e.path === "string" && !isRemote(e.path) &&
|
|
22911
|
+
if (typeof e.path === "string" && !isRemote(e.path) && fs15.existsSync(e.path)) {
|
|
22583
22912
|
e.path = bundle(e.path);
|
|
22584
22913
|
}
|
|
22585
|
-
if (typeof e.poster === "string" && !isRemote(e.poster) &&
|
|
22914
|
+
if (typeof e.poster === "string" && !isRemote(e.poster) && fs15.existsSync(e.poster)) {
|
|
22586
22915
|
e.poster = bundle(e.poster);
|
|
22587
22916
|
}
|
|
22588
22917
|
}
|
|
@@ -22595,25 +22924,78 @@ function bundleExplorerAssets(reportPath, assetsDir, baseUrl = "/stories/assets"
|
|
|
22595
22924
|
}
|
|
22596
22925
|
}
|
|
22597
22926
|
if (copied > 0) {
|
|
22598
|
-
|
|
22927
|
+
fs15.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
|
|
22599
22928
|
}
|
|
22600
22929
|
return copied;
|
|
22601
22930
|
}
|
|
22931
|
+
var CHANGE_BADGE = {
|
|
22932
|
+
added: "\u{1F195} **New** _since last run_",
|
|
22933
|
+
fixed: "\u2705 **Fixed** _since last run_",
|
|
22934
|
+
regressed: "\u26A0\uFE0F **Regressed** _since last run_"
|
|
22935
|
+
};
|
|
22936
|
+
function scenarioKey(sourceFile, title) {
|
|
22937
|
+
return `${sourceFile}\0${title}`;
|
|
22938
|
+
}
|
|
22939
|
+
function changeBadgeLookup(diff) {
|
|
22940
|
+
if (!diff) return void 0;
|
|
22941
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
22942
|
+
for (const s of diff.scenarios) {
|
|
22943
|
+
const badge2 = CHANGE_BADGE[s.kind];
|
|
22944
|
+
if (badge2) byKey.set(scenarioKey(s.sourceFile, s.title), badge2);
|
|
22945
|
+
}
|
|
22946
|
+
if (byKey.size === 0) return void 0;
|
|
22947
|
+
return (tc) => byKey.get(scenarioKey(tc.sourceFile, tc.story.scenario));
|
|
22948
|
+
}
|
|
22949
|
+
function readStoryReport(reportPath) {
|
|
22950
|
+
if (!fs15.existsSync(reportPath)) return null;
|
|
22951
|
+
try {
|
|
22952
|
+
return JSON.parse(fs15.readFileSync(reportPath, "utf8"));
|
|
22953
|
+
} catch {
|
|
22954
|
+
return null;
|
|
22955
|
+
}
|
|
22956
|
+
}
|
|
22957
|
+
function noteLinkLookup(report, notesIndex) {
|
|
22958
|
+
if (!report) return void 0;
|
|
22959
|
+
const noteById = notesByScenarioId(notesIndex);
|
|
22960
|
+
if (noteById.size === 0) return void 0;
|
|
22961
|
+
const idByKey = /* @__PURE__ */ new Map();
|
|
22962
|
+
for (const feature of report.features) {
|
|
22963
|
+
for (const scenario of feature.scenarios) {
|
|
22964
|
+
idByKey.set(scenarioKey(feature.sourceFile, scenario.title), scenario.id);
|
|
22965
|
+
}
|
|
22966
|
+
}
|
|
22967
|
+
return (tc) => {
|
|
22968
|
+
const id = idByKey.get(scenarioKey(tc.sourceFile, tc.story.scenario));
|
|
22969
|
+
const note = id ? noteById.get(id) : void 0;
|
|
22970
|
+
return note ? noteLinkMarkdown(note) : void 0;
|
|
22971
|
+
};
|
|
22972
|
+
}
|
|
22973
|
+
function writeScenarioLinks(reportPath, outDir, options = {}) {
|
|
22974
|
+
const report = readStoryReport(reportPath);
|
|
22975
|
+
if (!report) return null;
|
|
22976
|
+
const index = buildScenarioLinks(report, { audienceSplit: options.audienceSplit });
|
|
22977
|
+
fs15.writeFileSync(
|
|
22978
|
+
path16.join(outDir, "scenario-links.json"),
|
|
22979
|
+
JSON.stringify(index, null, 2),
|
|
22980
|
+
"utf8"
|
|
22981
|
+
);
|
|
22982
|
+
return index;
|
|
22983
|
+
}
|
|
22602
22984
|
function clearGeneratedPages(dir) {
|
|
22603
|
-
if (!
|
|
22604
|
-
for (const entry of
|
|
22605
|
-
const full =
|
|
22985
|
+
if (!fs15.existsSync(dir)) return;
|
|
22986
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
22987
|
+
const full = path16.join(dir, entry.name);
|
|
22606
22988
|
if (entry.isDirectory()) {
|
|
22607
22989
|
clearGeneratedPages(full);
|
|
22608
|
-
if (
|
|
22990
|
+
if (fs15.readdirSync(full).length === 0) fs15.rmdirSync(full);
|
|
22609
22991
|
} else if (/\.mdx?$/.test(entry.name)) {
|
|
22610
|
-
|
|
22992
|
+
fs15.rmSync(full);
|
|
22611
22993
|
}
|
|
22612
22994
|
}
|
|
22613
22995
|
}
|
|
22614
22996
|
function loadCanonicalRun(rawRunPath, synthesize) {
|
|
22615
22997
|
try {
|
|
22616
|
-
const data = JSON.parse(
|
|
22998
|
+
const data = JSON.parse(fs15.readFileSync(path16.resolve(rawRunPath), "utf8"));
|
|
22617
22999
|
if (data.schemaVersion !== 1) {
|
|
22618
23000
|
throw new BuildDocsError(`Unsupported schemaVersion ${data.schemaVersion}. Supported: 1.`, "schema");
|
|
22619
23001
|
}
|
|
@@ -22636,12 +23018,13 @@ ${schemaResult.errors.map((e) => ` ${e}`).join("\n")}`,
|
|
|
22636
23018
|
}
|
|
22637
23019
|
}
|
|
22638
23020
|
async function buildDocs(options) {
|
|
22639
|
-
const siteDir =
|
|
22640
|
-
const storiesPublicDir =
|
|
22641
|
-
const assetsDir =
|
|
22642
|
-
const storyPagesDir =
|
|
22643
|
-
const
|
|
22644
|
-
const
|
|
23021
|
+
const siteDir = path16.resolve(options.siteDir);
|
|
23022
|
+
const storiesPublicDir = path16.join(siteDir, "public", "stories");
|
|
23023
|
+
const assetsDir = path16.join(storiesPublicDir, "assets");
|
|
23024
|
+
const storyPagesDir = path16.join(siteDir, "src", "content", "docs", "stories");
|
|
23025
|
+
const notesDir = path16.join(siteDir, "src", "content", "docs", "notes");
|
|
23026
|
+
const apiDir = path16.join(siteDir, "src", "content", "docs", "api");
|
|
23027
|
+
const reportPath = path16.join(storiesPublicDir, "story-report.json");
|
|
22645
23028
|
const canonical = loadCanonicalRun(options.rawRunPath, options.synthesizeStories ?? true);
|
|
22646
23029
|
try {
|
|
22647
23030
|
await new ReportGenerator({
|
|
@@ -22649,27 +23032,88 @@ async function buildDocs(options) {
|
|
|
22649
23032
|
outputDir: storiesPublicDir,
|
|
22650
23033
|
outputName: "story-report"
|
|
22651
23034
|
}).generate(canonical);
|
|
23035
|
+
const currentReport = readStoryReport(reportPath);
|
|
23036
|
+
let diff;
|
|
23037
|
+
if (options.baselinePath) {
|
|
23038
|
+
const baselineResolved = path16.resolve(options.baselinePath);
|
|
23039
|
+
const baseline = readStoryReport(baselineResolved);
|
|
23040
|
+
if (!baseline) {
|
|
23041
|
+
throw new BuildDocsError(
|
|
23042
|
+
`Baseline story-report not found or unreadable: ${baselineResolved}`,
|
|
23043
|
+
"input"
|
|
23044
|
+
);
|
|
23045
|
+
}
|
|
23046
|
+
if (currentReport) diff = diffStoryReports(baseline, currentReport);
|
|
23047
|
+
}
|
|
23048
|
+
const scenarioBadge = changeBadgeLookup(diff);
|
|
23049
|
+
const notesIndex = buildScenarioNotesIndex(notesDir);
|
|
23050
|
+
const scenarioNoteLink = noteLinkLookup(currentReport, notesIndex);
|
|
22652
23051
|
clearGeneratedPages(storyPagesDir);
|
|
22653
|
-
|
|
23052
|
+
const genPages = (run, outDir) => new ReportGenerator({
|
|
22654
23053
|
formats: ["astro"],
|
|
22655
|
-
outputDir:
|
|
23054
|
+
outputDir: outDir,
|
|
22656
23055
|
outputName: "index",
|
|
22657
23056
|
output: { mode: "colocated", colocatedStyle: "flat" },
|
|
22658
23057
|
assetMode: "copy",
|
|
22659
|
-
astro: {
|
|
22660
|
-
|
|
23058
|
+
astro: {
|
|
23059
|
+
assetsDir,
|
|
23060
|
+
assetsBaseUrl: "/stories/assets",
|
|
23061
|
+
markdown: {
|
|
23062
|
+
// Emit the same anchor scenario-links.json points at, so fragments resolve.
|
|
23063
|
+
scenarioAnchor: (tc) => scenarioAnchor(tc.story.scenario),
|
|
23064
|
+
scenarioBadge,
|
|
23065
|
+
scenarioNoteLink
|
|
23066
|
+
}
|
|
23067
|
+
}
|
|
23068
|
+
}).generate(run);
|
|
23069
|
+
const audiences = { engineer: 0, stakeholder: 0 };
|
|
23070
|
+
if (options.audienceSplit ?? false) {
|
|
23071
|
+
const partitioned = partitionByAudience(canonical);
|
|
23072
|
+
for (const audience of AUDIENCES) {
|
|
23073
|
+
const sub = partitioned[audience];
|
|
23074
|
+
audiences[audience] = sub.testCases.length;
|
|
23075
|
+
if (sub.testCases.length === 0) continue;
|
|
23076
|
+
await genPages(sub, path16.join(storyPagesDir, audience));
|
|
23077
|
+
}
|
|
23078
|
+
} else {
|
|
23079
|
+
await genPages(canonical, storyPagesDir);
|
|
23080
|
+
}
|
|
22661
23081
|
const bundledAssets = bundleExplorerAssets(reportPath, assetsDir);
|
|
23082
|
+
const linksIndex = writeScenarioLinks(reportPath, storiesPublicDir, {
|
|
23083
|
+
audienceSplit: options.audienceSplit ?? false
|
|
23084
|
+
});
|
|
23085
|
+
const scenarioLinks = linksIndex ? Object.keys(linksIndex.scenarios).length : 0;
|
|
23086
|
+
writeNotesIndex(notesIndex, path16.join(storiesPublicDir, "notes-index.json"));
|
|
23087
|
+
const notesIndexed = notesIndex.notes.length;
|
|
23088
|
+
if (linksIndex) {
|
|
23089
|
+
fs15.writeFileSync(
|
|
23090
|
+
path16.join(storyPagesDir, "index.md"),
|
|
23091
|
+
renderOverviewPage(linksIndex, notesIndex),
|
|
23092
|
+
"utf8"
|
|
23093
|
+
);
|
|
23094
|
+
}
|
|
23095
|
+
const changesJsonPath = path16.join(storiesPublicDir, "changes.json");
|
|
23096
|
+
const changesMdPath = path16.join(storyPagesDir, "changes.md");
|
|
23097
|
+
let changes;
|
|
23098
|
+
if (diff && linksIndex) {
|
|
23099
|
+
fs15.writeFileSync(changesJsonPath, JSON.stringify(diff, null, 2), "utf8");
|
|
23100
|
+
fs15.writeFileSync(changesMdPath, renderChangesPage(diff, linksIndex), "utf8");
|
|
23101
|
+
changes = diff.summary;
|
|
23102
|
+
} else {
|
|
23103
|
+
fs15.rmSync(changesJsonPath, { force: true });
|
|
23104
|
+
fs15.rmSync(changesMdPath, { force: true });
|
|
23105
|
+
}
|
|
22662
23106
|
let apiPages = 0;
|
|
22663
23107
|
if (options.openapiPath) {
|
|
22664
23108
|
const res = await importOpenApi({
|
|
22665
|
-
specPath:
|
|
23109
|
+
specPath: path16.resolve(options.openapiPath),
|
|
22666
23110
|
outputDir: apiDir,
|
|
22667
23111
|
runFile: reportPath,
|
|
22668
23112
|
force: true
|
|
22669
23113
|
});
|
|
22670
23114
|
apiPages = res.pageCount;
|
|
22671
23115
|
}
|
|
22672
|
-
return { siteDir, bundledAssets, apiPages };
|
|
23116
|
+
return { siteDir, bundledAssets, apiPages, audiences, scenarioLinks, notesIndexed, changes };
|
|
22673
23117
|
} catch (err) {
|
|
22674
23118
|
if (err instanceof BuildDocsError) throw err;
|
|
22675
23119
|
throw new BuildDocsError(`Generation failed: ${err.message}`, "generation");
|
|
@@ -22677,11 +23121,11 @@ async function buildDocs(options) {
|
|
|
22677
23121
|
}
|
|
22678
23122
|
|
|
22679
23123
|
// src/config.ts
|
|
22680
|
-
import { existsSync as
|
|
23124
|
+
import { existsSync as existsSync13 } from "fs";
|
|
22681
23125
|
import { resolve as resolve10 } from "path";
|
|
22682
23126
|
async function loadConfig(configPath) {
|
|
22683
23127
|
const resolved = configPath ? resolve10(configPath) : resolve10(process.cwd(), "executable-stories.config.js");
|
|
22684
|
-
if (!
|
|
23128
|
+
if (!existsSync13(resolved)) return {};
|
|
22685
23129
|
const mod = await import(resolved);
|
|
22686
23130
|
const config = mod.default;
|
|
22687
23131
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
@@ -22739,7 +23183,7 @@ SUBCOMMANDS
|
|
|
22739
23183
|
triage Discovery worklist for agent loops: failing scenarios, regressions first, each with the code it covers
|
|
22740
23184
|
validate Validate a JSON file against the schema (no output generated)
|
|
22741
23185
|
init-astro Scaffold an Astro docs site for story output (Starlight with themed CSS)
|
|
22742
|
-
new Scaffold a docs page from a template (adr, runbook, decision-log, incident)
|
|
23186
|
+
new Scaffold a docs page from a template (adr, runbook, decision-log, incident, scenario-note)
|
|
22743
23187
|
check-links Scan docs for broken internal/external links (CI-friendly exit code)
|
|
22744
23188
|
import-openapi Generate API doc pages from an OpenAPI spec, linked to verifying stories
|
|
22745
23189
|
publish-confluence Publish an ADF JSON file to a Confluence page via REST API
|
|
@@ -23278,20 +23722,20 @@ async function readInput(args) {
|
|
|
23278
23722
|
if (args.stdin) {
|
|
23279
23723
|
return readStdin();
|
|
23280
23724
|
}
|
|
23281
|
-
const filePath =
|
|
23282
|
-
if (!
|
|
23725
|
+
const filePath = path17.resolve(args.inputFile);
|
|
23726
|
+
if (!fs16.existsSync(filePath)) {
|
|
23283
23727
|
console.error(`Error: File not found: ${filePath}`);
|
|
23284
23728
|
process.exit(EXIT_USAGE);
|
|
23285
23729
|
}
|
|
23286
|
-
return
|
|
23730
|
+
return fs16.readFileSync(filePath, "utf8");
|
|
23287
23731
|
}
|
|
23288
23732
|
function readFileInput(filePath) {
|
|
23289
|
-
const resolved =
|
|
23290
|
-
if (!
|
|
23733
|
+
const resolved = path17.resolve(filePath);
|
|
23734
|
+
if (!fs16.existsSync(resolved)) {
|
|
23291
23735
|
console.error(`Error: File not found: ${resolved}`);
|
|
23292
23736
|
process.exit(EXIT_USAGE);
|
|
23293
23737
|
}
|
|
23294
|
-
return
|
|
23738
|
+
return fs16.readFileSync(resolved, "utf8");
|
|
23295
23739
|
}
|
|
23296
23740
|
function readStdin() {
|
|
23297
23741
|
return new Promise((resolve12, reject) => {
|
|
@@ -23424,14 +23868,14 @@ function tryNormalizeRunFromText(text2, args) {
|
|
|
23424
23868
|
}
|
|
23425
23869
|
}
|
|
23426
23870
|
function listBaselineCandidates(currentFile, args) {
|
|
23427
|
-
const baselineDir =
|
|
23428
|
-
const currentResolved =
|
|
23429
|
-
if (!
|
|
23871
|
+
const baselineDir = path17.resolve(args.baselineDir ?? path17.dirname(currentFile));
|
|
23872
|
+
const currentResolved = path17.resolve(currentFile);
|
|
23873
|
+
if (!fs16.existsSync(baselineDir)) {
|
|
23430
23874
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
23431
23875
|
process.exit(EXIT_USAGE);
|
|
23432
23876
|
}
|
|
23433
|
-
const entries =
|
|
23434
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
23877
|
+
const entries = fs16.readdirSync(baselineDir, { withFileTypes: true });
|
|
23878
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path17.join(baselineDir, entry.name)).filter((candidate) => path17.resolve(candidate) !== currentResolved).filter(
|
|
23435
23879
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
23436
23880
|
);
|
|
23437
23881
|
}
|
|
@@ -23439,14 +23883,14 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
23439
23883
|
const candidates = listBaselineCandidates(currentFile, args);
|
|
23440
23884
|
const comparable = [];
|
|
23441
23885
|
for (const candidate of candidates) {
|
|
23442
|
-
const run = tryNormalizeRunFromText(
|
|
23886
|
+
const run = tryNormalizeRunFromText(fs16.readFileSync(candidate, "utf8"), args);
|
|
23443
23887
|
if (run) {
|
|
23444
23888
|
comparable.push({ file: candidate, run });
|
|
23445
23889
|
}
|
|
23446
23890
|
}
|
|
23447
23891
|
if (comparable.length === 0) {
|
|
23448
23892
|
console.error(
|
|
23449
|
-
`Error: no compatible baseline files found in ${
|
|
23893
|
+
`Error: no compatible baseline files found in ${path17.resolve(args.baselineDir ?? path17.dirname(currentFile))}.`
|
|
23450
23894
|
);
|
|
23451
23895
|
process.exit(EXIT_USAGE);
|
|
23452
23896
|
}
|
|
@@ -23691,9 +24135,9 @@ async function main() {
|
|
|
23691
24135
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
23692
24136
|
}
|
|
23693
24137
|
if (args.emitCanonical) {
|
|
23694
|
-
const outPath =
|
|
23695
|
-
|
|
23696
|
-
|
|
24138
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24139
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24140
|
+
fs16.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
23697
24141
|
}
|
|
23698
24142
|
try {
|
|
23699
24143
|
const result = await generateReports(run, args);
|
|
@@ -23750,9 +24194,9 @@ ${msg}`);
|
|
|
23750
24194
|
}
|
|
23751
24195
|
const run = data;
|
|
23752
24196
|
if (args.emitCanonical) {
|
|
23753
|
-
const outPath =
|
|
23754
|
-
|
|
23755
|
-
|
|
24197
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24198
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24199
|
+
fs16.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
23756
24200
|
}
|
|
23757
24201
|
try {
|
|
23758
24202
|
const result = await generateReports(run, args);
|
|
@@ -23808,9 +24252,9 @@ ${msg}`);
|
|
|
23808
24252
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
23809
24253
|
}
|
|
23810
24254
|
if (args.emitCanonical) {
|
|
23811
|
-
const outPath =
|
|
23812
|
-
|
|
23813
|
-
|
|
24255
|
+
const outPath = path17.resolve(args.emitCanonical);
|
|
24256
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
24257
|
+
fs16.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
23814
24258
|
}
|
|
23815
24259
|
try {
|
|
23816
24260
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
@@ -23835,9 +24279,9 @@ function runCustomFormatters(run, customRequested, formatters, args) {
|
|
|
23835
24279
|
const ext = formatter.fileExtension ?? formatName;
|
|
23836
24280
|
const baseName = args.outputName ?? "report";
|
|
23837
24281
|
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
23838
|
-
const filepath =
|
|
23839
|
-
|
|
23840
|
-
|
|
24282
|
+
const filepath = path17.join(outputDir, filename);
|
|
24283
|
+
fs16.mkdirSync(outputDir, { recursive: true });
|
|
24284
|
+
fs16.writeFileSync(filepath, content, "utf8");
|
|
23841
24285
|
console.log(`Generated: ${filepath}`);
|
|
23842
24286
|
} catch (err) {
|
|
23843
24287
|
console.error(`Error running custom formatter "${formatName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -23887,13 +24331,13 @@ async function dispatchNotifications(run, args) {
|
|
|
23887
24331
|
}
|
|
23888
24332
|
function runHistoryPipeline(run, args) {
|
|
23889
24333
|
if (!args.historyFile) return;
|
|
23890
|
-
const historyPath =
|
|
24334
|
+
const historyPath = path17.resolve(args.historyFile);
|
|
23891
24335
|
const store = loadHistory(
|
|
23892
24336
|
{ filePath: historyPath },
|
|
23893
24337
|
{
|
|
23894
24338
|
readFile: (p) => {
|
|
23895
24339
|
try {
|
|
23896
|
-
return
|
|
24340
|
+
return fs16.readFileSync(p, "utf8");
|
|
23897
24341
|
} catch {
|
|
23898
24342
|
return void 0;
|
|
23899
24343
|
}
|
|
@@ -23906,11 +24350,11 @@ function runHistoryPipeline(run, args) {
|
|
|
23906
24350
|
run,
|
|
23907
24351
|
maxRuns: args.maxHistoryRuns
|
|
23908
24352
|
});
|
|
23909
|
-
const dir =
|
|
23910
|
-
|
|
24353
|
+
const dir = path17.dirname(historyPath);
|
|
24354
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
23911
24355
|
saveHistory(
|
|
23912
24356
|
{ filePath: historyPath, store: updated },
|
|
23913
|
-
{ writeFile: (p, content) =>
|
|
24357
|
+
{ writeFile: (p, content) => fs16.writeFileSync(p, content, "utf8") }
|
|
23914
24358
|
);
|
|
23915
24359
|
let metricsCount = 0;
|
|
23916
24360
|
for (const testId of Object.keys(updated.tests)) {
|
|
@@ -24008,9 +24452,9 @@ function mapStatus(status) {
|
|
|
24008
24452
|
function parseNameStatus(text2) {
|
|
24009
24453
|
const files = [];
|
|
24010
24454
|
for (const raw of text2.split("\n")) {
|
|
24011
|
-
const
|
|
24012
|
-
if (!
|
|
24013
|
-
const cols =
|
|
24455
|
+
const line2 = raw.trim();
|
|
24456
|
+
if (!line2) continue;
|
|
24457
|
+
const cols = line2.includes(" ") ? line2.split(" ") : line2.split(/\s+/);
|
|
24014
24458
|
const status = cols[0];
|
|
24015
24459
|
if (!status) continue;
|
|
24016
24460
|
const filePath = /^[RC]/i.test(status) && cols.length >= 3 ? cols[cols.length - 1] : cols[1];
|
|
@@ -24058,11 +24502,11 @@ function writeReviewReport(review, args) {
|
|
|
24058
24502
|
const outputDir = args.outputDir ?? "reports";
|
|
24059
24503
|
const baseName = args.outputName ?? "evidence-review";
|
|
24060
24504
|
const suffix = args.outputNameTimestamp ? `-${Math.floor(review.run.startedAtMs / 1e3)}` : "";
|
|
24061
|
-
|
|
24062
|
-
const mdPath =
|
|
24063
|
-
const htmlPath =
|
|
24064
|
-
|
|
24065
|
-
|
|
24505
|
+
fs16.mkdirSync(outputDir, { recursive: true });
|
|
24506
|
+
const mdPath = path17.join(outputDir, `${baseName}${suffix}.md`);
|
|
24507
|
+
const htmlPath = path17.join(outputDir, `${baseName}${suffix}.html`);
|
|
24508
|
+
fs16.writeFileSync(mdPath, markdown, "utf8");
|
|
24509
|
+
fs16.writeFileSync(htmlPath, html, "utf8");
|
|
24066
24510
|
return [mdPath, htmlPath];
|
|
24067
24511
|
}
|
|
24068
24512
|
function evaluateReviewGate(review, args) {
|
|
@@ -24108,9 +24552,9 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
24108
24552
|
function printCompareResult(result, args, startMs) {
|
|
24109
24553
|
const durationMs = Date.now() - startMs;
|
|
24110
24554
|
if (result.prSummary && args.prSummaryFile) {
|
|
24111
|
-
const outputPath =
|
|
24112
|
-
|
|
24113
|
-
|
|
24555
|
+
const outputPath = path17.resolve(args.prSummaryFile);
|
|
24556
|
+
fs16.mkdirSync(path17.dirname(outputPath), { recursive: true });
|
|
24557
|
+
fs16.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
24114
24558
|
}
|
|
24115
24559
|
if (args.jsonSummary) {
|
|
24116
24560
|
console.log(
|
|
@@ -24139,13 +24583,13 @@ function printCompareResult(result, args, startMs) {
|
|
|
24139
24583
|
}
|
|
24140
24584
|
}
|
|
24141
24585
|
function loadReleasePolicy(policyPath) {
|
|
24142
|
-
const resolved =
|
|
24143
|
-
if (!
|
|
24586
|
+
const resolved = path17.resolve(policyPath);
|
|
24587
|
+
if (!fs16.existsSync(resolved)) {
|
|
24144
24588
|
console.error(`Error: release policy file not found: ${resolved}`);
|
|
24145
24589
|
process.exit(EXIT_USAGE);
|
|
24146
24590
|
}
|
|
24147
24591
|
try {
|
|
24148
|
-
const raw = JSON.parse(
|
|
24592
|
+
const raw = JSON.parse(fs16.readFileSync(resolved, "utf8"));
|
|
24149
24593
|
return {
|
|
24150
24594
|
allowedOmissions: Array.isArray(raw.allowedOmissions) ? raw.allowedOmissions : [],
|
|
24151
24595
|
allowedRegressions: Array.isArray(raw.allowedRegressions) ? raw.allowedRegressions : [],
|
|
@@ -24249,7 +24693,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24249
24693
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
24250
24694
|
process.exit(EXIT_USAGE);
|
|
24251
24695
|
}
|
|
24252
|
-
if (!
|
|
24696
|
+
if (!fs16.existsSync(inputFile)) {
|
|
24253
24697
|
console.error(`Error: file not found: ${inputFile}`);
|
|
24254
24698
|
process.exit(EXIT_USAGE);
|
|
24255
24699
|
}
|
|
@@ -24277,7 +24721,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24277
24721
|
console.error("Error: --title is required when creating a new page");
|
|
24278
24722
|
process.exit(EXIT_USAGE);
|
|
24279
24723
|
}
|
|
24280
|
-
const adf =
|
|
24724
|
+
const adf = fs16.readFileSync(path17.resolve(inputFile), "utf8");
|
|
24281
24725
|
if (dryRun) {
|
|
24282
24726
|
console.log(
|
|
24283
24727
|
JSON.stringify(
|
|
@@ -24356,7 +24800,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24356
24800
|
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
24357
24801
|
process.exit(EXIT_USAGE);
|
|
24358
24802
|
}
|
|
24359
|
-
if (!
|
|
24803
|
+
if (!fs16.existsSync(inputFile)) {
|
|
24360
24804
|
console.error(`Error: file not found: ${inputFile}`);
|
|
24361
24805
|
process.exit(EXIT_USAGE);
|
|
24362
24806
|
}
|
|
@@ -24383,7 +24827,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24383
24827
|
process.exit(EXIT_USAGE);
|
|
24384
24828
|
}
|
|
24385
24829
|
const mode = modeRaw;
|
|
24386
|
-
const adf =
|
|
24830
|
+
const adf = fs16.readFileSync(path17.resolve(inputFile), "utf8");
|
|
24387
24831
|
if (dryRun) {
|
|
24388
24832
|
console.log(
|
|
24389
24833
|
JSON.stringify(
|
|
@@ -24427,14 +24871,20 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
24427
24871
|
function runNew(rawArgs) {
|
|
24428
24872
|
const { values, positionals } = parseArgs({
|
|
24429
24873
|
args: rawArgs,
|
|
24430
|
-
options: {
|
|
24874
|
+
options: {
|
|
24875
|
+
dir: { type: "string" },
|
|
24876
|
+
force: { type: "boolean", default: false },
|
|
24877
|
+
"scenario-id": { type: "string" }
|
|
24878
|
+
},
|
|
24431
24879
|
allowPositionals: true,
|
|
24432
24880
|
strict: true
|
|
24433
24881
|
});
|
|
24434
24882
|
const template = positionals[0];
|
|
24435
24883
|
const name = positionals.slice(1).join(" ");
|
|
24436
24884
|
if (!template) {
|
|
24437
|
-
console.error(
|
|
24885
|
+
console.error(
|
|
24886
|
+
`Usage: executable-stories new <template> "<name>" [--dir <docs-dir>] [--scenario-id <id>] [--force]`
|
|
24887
|
+
);
|
|
24438
24888
|
console.error(`Templates: ${TEMPLATES.join(", ")}`);
|
|
24439
24889
|
return EXIT_USAGE;
|
|
24440
24890
|
}
|
|
@@ -24442,6 +24892,7 @@ function runNew(rawArgs) {
|
|
|
24442
24892
|
const result = scaffoldDoc({
|
|
24443
24893
|
template,
|
|
24444
24894
|
name,
|
|
24895
|
+
scenarioId: values["scenario-id"],
|
|
24445
24896
|
baseDir: values.dir,
|
|
24446
24897
|
force: values.force
|
|
24447
24898
|
});
|
|
@@ -24517,7 +24968,9 @@ async function runBuildDocs(rawArgs) {
|
|
|
24517
24968
|
options: {
|
|
24518
24969
|
"site-dir": { type: "string" },
|
|
24519
24970
|
openapi: { type: "string" },
|
|
24520
|
-
"no-synthesize-stories": { type: "boolean", default: false }
|
|
24971
|
+
"no-synthesize-stories": { type: "boolean", default: false },
|
|
24972
|
+
"audience-split": { type: "boolean", default: false },
|
|
24973
|
+
baseline: { type: "string" }
|
|
24521
24974
|
},
|
|
24522
24975
|
allowPositionals: true,
|
|
24523
24976
|
strict: true
|
|
@@ -24525,27 +24978,44 @@ async function runBuildDocs(rawArgs) {
|
|
|
24525
24978
|
const rawRunPath = positionals[0];
|
|
24526
24979
|
if (!rawRunPath) {
|
|
24527
24980
|
console.error(
|
|
24528
|
-
`Usage: executable-stories build-docs <raw-run.json> [--site-dir <dir>] [--openapi <spec>]`
|
|
24981
|
+
`Usage: executable-stories build-docs <raw-run.json> [--site-dir <dir>] [--openapi <spec>] [--baseline <prev-story-report.json>] [--audience-split]`
|
|
24529
24982
|
);
|
|
24530
24983
|
return EXIT_USAGE;
|
|
24531
24984
|
}
|
|
24985
|
+
const audienceSplit = values["audience-split"];
|
|
24532
24986
|
try {
|
|
24533
24987
|
const result = await buildDocs({
|
|
24534
24988
|
rawRunPath,
|
|
24535
24989
|
siteDir: values["site-dir"] ?? ".",
|
|
24536
24990
|
openapiPath: values.openapi,
|
|
24537
|
-
synthesizeStories: !values["no-synthesize-stories"]
|
|
24991
|
+
synthesizeStories: !values["no-synthesize-stories"],
|
|
24992
|
+
audienceSplit,
|
|
24993
|
+
baselinePath: values.baseline
|
|
24538
24994
|
});
|
|
24539
24995
|
console.log(`\u2713 Living docs generated in ${result.siteDir}`);
|
|
24540
24996
|
console.log(` \u2022 Explorer data \u2192 public/stories/story-report.json`);
|
|
24541
|
-
console.log(` \u2022
|
|
24997
|
+
console.log(` \u2022 Deep links \u2192 public/stories/scenario-links.json (${result.scenarioLinks})`);
|
|
24998
|
+
console.log(` \u2022 Note links \u2192 public/stories/notes-index.json (${result.notesIndexed})`);
|
|
24999
|
+
if (audienceSplit) {
|
|
25000
|
+
console.log(
|
|
25001
|
+
` \u2022 Story pages \u2192 src/content/docs/stories/{engineer,stakeholder} (engineer: ${result.audiences.engineer}, stakeholder: ${result.audiences.stakeholder})`
|
|
25002
|
+
);
|
|
25003
|
+
} else {
|
|
25004
|
+
console.log(` \u2022 Story pages \u2192 src/content/docs/stories`);
|
|
25005
|
+
}
|
|
24542
25006
|
if (result.bundledAssets > 0) {
|
|
24543
25007
|
console.log(` \u2022 Bundled assets \u2192 public/stories/assets (${result.bundledAssets})`);
|
|
24544
25008
|
}
|
|
24545
25009
|
if (result.apiPages > 0) {
|
|
24546
25010
|
console.log(` \u2022 API pages \u2192 src/content/docs/api (${result.apiPages})`);
|
|
24547
25011
|
}
|
|
24548
|
-
|
|
25012
|
+
if (result.changes) {
|
|
25013
|
+
const c = result.changes;
|
|
25014
|
+
console.log(
|
|
25015
|
+
` \u2022 What's changed \u2192 src/content/docs/stories/changes.md (+${c.added} added, ${c.regressed} regressed, ${c.fixed} fixed, ${c.removed} removed)`
|
|
25016
|
+
);
|
|
25017
|
+
}
|
|
25018
|
+
const rel = path17.relative(process.cwd(), result.siteDir) || ".";
|
|
24549
25019
|
console.log(`
|
|
24550
25020
|
Preview: cd ${rel} && npm run dev`);
|
|
24551
25021
|
return EXIT_SUCCESS;
|