executable-stories-formatters 0.13.0 → 0.14.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 +426 -103
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +43 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +43 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -360,6 +360,10 @@ interface MarkdownFormatterOptions {
|
|
|
360
360
|
includeSourceLinks?: boolean;
|
|
361
361
|
/** Custom renderers for doc entries */
|
|
362
362
|
customRenderers?: MarkdownRenderers;
|
|
363
|
+
/** Emit a stable per-scenario anchor for deep-linking. Returns the id, or undefined to skip. */
|
|
364
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
365
|
+
/** Render a badge line under a scenario heading (e.g. a what's-changed marker). Undefined to skip. */
|
|
366
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
/** Astro/Starlight formatter options */
|
|
@@ -487,6 +491,8 @@ interface ResolvedFormatterOptions {
|
|
|
487
491
|
ticketUrlTemplate?: string;
|
|
488
492
|
traceUrlTemplate?: string;
|
|
489
493
|
customRenderers?: MarkdownRenderers;
|
|
494
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
495
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
490
496
|
};
|
|
491
497
|
};
|
|
492
498
|
assetMode: "none" | "copy";
|
|
@@ -1924,6 +1930,19 @@ interface MarkdownOptions {
|
|
|
1924
1930
|
includeSourceLinks?: boolean;
|
|
1925
1931
|
/** Custom renderers for doc entries */
|
|
1926
1932
|
customRenderers?: MarkdownRenderers;
|
|
1933
|
+
/**
|
|
1934
|
+
* Emit a stable in-page anchor before each scenario heading, so external tools
|
|
1935
|
+
* can deep-link to a scenario by fragment. Given a test case, return the anchor
|
|
1936
|
+
* id (without `#`), or undefined to skip. Off by default — only the living-docs
|
|
1937
|
+
* site opts in, so plain markdown output is unchanged.
|
|
1938
|
+
*/
|
|
1939
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
1940
|
+
/**
|
|
1941
|
+
* Render a short badge line directly under a scenario heading (e.g. a
|
|
1942
|
+
* what's-changed marker like "🆕 New" / "⚠️ Regressed"). Return the markdown
|
|
1943
|
+
* line, or undefined to skip. Off by default — only the living-docs site opts in.
|
|
1944
|
+
*/
|
|
1945
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
1927
1946
|
}
|
|
1928
1947
|
/**
|
|
1929
1948
|
* Markdown Formatter.
|
package/dist/index.d.ts
CHANGED
|
@@ -360,6 +360,10 @@ interface MarkdownFormatterOptions {
|
|
|
360
360
|
includeSourceLinks?: boolean;
|
|
361
361
|
/** Custom renderers for doc entries */
|
|
362
362
|
customRenderers?: MarkdownRenderers;
|
|
363
|
+
/** Emit a stable per-scenario anchor for deep-linking. Returns the id, or undefined to skip. */
|
|
364
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
365
|
+
/** Render a badge line under a scenario heading (e.g. a what's-changed marker). Undefined to skip. */
|
|
366
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
/** Astro/Starlight formatter options */
|
|
@@ -487,6 +491,8 @@ interface ResolvedFormatterOptions {
|
|
|
487
491
|
ticketUrlTemplate?: string;
|
|
488
492
|
traceUrlTemplate?: string;
|
|
489
493
|
customRenderers?: MarkdownRenderers;
|
|
494
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
495
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
490
496
|
};
|
|
491
497
|
};
|
|
492
498
|
assetMode: "none" | "copy";
|
|
@@ -1924,6 +1930,19 @@ interface MarkdownOptions {
|
|
|
1924
1930
|
includeSourceLinks?: boolean;
|
|
1925
1931
|
/** Custom renderers for doc entries */
|
|
1926
1932
|
customRenderers?: MarkdownRenderers;
|
|
1933
|
+
/**
|
|
1934
|
+
* Emit a stable in-page anchor before each scenario heading, so external tools
|
|
1935
|
+
* can deep-link to a scenario by fragment. Given a test case, return the anchor
|
|
1936
|
+
* id (without `#`), or undefined to skip. Off by default — only the living-docs
|
|
1937
|
+
* site opts in, so plain markdown output is unchanged.
|
|
1938
|
+
*/
|
|
1939
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
1940
|
+
/**
|
|
1941
|
+
* Render a short badge line directly under a scenario heading (e.g. a
|
|
1942
|
+
* what's-changed marker like "🆕 New" / "⚠️ Regressed"). Return the markdown
|
|
1943
|
+
* line, or undefined to skip. Off by default — only the living-docs site opts in.
|
|
1944
|
+
*/
|
|
1945
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
1927
1946
|
}
|
|
1928
1947
|
/**
|
|
1929
1948
|
* Markdown Formatter.
|
package/dist/index.js
CHANGED
|
@@ -15124,7 +15124,9 @@ var MarkdownFormatter = class {
|
|
|
15124
15124
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
15125
15125
|
traceUrlTemplate: options.traceUrlTemplate,
|
|
15126
15126
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
15127
|
-
customRenderers: options.customRenderers
|
|
15127
|
+
customRenderers: options.customRenderers,
|
|
15128
|
+
scenarioAnchor: options.scenarioAnchor,
|
|
15129
|
+
scenarioBadge: options.scenarioBadge
|
|
15128
15130
|
};
|
|
15129
15131
|
}
|
|
15130
15132
|
/**
|
|
@@ -15311,6 +15313,11 @@ var MarkdownFormatter = class {
|
|
|
15311
15313
|
* Render a single scenario.
|
|
15312
15314
|
*/
|
|
15313
15315
|
renderScenario(lines, tc) {
|
|
15316
|
+
const anchorId = this.options.scenarioAnchor?.(tc);
|
|
15317
|
+
if (anchorId) {
|
|
15318
|
+
lines.push(`<a id="${anchorId}"></a>`);
|
|
15319
|
+
lines.push("");
|
|
15320
|
+
}
|
|
15314
15321
|
if (this.options.customRenderers?.renderScenarioHeader) {
|
|
15315
15322
|
const custom = this.options.customRenderers.renderScenarioHeader(tc);
|
|
15316
15323
|
if (custom !== null) {
|
|
@@ -15326,6 +15333,10 @@ var MarkdownFormatter = class {
|
|
|
15326
15333
|
icon = this.getStatusIcon(tc.status) + " ";
|
|
15327
15334
|
}
|
|
15328
15335
|
lines.push(`${headingPrefix} ${icon}${tc.story.scenario}`);
|
|
15336
|
+
const badge = this.options.scenarioBadge?.(tc);
|
|
15337
|
+
if (badge) {
|
|
15338
|
+
lines.push(badge);
|
|
15339
|
+
}
|
|
15329
15340
|
if (this.options.includeSourceLinks && this.options.permalinkBaseUrl && tc.sourceFile !== "unknown") {
|
|
15330
15341
|
const permalink = this.buildPermalink(tc);
|
|
15331
15342
|
lines.push(`Source: [${tc.sourceFile}](${permalink})`);
|
|
@@ -18142,12 +18153,16 @@ function groupBy7(items, keyFn) {
|
|
|
18142
18153
|
import * as fs5 from "fs";
|
|
18143
18154
|
import * as path6 from "path";
|
|
18144
18155
|
var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
|
|
18145
|
-
function
|
|
18156
|
+
function isRemoteRef(src) {
|
|
18146
18157
|
const trimmed = src.trim();
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18158
|
+
return SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
|
|
18159
|
+
}
|
|
18160
|
+
function isAbsoluteRef(src) {
|
|
18161
|
+
const trimmed = src.trim();
|
|
18162
|
+
return path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed);
|
|
18163
|
+
}
|
|
18164
|
+
function isRelativeLocalPath(src) {
|
|
18165
|
+
return !isRemoteRef(src) && !isAbsoluteRef(src);
|
|
18151
18166
|
}
|
|
18152
18167
|
function stripCodeContent(markdown) {
|
|
18153
18168
|
let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
@@ -18163,21 +18178,21 @@ function scanMarkdownAssets(markdown) {
|
|
|
18163
18178
|
let match;
|
|
18164
18179
|
while ((match = mdImageRe.exec(stripped)) !== null) {
|
|
18165
18180
|
const src = match[1].trim();
|
|
18166
|
-
if (
|
|
18181
|
+
if (!isRemoteRef(src)) {
|
|
18167
18182
|
found.add(src);
|
|
18168
18183
|
}
|
|
18169
18184
|
}
|
|
18170
18185
|
const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
|
|
18171
18186
|
while ((match = htmlSrcRe.exec(stripped)) !== null) {
|
|
18172
18187
|
const src = match[1].trim();
|
|
18173
|
-
if (
|
|
18188
|
+
if (!isRemoteRef(src)) {
|
|
18174
18189
|
found.add(src);
|
|
18175
18190
|
}
|
|
18176
18191
|
}
|
|
18177
18192
|
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
18178
18193
|
while ((match = posterRe.exec(stripped)) !== null) {
|
|
18179
18194
|
const src = match[1].trim();
|
|
18180
|
-
if (
|
|
18195
|
+
if (!isRemoteRef(src)) {
|
|
18181
18196
|
found.add(src);
|
|
18182
18197
|
}
|
|
18183
18198
|
}
|
|
@@ -18203,48 +18218,21 @@ function isCode(segment) {
|
|
|
18203
18218
|
const trimmed = segment.trimStart();
|
|
18204
18219
|
return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
|
|
18205
18220
|
}
|
|
18221
|
+
function resolveRewrite(trimmed, assetsBaseUrl, pathMap) {
|
|
18222
|
+
if (isRemoteRef(trimmed)) return null;
|
|
18223
|
+
if (pathMap) {
|
|
18224
|
+
const mapped = pathMap.get(trimmed);
|
|
18225
|
+
return mapped === void 0 ? null : `${assetsBaseUrl}/${mapped}`;
|
|
18226
|
+
}
|
|
18227
|
+
if (!isRelativeLocalPath(trimmed)) return null;
|
|
18228
|
+
return `${assetsBaseUrl}/${trimmed}`;
|
|
18229
|
+
}
|
|
18206
18230
|
function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18213
|
-
if (pathMap) {
|
|
18214
|
-
const mapped = pathMap.get(trimmed);
|
|
18215
|
-
if (mapped === void 0) return full;
|
|
18216
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18217
|
-
}
|
|
18218
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18219
|
-
}
|
|
18220
|
-
);
|
|
18221
|
-
result = result.replace(
|
|
18222
|
-
/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18223
|
-
(full, pre, src, post) => {
|
|
18224
|
-
const trimmed = src.trim();
|
|
18225
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18226
|
-
if (pathMap) {
|
|
18227
|
-
const mapped = pathMap.get(trimmed);
|
|
18228
|
-
if (mapped === void 0) return full;
|
|
18229
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18230
|
-
}
|
|
18231
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18232
|
-
}
|
|
18233
|
-
);
|
|
18234
|
-
result = result.replace(
|
|
18235
|
-
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18236
|
-
(full, pre, src, post) => {
|
|
18237
|
-
const trimmed = src.trim();
|
|
18238
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18239
|
-
if (pathMap) {
|
|
18240
|
-
const mapped = pathMap.get(trimmed);
|
|
18241
|
-
if (mapped === void 0) return full;
|
|
18242
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18243
|
-
}
|
|
18244
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18245
|
-
}
|
|
18246
|
-
);
|
|
18247
|
-
return result;
|
|
18231
|
+
const rewrite = (full, pre, src, post) => {
|
|
18232
|
+
const target = resolveRewrite(src.trim(), assetsBaseUrl, pathMap);
|
|
18233
|
+
return target === null ? full : `${pre}${target}${post}`;
|
|
18234
|
+
};
|
|
18235
|
+
return prose.replace(/(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g, rewrite).replace(/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi, rewrite).replace(/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi, rewrite);
|
|
18248
18236
|
}
|
|
18249
18237
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
18250
18238
|
return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
|
|
@@ -18261,8 +18249,9 @@ function copyMarkdownAssets(options) {
|
|
|
18261
18249
|
const pathMap = /* @__PURE__ */ new Map();
|
|
18262
18250
|
const missing = [];
|
|
18263
18251
|
for (const ref of refs) {
|
|
18264
|
-
const absPath = path6.resolve(markdownDir, ref);
|
|
18252
|
+
const absPath = isAbsoluteRef(ref) ? ref : path6.resolve(markdownDir, ref);
|
|
18265
18253
|
if (!fs5.existsSync(absPath)) {
|
|
18254
|
+
if (isAbsoluteRef(ref)) continue;
|
|
18266
18255
|
if (!allowMissing) {
|
|
18267
18256
|
throw new Error(`Asset not found: ${absPath}`);
|
|
18268
18257
|
}
|
|
@@ -21888,7 +21877,9 @@ var ReportGenerator = class {
|
|
|
21888
21877
|
permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
|
|
21889
21878
|
ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
|
|
21890
21879
|
traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
|
|
21891
|
-
customRenderers: options.astro?.markdown?.customRenderers
|
|
21880
|
+
customRenderers: options.astro?.markdown?.customRenderers,
|
|
21881
|
+
scenarioAnchor: options.astro?.markdown?.scenarioAnchor,
|
|
21882
|
+
scenarioBadge: options.astro?.markdown?.scenarioBadge
|
|
21892
21883
|
}
|
|
21893
21884
|
},
|
|
21894
21885
|
assetMode: options.assetMode ?? "none",
|