jamdesk 1.1.148 → 1.1.150
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/README.md +28 -0
- package/dist/__tests__/integration/fix-scan-integration.test.d.ts +2 -0
- package/dist/__tests__/integration/fix-scan-integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/fix-scan-integration.test.js +77 -0
- package/dist/__tests__/integration/fix-scan-integration.test.js.map +1 -0
- package/dist/__tests__/unit/ai-fix-client.test.d.ts +2 -0
- package/dist/__tests__/unit/ai-fix-client.test.d.ts.map +1 -0
- package/dist/__tests__/unit/ai-fix-client.test.js +64 -0
- package/dist/__tests__/unit/ai-fix-client.test.js.map +1 -0
- package/dist/__tests__/unit/docs-config.test.js +66 -0
- package/dist/__tests__/unit/docs-config.test.js.map +1 -1
- package/dist/__tests__/unit/fix-ai-fallback.test.d.ts +2 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.js +82 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.js.map +1 -0
- package/dist/__tests__/unit/fix-engine-sync.test.d.ts +2 -0
- package/dist/__tests__/unit/fix-engine-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix-engine-sync.test.js +16 -0
- package/dist/__tests__/unit/fix-engine-sync.test.js.map +1 -0
- package/dist/__tests__/unit/fix.test.d.ts +2 -0
- package/dist/__tests__/unit/fix.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix.test.js +61 -0
- package/dist/__tests__/unit/fix.test.js.map +1 -0
- package/dist/__tests__/unit/frontmatter-write.test.d.ts +2 -0
- package/dist/__tests__/unit/frontmatter-write.test.d.ts.map +1 -0
- package/dist/__tests__/unit/frontmatter-write.test.js +31 -0
- package/dist/__tests__/unit/frontmatter-write.test.js.map +1 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.d.ts +2 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.js +16 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.js.map +1 -0
- package/dist/__tests__/unit/missing-description-scan.test.d.ts +2 -0
- package/dist/__tests__/unit/missing-description-scan.test.d.ts.map +1 -0
- package/dist/__tests__/unit/missing-description-scan.test.js +36 -0
- package/dist/__tests__/unit/missing-description-scan.test.js.map +1 -0
- package/dist/__tests__/unit/seo-ai-schema.test.d.ts +2 -0
- package/dist/__tests__/unit/seo-ai-schema.test.d.ts.map +1 -0
- package/dist/__tests__/unit/seo-ai-schema.test.js +40 -0
- package/dist/__tests__/unit/seo-ai-schema.test.js.map +1 -0
- package/dist/commands/fix.d.ts +97 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +331 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-fix-client.d.ts +27 -0
- package/dist/lib/ai-fix-client.d.ts.map +1 -0
- package/dist/lib/ai-fix-client.js +45 -0
- package/dist/lib/ai-fix-client.js.map +1 -0
- package/dist/lib/deps.js +1 -1
- package/dist/lib/deps.js.map +1 -1
- package/dist/lib/docs-config.d.ts +1 -1
- package/dist/lib/docs-config.d.ts.map +1 -1
- package/dist/lib/docs-config.js +59 -4
- package/dist/lib/docs-config.js.map +1 -1
- package/dist/lib/fix-engine.d.ts +89 -0
- package/dist/lib/fix-engine.d.ts.map +1 -0
- package/dist/lib/fix-engine.js +236 -0
- package/dist/lib/fix-engine.js.map +1 -0
- package/dist/lib/frontmatter-write.d.ts +4 -0
- package/dist/lib/frontmatter-write.d.ts.map +1 -0
- package/dist/lib/frontmatter-write.js +10 -0
- package/dist/lib/frontmatter-write.js.map +1 -0
- package/dist/lib/heading-extractor.d.ts +63 -0
- package/dist/lib/heading-extractor.d.ts.map +1 -0
- package/dist/lib/heading-extractor.js +171 -0
- package/dist/lib/heading-extractor.js.map +1 -0
- package/dist/lib/missing-description-scan.d.ts +7 -0
- package/dist/lib/missing-description-scan.d.ts.map +1 -0
- package/dist/lib/missing-description-scan.js +20 -0
- package/dist/lib/missing-description-scan.js.map +1 -0
- package/package.json +3 -3
- package/vendored/components/mdx/Accordion.tsx +6 -1
- package/vendored/components/mdx/ApiEndpoint.tsx +1 -1
- package/vendored/components/mdx/MDXComponents.tsx +14 -1
- package/vendored/components/mdx/MermaidInner.tsx +2 -8
- package/vendored/components/mdx/OpenApiEndpoint.tsx +7 -2
- package/vendored/components/navigation/SocialFooter.tsx +9 -1
- package/vendored/lib/build/error-parser.ts +6 -3
- package/vendored/lib/docs-types.ts +1 -0
- package/vendored/lib/extract-faq.ts +54 -0
- package/vendored/lib/firestore-helpers.ts +2 -3
- package/vendored/lib/fix-engine.ts +293 -0
- package/vendored/lib/health-checks.ts +2 -2
- package/vendored/lib/json-ld.ts +113 -10
- package/vendored/lib/layout-helpers.tsx +14 -15
- package/vendored/lib/llms-flag.ts +5 -0
- package/vendored/lib/logo-warning.ts +35 -0
- package/vendored/lib/noindex-warning.ts +14 -0
- package/vendored/lib/r2-cleanup.ts +26 -0
- package/vendored/lib/render-doc-page.tsx +11 -2
- package/vendored/lib/search.ts +2 -2
- package/vendored/lib/seo.ts +49 -19
- package/vendored/lib/static-artifacts.ts +2 -2
- package/vendored/lib/ui-strings.ts +6 -0
- package/vendored/lib/validate-config.ts +63 -4
- package/vendored/lib/validate-page-frontmatter.ts +30 -0
- package/vendored/schema/docs-schema.json +10 -0
- package/vendored/scripts/build-search-index.cjs +2 -2
- package/vendored/shared/status-reporter.ts +27 -1
- package/vendored/shared/types.ts +1 -0
- package/vendored/workspace-package-lock.json +39 -39
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Doc-Warning Fix Engine.
|
|
3
|
+
*
|
|
4
|
+
* AUTO-SYNCED: This file is automatically copied from build-service by vendor.js.
|
|
5
|
+
* DO NOT EDIT directly - edit builder/build-service/lib/fix-engine.ts instead.
|
|
6
|
+
*/
|
|
7
|
+
/** A markdown heading entry (not a <Step> or <Update> component anchor). */
|
|
8
|
+
export function isMarkdownHeading(h) {
|
|
9
|
+
return h.stepNumber == null && !h.isUpdate;
|
|
10
|
+
}
|
|
11
|
+
/** True iff `candidate` is an exact heading id on `headings`. The canonical
|
|
12
|
+
* guard for any externally-sourced (e.g. AI) anchor suggestion — the engine
|
|
13
|
+
* itself uses the same `allIds.includes(...)` check inside fixBrokenLink's
|
|
14
|
+
* `emit`. Phase 2's AI tier MUST route every suggestion through this so a
|
|
15
|
+
* hallucinated fragment can never be written. Pure, dependency-free. */
|
|
16
|
+
export function isRealAnchor(candidate, headings) {
|
|
17
|
+
if (!candidate)
|
|
18
|
+
return false;
|
|
19
|
+
return headings.some((h) => h.id === candidate);
|
|
20
|
+
}
|
|
21
|
+
/** Substring appended to the skip reason when the deterministic engine defers an
|
|
22
|
+
* anchor to the AI tier. Exported so the AI-tier callers match on this constant
|
|
23
|
+
* instead of a re-typed literal — producer and consumer cannot drift. */
|
|
24
|
+
export const NEEDS_AI_MARKER = '(needs AI)';
|
|
25
|
+
// ---- Edit application -----------------------------------------------------
|
|
26
|
+
// A flagged href is always the target of a markdown link `](href)` or an
|
|
27
|
+
// `href="…"`/`href='…'` attribute, so in source it is bounded on the left by a
|
|
28
|
+
// delimiter — never by another URL/path character. A match whose preceding char
|
|
29
|
+
// IS a path character is the href appearing as a SUBSTRING inside a longer URL
|
|
30
|
+
// (e.g. `/setup#x` inside `/guide/setup#x`) and must be skipped, or the edit
|
|
31
|
+
// would corrupt an unrelated, valid link on the same line.
|
|
32
|
+
const URL_PATH_CHAR = /[A-Za-z0-9\-._~%/#?:@]/;
|
|
33
|
+
/** Index of the next occurrence of `href` at/after `from` that sits at a link
|
|
34
|
+
* boundary (not as a substring inside a longer path), or -1. */
|
|
35
|
+
function findHrefBoundary(line, href, from) {
|
|
36
|
+
let pos = line.indexOf(href, from);
|
|
37
|
+
while (pos !== -1) {
|
|
38
|
+
if (pos === 0 || !URL_PATH_CHAR.test(line[pos - 1]))
|
|
39
|
+
return pos;
|
|
40
|
+
pos = line.indexOf(href, pos + 1);
|
|
41
|
+
}
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Apply edits, replacing each edit's oldText with newText on its specific
|
|
46
|
+
* (1-indexed) source line. Line-scoped, so an identical href on another line
|
|
47
|
+
* (e.g. a code sample) is never touched. Within a line: oldText is matched at a
|
|
48
|
+
* link boundary (so a short href is never replaced inside a longer one),
|
|
49
|
+
* matching runs against the ORIGINAL line (so an edit whose newText contains its
|
|
50
|
+
* oldText is never re-matched), and edits apply right-to-left so earlier
|
|
51
|
+
* positions keep their indices. Identical oldTexts on one line consume
|
|
52
|
+
* successive occurrences. Throws if a line is out of range or an oldText is
|
|
53
|
+
* absent — a missing match means the source changed since the fix was derived,
|
|
54
|
+
* and a partial/wrong edit must never be written.
|
|
55
|
+
*/
|
|
56
|
+
export function applyEdits(content, edits) {
|
|
57
|
+
if (edits.length === 0)
|
|
58
|
+
return content;
|
|
59
|
+
const lines = content.split('\n');
|
|
60
|
+
const byLine = new Map();
|
|
61
|
+
for (const edit of edits) {
|
|
62
|
+
const list = byLine.get(edit.line);
|
|
63
|
+
if (list)
|
|
64
|
+
list.push(edit);
|
|
65
|
+
else
|
|
66
|
+
byLine.set(edit.line, [edit]);
|
|
67
|
+
}
|
|
68
|
+
for (const [line, lineEdits] of byLine) {
|
|
69
|
+
const idx = line - 1;
|
|
70
|
+
if (idx < 0 || idx >= lines.length) {
|
|
71
|
+
throw new Error(`applyEdits: line ${line} out of range (${lines.length} lines)`);
|
|
72
|
+
}
|
|
73
|
+
const src = lines[idx];
|
|
74
|
+
// Resolve every edit to a concrete [start,end) span on the ORIGINAL line.
|
|
75
|
+
const spans = [];
|
|
76
|
+
const nextFrom = new Map();
|
|
77
|
+
for (const edit of lineEdits) {
|
|
78
|
+
const from = nextFrom.get(edit.oldText) ?? 0;
|
|
79
|
+
const start = findHrefBoundary(src, edit.oldText, from);
|
|
80
|
+
if (start === -1) {
|
|
81
|
+
throw new Error(`applyEdits: oldText not found on line ${line}: ${JSON.stringify(edit.oldText)}`);
|
|
82
|
+
}
|
|
83
|
+
const end = start + edit.oldText.length;
|
|
84
|
+
nextFrom.set(edit.oldText, end);
|
|
85
|
+
spans.push({ start, end, newText: edit.newText });
|
|
86
|
+
}
|
|
87
|
+
// Apply right-to-left so an earlier span's indices survive later edits.
|
|
88
|
+
spans.sort((a, b) => b.start - a.start);
|
|
89
|
+
let out = src;
|
|
90
|
+
for (const s of spans)
|
|
91
|
+
out = out.slice(0, s.start) + s.newText + out.slice(s.end);
|
|
92
|
+
lines[idx] = out;
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
// ---- Distance / nearest-slug (ported from validate-links.cjs) -------------
|
|
97
|
+
const MAX_SUGGESTION_DISTANCE = 2;
|
|
98
|
+
export function levenshtein(a, b) {
|
|
99
|
+
if (a === b)
|
|
100
|
+
return 0;
|
|
101
|
+
if (a.length === 0)
|
|
102
|
+
return b.length;
|
|
103
|
+
if (b.length === 0)
|
|
104
|
+
return a.length;
|
|
105
|
+
let prev = new Array(b.length + 1);
|
|
106
|
+
let curr = new Array(b.length + 1);
|
|
107
|
+
for (let j = 0; j <= b.length; j++)
|
|
108
|
+
prev[j] = j;
|
|
109
|
+
for (let i = 0; i < a.length; i++) {
|
|
110
|
+
curr[0] = i + 1;
|
|
111
|
+
for (let j = 0; j < b.length; j++) {
|
|
112
|
+
const cost = a[i] === b[j] ? 0 : 1;
|
|
113
|
+
curr[j + 1] = Math.min(curr[j] + 1, prev[j + 1] + 1, prev[j] + cost);
|
|
114
|
+
}
|
|
115
|
+
[prev, curr] = [curr, prev];
|
|
116
|
+
}
|
|
117
|
+
return prev[b.length];
|
|
118
|
+
}
|
|
119
|
+
export function closestSlug(fragment, slugs) {
|
|
120
|
+
if (!fragment)
|
|
121
|
+
return null;
|
|
122
|
+
let best = null;
|
|
123
|
+
let bestDist = Infinity;
|
|
124
|
+
let secondDist = Infinity;
|
|
125
|
+
for (const slug of slugs) {
|
|
126
|
+
if (Math.abs(fragment.length - slug.length) > MAX_SUGGESTION_DISTANCE)
|
|
127
|
+
continue;
|
|
128
|
+
const d = levenshtein(fragment, slug);
|
|
129
|
+
if (d < bestDist) {
|
|
130
|
+
secondDist = bestDist;
|
|
131
|
+
bestDist = d;
|
|
132
|
+
best = slug;
|
|
133
|
+
}
|
|
134
|
+
else if (d < secondDist) {
|
|
135
|
+
secondDist = d;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return best !== null && bestDist >= 1 && bestDist <= MAX_SUGGESTION_DISTANCE && bestDist < secondDist
|
|
139
|
+
? best
|
|
140
|
+
: null;
|
|
141
|
+
}
|
|
142
|
+
// ---- Link + locale helpers ------------------------------------------------
|
|
143
|
+
export function parseHref(href) {
|
|
144
|
+
const i = href.indexOf('#');
|
|
145
|
+
if (i === -1)
|
|
146
|
+
return { path: href, fragment: null };
|
|
147
|
+
return { path: i === 0 ? null : href.slice(0, i), fragment: href.slice(i + 1) || null };
|
|
148
|
+
}
|
|
149
|
+
export function normalizeLinkPath(linkPath, hostAtDocs) {
|
|
150
|
+
let p = linkPath;
|
|
151
|
+
if (hostAtDocs && p.startsWith('/docs/'))
|
|
152
|
+
p = p.slice('/docs/'.length);
|
|
153
|
+
if (p.startsWith('/'))
|
|
154
|
+
p = p.slice(1);
|
|
155
|
+
return p.replace(/\.mdx$/, '');
|
|
156
|
+
}
|
|
157
|
+
/** The default-locale twin of a localized page path, or null if already default. */
|
|
158
|
+
export function defaultTwinPath(pagePath, locales) {
|
|
159
|
+
const segments = pagePath.split('/');
|
|
160
|
+
const first = segments[0];
|
|
161
|
+
if (first !== locales.defaultCode && locales.codes.includes(first)) {
|
|
162
|
+
return segments.slice(1).join('/');
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
// ---- Broken-link fixer ----------------------------------------------------
|
|
167
|
+
function skip(reason) {
|
|
168
|
+
return { status: 'skipped', edits: [], reason };
|
|
169
|
+
}
|
|
170
|
+
export const fixBrokenLink = (warning, ctx) => {
|
|
171
|
+
const { path: linkPath, fragment } = parseHref(warning.link);
|
|
172
|
+
if (!fragment)
|
|
173
|
+
return skip('not a fragment link — out of scope for phase 1');
|
|
174
|
+
if (!linkPath)
|
|
175
|
+
return skip('same-page fragment — out of scope for phase 1');
|
|
176
|
+
if (warning.line == null)
|
|
177
|
+
return skip('warning has no line — cannot place a safe edit');
|
|
178
|
+
const targetPage = ctx.resolveLinkPagePath(linkPath);
|
|
179
|
+
if (!targetPage)
|
|
180
|
+
return skip('target page could not be resolved');
|
|
181
|
+
const targetHeadings = ctx.headingsOf(targetPage);
|
|
182
|
+
if (!targetHeadings)
|
|
183
|
+
return skip('target page headings could not be read');
|
|
184
|
+
const allIds = targetHeadings.map((h) => h.id);
|
|
185
|
+
// (0) Re-derivation: may already be valid in current source.
|
|
186
|
+
if (allIds.includes(fragment))
|
|
187
|
+
return skip('already valid — fragment exists on target');
|
|
188
|
+
// Emit only a fragment that is a REAL anchor on the target (never point at a
|
|
189
|
+
// non-existent or wrong-kind id), reconstructing the href (no substring replace).
|
|
190
|
+
const emit = (candidate, reason) => {
|
|
191
|
+
if (!allIds.includes(candidate))
|
|
192
|
+
return skip('internal: candidate is not a real anchor — refusing');
|
|
193
|
+
return {
|
|
194
|
+
status: 'fixed',
|
|
195
|
+
edits: [{ file: warning.file, line: warning.line, oldText: warning.link, newText: linkPath + '#' + candidate }],
|
|
196
|
+
reason,
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
// (1) Cross-locale positional map FIRST (high precision for localized targets).
|
|
200
|
+
// Ordinals over MARKDOWN headings only (so <Step>/<Update> can't misalign them),
|
|
201
|
+
// but the emitted id is the target heading's REAL id (full-set dedup).
|
|
202
|
+
const twinPage = defaultTwinPath(targetPage, ctx.locales);
|
|
203
|
+
if (twinPage) {
|
|
204
|
+
const twinHeadings = ctx.headingsOf(twinPage);
|
|
205
|
+
if (twinHeadings) {
|
|
206
|
+
const twinMd = twinHeadings.filter(isMarkdownHeading);
|
|
207
|
+
const targetMd = targetHeadings.filter(isMarkdownHeading);
|
|
208
|
+
const ordinal = twinMd.findIndex((h) => h.id === fragment);
|
|
209
|
+
if (ordinal !== -1 && twinMd.length === targetMd.length && targetMd[ordinal]) {
|
|
210
|
+
return emit(targetMd[ordinal].id, `cross-locale positional map: #${fragment} → #${targetMd[ordinal].id}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// (2) Near-miss fallback (typos within the same language).
|
|
215
|
+
const near = closestSlug(fragment, allIds);
|
|
216
|
+
if (near)
|
|
217
|
+
return emit(near, `near-miss: #${fragment} → #${near}`);
|
|
218
|
+
return skip(`no confident deterministic fix — structures diverge or target is ambiguous ${NEEDS_AI_MARKER}`);
|
|
219
|
+
};
|
|
220
|
+
// ---- Registry + engine entry point ----------------------------------------
|
|
221
|
+
/** Fixer registry keyed by BuildWarningType. Phase 1: broken_link only. */
|
|
222
|
+
export const FIXERS = {
|
|
223
|
+
broken_link: fixBrokenLink,
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Run the registered fixer for a warning. Deterministic-first is implemented
|
|
227
|
+
* inside each fixer (exact strategies, then 'skipped' for AI cases). The AI
|
|
228
|
+
* fallback tier is wired in Phase 2; until then a 'skipped' result is terminal.
|
|
229
|
+
*/
|
|
230
|
+
export function runFixer(warning, ctx) {
|
|
231
|
+
const fixer = FIXERS[warning.type];
|
|
232
|
+
if (!fixer)
|
|
233
|
+
return skip(`no fixer registered for type "${warning.type}"`);
|
|
234
|
+
return fixer(warning, ctx);
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=fix-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix-engine.js","sourceRoot":"","sources":["../../src/lib/fix-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Set a single frontmatter field, preserving the body. gray-matter round-trips
|
|
2
|
+
* via matter.stringify (already used elsewhere in the CLI dep tree). */
|
|
3
|
+
export declare function setFrontmatterField(source: string, key: string, value: string): string;
|
|
4
|
+
//# sourceMappingURL=frontmatter-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-write.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter-write.ts"],"names":[],"mappings":"AAGA;yEACyE;AACzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAItF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// builder/cli/src/lib/frontmatter-write.ts
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
/** Set a single frontmatter field, preserving the body. gray-matter round-trips
|
|
4
|
+
* via matter.stringify (already used elsewhere in the CLI dep tree). */
|
|
5
|
+
export function setFrontmatterField(source, key, value) {
|
|
6
|
+
const parsed = matter(source);
|
|
7
|
+
const data = { ...parsed.data, [key]: value };
|
|
8
|
+
return matter.stringify(parsed.content, data);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=frontmatter-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-write.js","sourceRoot":"","sources":["../../src/lib/frontmatter-write.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC;yEACyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,GAAW,EAAE,KAAa;IAC5E,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heading Extractor (slug ids matching rehype-slug / the real DOM).
|
|
3
|
+
*
|
|
4
|
+
* AUTO-SYNCED: This file is automatically copied from build-service by vendor.js.
|
|
5
|
+
* DO NOT EDIT directly - edit builder/build-service/lib/heading-extractor.ts instead.
|
|
6
|
+
*/
|
|
7
|
+
export interface HeadingInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
text: string;
|
|
10
|
+
level: number;
|
|
11
|
+
line: number;
|
|
12
|
+
/** When the entry is a <Step> inside a <Steps> block, this is its 1-based index within the block. */
|
|
13
|
+
stepNumber?: number;
|
|
14
|
+
/** True when the entry came from an <Update label="..."> anchor (vs a markdown heading). */
|
|
15
|
+
isUpdate?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a URL-friendly slug from heading text.
|
|
19
|
+
*
|
|
20
|
+
* Delegates to github-slugger — the SAME library `rehype-slug` uses to assign the
|
|
21
|
+
* real heading `id` attributes during MDX compile (wired in render-doc-page.tsx).
|
|
22
|
+
* Using it here keeps the "On this page" TOC, <Update>/<Step> anchors, and the
|
|
23
|
+
* link validator byte-for-byte aligned with the actual DOM ids — including for
|
|
24
|
+
* non-ASCII headings, where the previous `[^a-z0-9]+ -> '-'` implementation
|
|
25
|
+
* diverged (accents/apostrophes/underscores became dashes) and produced dead
|
|
26
|
+
* anchors (e.g. heading "Abrir cualquier página" → real id `abrir-cualquier-página`,
|
|
27
|
+
* but the old slug was `abrir-cualquier-p-gina`).
|
|
28
|
+
*
|
|
29
|
+
* Stateless: repeated calls with the same text return the same slug. For
|
|
30
|
+
* per-document uniqueness across duplicate headings, use a `GithubSlugger`
|
|
31
|
+
* instance (as extractHeadings does) — it mirrors rehype-slug's `-1`/`-2`
|
|
32
|
+
* suffixing exactly.
|
|
33
|
+
*
|
|
34
|
+
* Mirrored in scripts/validate-links.cjs (CJS — can't import the ESM-only
|
|
35
|
+
* github-slugger), where the algorithm + regex are replicated and guarded by a
|
|
36
|
+
* drift test (`validate-links-slug.test.ts`). Keep both in sync.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateSlug(text: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Extract all heading slugs from MDX content.
|
|
41
|
+
* Includes markdown headings, <Update label="..."> component anchors,
|
|
42
|
+
* and <Step title="..."> entries inside <Steps> blocks (with per-block numbering).
|
|
43
|
+
* Skips content inside fenced code blocks.
|
|
44
|
+
*
|
|
45
|
+
* A single page-scoped `GithubSlugger` assigns ids so duplicate titles (across
|
|
46
|
+
* H2s, Update labels, and Steps) get `-1`, `-2`, ... suffixes in source order —
|
|
47
|
+
* the same algorithm rehype-slug applies to the real markdown-heading DOM ids.
|
|
48
|
+
*/
|
|
49
|
+
export declare function extractHeadings(content: string): HeadingInfo[];
|
|
50
|
+
/**
|
|
51
|
+
* Build a map of page path -> heading slug set.
|
|
52
|
+
* Keys are page paths without .mdx extension (matching docs.json navigation format).
|
|
53
|
+
* Skips API endpoint pages (they generate dynamic anchors at render time).
|
|
54
|
+
*
|
|
55
|
+
* Unlike buildHeadingMapFromFiles() in validate-links.cjs, this doesn't add dual
|
|
56
|
+
* keys for index pages. The CJS validator handles index resolution via
|
|
57
|
+
* getTargetHeadingSlugs() which derives the key from the filesystem path.
|
|
58
|
+
*
|
|
59
|
+
* @param fileContents - Map of relative file paths (with .mdx) to content.
|
|
60
|
+
* Keys come from build.ts fileContents Map (e.g., 'getting-started.mdx', 'api/auth.mdx').
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildHeadingMap(fileContents: Map<string, string>): Map<string, Set<string>>;
|
|
63
|
+
//# sourceMappingURL=heading-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heading-extractor.d.ts","sourceRoot":"","sources":["../../src/lib/heading-extractor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,qGAAqG;IACrG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAgF9D;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAU1B"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heading Extractor (slug ids matching rehype-slug / the real DOM).
|
|
3
|
+
*
|
|
4
|
+
* AUTO-SYNCED: This file is automatically copied from build-service by vendor.js.
|
|
5
|
+
* DO NOT EDIT directly - edit builder/build-service/lib/heading-extractor.ts instead.
|
|
6
|
+
*/
|
|
7
|
+
import GithubSlugger, { slug as githubSlug } from 'github-slugger';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a URL-friendly slug from heading text.
|
|
10
|
+
*
|
|
11
|
+
* Delegates to github-slugger — the SAME library `rehype-slug` uses to assign the
|
|
12
|
+
* real heading `id` attributes during MDX compile (wired in render-doc-page.tsx).
|
|
13
|
+
* Using it here keeps the "On this page" TOC, <Update>/<Step> anchors, and the
|
|
14
|
+
* link validator byte-for-byte aligned with the actual DOM ids — including for
|
|
15
|
+
* non-ASCII headings, where the previous `[^a-z0-9]+ -> '-'` implementation
|
|
16
|
+
* diverged (accents/apostrophes/underscores became dashes) and produced dead
|
|
17
|
+
* anchors (e.g. heading "Abrir cualquier página" → real id `abrir-cualquier-página`,
|
|
18
|
+
* but the old slug was `abrir-cualquier-p-gina`).
|
|
19
|
+
*
|
|
20
|
+
* Stateless: repeated calls with the same text return the same slug. For
|
|
21
|
+
* per-document uniqueness across duplicate headings, use a `GithubSlugger`
|
|
22
|
+
* instance (as extractHeadings does) — it mirrors rehype-slug's `-1`/`-2`
|
|
23
|
+
* suffixing exactly.
|
|
24
|
+
*
|
|
25
|
+
* Mirrored in scripts/validate-links.cjs (CJS — can't import the ESM-only
|
|
26
|
+
* github-slugger), where the algorithm + regex are replicated and guarded by a
|
|
27
|
+
* drift test (`validate-links-slug.test.ts`). Keep both in sync.
|
|
28
|
+
*/
|
|
29
|
+
export function generateSlug(text) {
|
|
30
|
+
return githubSlug(text);
|
|
31
|
+
}
|
|
32
|
+
const HEADING_REGEX = /^(#{1,6})\s+(.+)$/;
|
|
33
|
+
const FENCE_REGEX = /^(`{3,}|~{3,})/;
|
|
34
|
+
// Global + matchAll (like STEP_TITLE_REGEX below) so multiple inline <Update>
|
|
35
|
+
// tags on one line each get an anchor, and `[^>]*` so `label=` need not be the
|
|
36
|
+
// first attribute (e.g. `<Update date=".." label="..">`). Alternation (not a
|
|
37
|
+
// character class) keeps an apostrophe inside a double-quoted label from
|
|
38
|
+
// terminating the capture. MUST only be used with matchAll — direct test/exec
|
|
39
|
+
// would share lastIndex across calls and miscount.
|
|
40
|
+
const UPDATE_LABEL_REGEX = /<Update\s+[^>]*label=(?:"([^"]+)"|'([^']+)')/g;
|
|
41
|
+
const STEPS_OPEN_REGEX = /<Steps(\s|>)/;
|
|
42
|
+
const STEPS_CLOSE_REGEX = /<\/Steps>/;
|
|
43
|
+
// Global flag — we iterate with matchAll so authors who inline multiple
|
|
44
|
+
// <Step> tags on one line get every one numbered.
|
|
45
|
+
// Alternation (not a character class) keeps the capture from terminating on
|
|
46
|
+
// apostrophes inside double-quoted attribute values (e.g. `<Step title="You're Live">`).
|
|
47
|
+
// MUST only be used with matchAll — direct `test`/`exec` would share
|
|
48
|
+
// lastIndex across calls and miscount.
|
|
49
|
+
const STEP_TITLE_REGEX = /<Step\s+[^>]*title=(?:"([^"]+)"|'([^']+)')/g;
|
|
50
|
+
/**
|
|
51
|
+
* Extract all heading slugs from MDX content.
|
|
52
|
+
* Includes markdown headings, <Update label="..."> component anchors,
|
|
53
|
+
* and <Step title="..."> entries inside <Steps> blocks (with per-block numbering).
|
|
54
|
+
* Skips content inside fenced code blocks.
|
|
55
|
+
*
|
|
56
|
+
* A single page-scoped `GithubSlugger` assigns ids so duplicate titles (across
|
|
57
|
+
* H2s, Update labels, and Steps) get `-1`, `-2`, ... suffixes in source order —
|
|
58
|
+
* the same algorithm rehype-slug applies to the real markdown-heading DOM ids.
|
|
59
|
+
*/
|
|
60
|
+
export function extractHeadings(content) {
|
|
61
|
+
const headings = [];
|
|
62
|
+
const lines = content.split('\n');
|
|
63
|
+
let inCodeBlock = false;
|
|
64
|
+
let fencePattern = '';
|
|
65
|
+
// Boolean (not depth counter) — resets on every <Steps> open so an unclosed
|
|
66
|
+
// block can't stick the counter across subsequent blocks.
|
|
67
|
+
let inStepsBlock = false;
|
|
68
|
+
let stepCounter = 0;
|
|
69
|
+
// Page-scoped slugger so duplicate titles get -1, -2, ... suffixes in source
|
|
70
|
+
// order, matching rehype-slug's per-document GithubSlugger.
|
|
71
|
+
const slugger = new GithubSlugger();
|
|
72
|
+
for (let i = 0; i < lines.length; i++) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
const fenceMatch = line.match(FENCE_REGEX);
|
|
75
|
+
if (fenceMatch) {
|
|
76
|
+
if (!inCodeBlock) {
|
|
77
|
+
inCodeBlock = true;
|
|
78
|
+
fencePattern = fenceMatch[1];
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
else if (line.startsWith(fencePattern)) {
|
|
82
|
+
inCodeBlock = false;
|
|
83
|
+
fencePattern = '';
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (inCodeBlock)
|
|
88
|
+
continue;
|
|
89
|
+
// <Steps> open resets the per-block counter unconditionally. Ordering
|
|
90
|
+
// matters: open-reset must run BEFORE step matching on the same line so
|
|
91
|
+
// `<Steps><Step title="A">...</Step>` numbers "A" as 1.
|
|
92
|
+
if (STEPS_OPEN_REGEX.test(line)) {
|
|
93
|
+
inStepsBlock = true;
|
|
94
|
+
stepCounter = 0;
|
|
95
|
+
}
|
|
96
|
+
const headingMatch = line.match(HEADING_REGEX);
|
|
97
|
+
if (headingMatch) {
|
|
98
|
+
const level = headingMatch[1].length;
|
|
99
|
+
const text = headingMatch[2].trim();
|
|
100
|
+
if (generateSlug(text)) {
|
|
101
|
+
const id = slugger.slug(text);
|
|
102
|
+
headings.push({ id, text, level, line: i + 1 });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const match of line.matchAll(UPDATE_LABEL_REGEX)) {
|
|
106
|
+
const text = match[1] ?? match[2];
|
|
107
|
+
if (!generateSlug(text))
|
|
108
|
+
continue;
|
|
109
|
+
const id = slugger.slug(text);
|
|
110
|
+
headings.push({ id, text, level: 2, line: i + 1, isUpdate: true });
|
|
111
|
+
}
|
|
112
|
+
if (inStepsBlock) {
|
|
113
|
+
for (const match of line.matchAll(STEP_TITLE_REGEX)) {
|
|
114
|
+
const text = match[1] ?? match[2];
|
|
115
|
+
if (!generateSlug(text))
|
|
116
|
+
continue;
|
|
117
|
+
stepCounter += 1;
|
|
118
|
+
const id = slugger.slug(text);
|
|
119
|
+
headings.push({
|
|
120
|
+
id,
|
|
121
|
+
text,
|
|
122
|
+
level: 3,
|
|
123
|
+
line: i + 1,
|
|
124
|
+
stepNumber: stepCounter,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// </Steps> close runs AFTER step matching so `</Steps>` on the same line
|
|
129
|
+
// as the last <Step> still picks up that step.
|
|
130
|
+
if (STEPS_CLOSE_REGEX.test(line)) {
|
|
131
|
+
inStepsBlock = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return headings;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if MDX content has api: or openapi: in its frontmatter.
|
|
138
|
+
* API endpoint pages generate dynamic anchor IDs at render time (#param-*, etc.)
|
|
139
|
+
* that aren't in the MDX source, so fragment validation is skipped for them.
|
|
140
|
+
*/
|
|
141
|
+
function hasApiFrontmatter(content) {
|
|
142
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
143
|
+
if (!match)
|
|
144
|
+
return false;
|
|
145
|
+
return /^(openapi|api):/m.test(match[1]);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build a map of page path -> heading slug set.
|
|
149
|
+
* Keys are page paths without .mdx extension (matching docs.json navigation format).
|
|
150
|
+
* Skips API endpoint pages (they generate dynamic anchors at render time).
|
|
151
|
+
*
|
|
152
|
+
* Unlike buildHeadingMapFromFiles() in validate-links.cjs, this doesn't add dual
|
|
153
|
+
* keys for index pages. The CJS validator handles index resolution via
|
|
154
|
+
* getTargetHeadingSlugs() which derives the key from the filesystem path.
|
|
155
|
+
*
|
|
156
|
+
* @param fileContents - Map of relative file paths (with .mdx) to content.
|
|
157
|
+
* Keys come from build.ts fileContents Map (e.g., 'getting-started.mdx', 'api/auth.mdx').
|
|
158
|
+
*/
|
|
159
|
+
export function buildHeadingMap(fileContents) {
|
|
160
|
+
const map = new Map();
|
|
161
|
+
for (const [filePath, content] of fileContents) {
|
|
162
|
+
if (hasApiFrontmatter(content))
|
|
163
|
+
continue;
|
|
164
|
+
const headings = extractHeadings(content);
|
|
165
|
+
const slugs = new Set(headings.map((h) => h.id));
|
|
166
|
+
const pagePath = filePath.replace(/\.mdx$/, '');
|
|
167
|
+
map.set(pagePath, slugs);
|
|
168
|
+
}
|
|
169
|
+
return map;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=heading-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heading-extractor.js","sourceRoot":"","sources":["../../src/lib/heading-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface MissingDescription {
|
|
2
|
+
file: string;
|
|
3
|
+
title: string;
|
|
4
|
+
pageText: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function scanMissingDescriptions(pagePaths: string[], readPage: (p: string) => string | null): MissingDescription[];
|
|
7
|
+
//# sourceMappingURL=missing-description-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-description-scan.d.ts","sourceRoot":"","sources":["../../src/lib/missing-description-scan.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;CAAE;AAEtF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACrC,kBAAkB,EAAE,CAetB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// builder/cli/src/lib/missing-description-scan.ts
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
export function scanMissingDescriptions(pagePaths, readPage) {
|
|
4
|
+
const out = [];
|
|
5
|
+
for (const page of pagePaths) {
|
|
6
|
+
const src = readPage(page);
|
|
7
|
+
if (src == null)
|
|
8
|
+
continue;
|
|
9
|
+
const parsed = matter(src);
|
|
10
|
+
const desc = parsed.data.description?.trim();
|
|
11
|
+
if (desc)
|
|
12
|
+
continue; // already has one
|
|
13
|
+
const title = parsed.data.title?.trim() ||
|
|
14
|
+
parsed.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ||
|
|
15
|
+
page.split('/').pop() || page;
|
|
16
|
+
out.push({ file: `${page}.mdx`, title, pageText: parsed.content.trim() });
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=missing-description-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-description-scan.js","sourceRoot":"","sources":["../../src/lib/missing-description-scan.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,OAAO,MAAM,MAAM,aAAa,CAAC;AAIjC,MAAM,UAAU,uBAAuB,CACrC,SAAmB,EACnB,QAAsC;IAEtC,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,GAAG,IAAI,IAAI;YAAE,SAAS;QAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAI,MAAM,CAAC,IAAI,CAAC,WAAkC,EAAE,IAAI,EAAE,CAAC;QACrE,IAAI,IAAI;YAAE,SAAS,CAAC,kBAAkB;QACtC,MAAM,KAAK,GACR,MAAM,CAAC,IAAI,CAAC,KAA4B,EAAE,IAAI,EAAE;YACjD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;YAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.150",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -128,9 +128,9 @@
|
|
|
128
128
|
"devDependencies": {
|
|
129
129
|
"@mdx-js/mdx": "^3.1.1",
|
|
130
130
|
"@types/fs-extra": "^11.0.0",
|
|
131
|
-
"@types/node": "^25.
|
|
131
|
+
"@types/node": "^25.9.3",
|
|
132
132
|
"typescript": "^6.0.2",
|
|
133
|
-
"vitest": "^4.1.
|
|
133
|
+
"vitest": "^4.1.9"
|
|
134
134
|
},
|
|
135
135
|
"engines": {
|
|
136
136
|
"node": ">=20.0.0"
|
|
@@ -35,6 +35,10 @@ export const Accordion = memo(function Accordion({
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<div className="border border-[var(--color-border)] rounded-lg mb-2 last:mb-0 not-prose overflow-hidden">
|
|
38
|
+
{/* WAI-ARIA accordion pattern: the heading WRAPS the toggle button. A heading
|
|
39
|
+
nested inside the button would be pruned from the a11y tree (button name is
|
|
40
|
+
computed from contents) and flagged by axe — so it goes on the outside. */}
|
|
41
|
+
<div role="heading" aria-level={3}>
|
|
38
42
|
<button
|
|
39
43
|
type="button"
|
|
40
44
|
onClick={() => setIsOpen(!isOpen)}
|
|
@@ -66,7 +70,8 @@ export const Accordion = memo(function Accordion({
|
|
|
66
70
|
aria-hidden="true"
|
|
67
71
|
/>
|
|
68
72
|
</button>
|
|
69
|
-
|
|
73
|
+
</div>
|
|
74
|
+
|
|
70
75
|
{/* Content */}
|
|
71
76
|
<div
|
|
72
77
|
className={`overflow-hidden transition-all duration-200 ${
|
|
@@ -64,7 +64,7 @@ export function ApiEndpoint({ method, path, baseUrl = 'https://api.jamdesk.com/a
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
|
-
<div data-api-endpoint="true" className="flex items-center gap-3 px-3 py-1.5 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-primary)] mb-4 not-prose">
|
|
67
|
+
<div data-api-endpoint="true" data-markdown-ignore className="flex items-center gap-3 px-3 py-1.5 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-primary)] mb-4 not-prose">
|
|
68
68
|
{/* Method badge */}
|
|
69
69
|
<span className={`px-2.5 py-1 text-xs font-bold rounded-md ${colors.bg} ${colors.text}`}>
|
|
70
70
|
{method}
|
|
@@ -414,7 +414,20 @@ export const MDXComponents = {
|
|
|
414
414
|
// Check for mermaid diagrams - render with Mermaid component
|
|
415
415
|
if (language === 'mermaid') {
|
|
416
416
|
const diagramCode = extractTextFromChildren(children);
|
|
417
|
-
return
|
|
417
|
+
return (
|
|
418
|
+
<>
|
|
419
|
+
<Mermaid>{diagramCode}</Mermaid>
|
|
420
|
+
{/* Crawler / AI fallback: the client-only SVG leaves the server HTML empty.
|
|
421
|
+
Use <noscript>, NOT sr-only: a JS-disabled crawler reads it, while users
|
|
422
|
+
with JS (and screen readers) get the real rendered diagram and never see
|
|
423
|
+
duplicate text. */}
|
|
424
|
+
{diagramCode.trim() && (
|
|
425
|
+
<noscript>
|
|
426
|
+
<pre>{diagramCode}</pre>
|
|
427
|
+
</noscript>
|
|
428
|
+
)}
|
|
429
|
+
</>
|
|
430
|
+
);
|
|
418
431
|
}
|
|
419
432
|
|
|
420
433
|
// Check for HTTP code blocks - render with special styling
|