@dogsbay/format-docusaurus 0.2.0-beta.78
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/adapter.d.ts +70 -0
- package/dist/adapter.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +246 -0
- package/dist/code-meta.d.ts +20 -0
- package/dist/code-meta.js +54 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +183 -0
- package/dist/frontmatter.d.ts +35 -0
- package/dist/frontmatter.js +45 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/load-cjs.d.ts +5 -0
- package/dist/load-cjs.js +45 -0
- package/dist/nav.d.ts +19 -0
- package/dist/nav.js +142 -0
- package/dist/parse-mdx.d.ts +54 -0
- package/dist/parse-mdx.js +765 -0
- package/dist/sidebars.d.ts +67 -0
- package/dist/sidebars.js +65 -0
- package/package.json +36 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Docusaurus MDX parser.
|
|
3
|
+
*
|
|
4
|
+
* Reads a standard Docusaurus `.mdx`/`.md` body and produces TreeNode[].
|
|
5
|
+
* Handles only built-in Docusaurus syntax:
|
|
6
|
+
* - :::admonitions (note/tip/info/warning/danger/caution) with optional title
|
|
7
|
+
* - <Tabs> / <TabItem value label>
|
|
8
|
+
* - <details> / <summary>
|
|
9
|
+
* - <CodeBlock> and fenced code with metastrings (title/{ranges}/showLineNumbers)
|
|
10
|
+
* - <DocCardList> / <DocCard> (theme built-ins → placeholder + warning)
|
|
11
|
+
* - import/export MDX statements (stripped)
|
|
12
|
+
*
|
|
13
|
+
* Site-specific components, variable substitution, and partial includes are
|
|
14
|
+
* the job of an injected `DocusaurusAdapter` — never this file.
|
|
15
|
+
*/
|
|
16
|
+
import { posix } from "node:path";
|
|
17
|
+
import MarkdownIt from "markdown-it";
|
|
18
|
+
import mdxJsx, { normalizeJsxLines } from "@dogsbay/markdown-it-mdx-jsx";
|
|
19
|
+
import { extractFrontmatter } from "./frontmatter.js";
|
|
20
|
+
import { parseCodeMeta } from "./code-meta.js";
|
|
21
|
+
// ── Standard Docusaurus components ──────────────────────
|
|
22
|
+
const DOCUSAURUS_COMPONENTS = {
|
|
23
|
+
Tabs: { type: "tabs", container: true },
|
|
24
|
+
TabItem: {
|
|
25
|
+
type: "tab",
|
|
26
|
+
container: true,
|
|
27
|
+
propsFromAttrs: (attrs) => ({ title: attrs.label || attrs.value || "" }),
|
|
28
|
+
},
|
|
29
|
+
// Docusaurus admonitions are converted to <Admonition> before parsing.
|
|
30
|
+
Admonition: {
|
|
31
|
+
type: "callout",
|
|
32
|
+
container: true,
|
|
33
|
+
propsFromAttrs: (attrs) => ({
|
|
34
|
+
variant: mapAdmonitionType(attrs.type || "note"),
|
|
35
|
+
title: attrs.title || undefined,
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
// Native collapsible. <details>/<summary> are lowercase HTML tags, which the
|
|
39
|
+
// mdx-jsx plugin (uppercase-only) ignores, so they are rewritten to these
|
|
40
|
+
// capitalized component names before parsing (see rewriteNativeHtml).
|
|
41
|
+
Details: { type: "details", container: true },
|
|
42
|
+
Summary: { type: "summary", container: true },
|
|
43
|
+
// Docusaurus theme code component.
|
|
44
|
+
CodeBlock: {
|
|
45
|
+
type: "code",
|
|
46
|
+
container: true,
|
|
47
|
+
propsFromAttrs: (attrs) => ({
|
|
48
|
+
lang: attrs.language || "plaintext",
|
|
49
|
+
title: attrs.title,
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
// Theme card components — auto-generated from the sidebar at runtime, so we
|
|
53
|
+
// can't reproduce them statically. Emit a placeholder and warn (see D2).
|
|
54
|
+
DocCardList: { type: "__doc-card-list", container: true },
|
|
55
|
+
DocCard: { type: "__doc-card-list", container: true },
|
|
56
|
+
};
|
|
57
|
+
const CORE_SELF_CLOSING = ["DocCardList", "DocCard"];
|
|
58
|
+
/** Map a Docusaurus admonition type to a Dogsbay callout variant. */
|
|
59
|
+
function mapAdmonitionType(type) {
|
|
60
|
+
const map = {
|
|
61
|
+
note: "note",
|
|
62
|
+
tip: "tip",
|
|
63
|
+
info: "info",
|
|
64
|
+
warning: "warning",
|
|
65
|
+
danger: "danger",
|
|
66
|
+
caution: "warning",
|
|
67
|
+
important: "warning",
|
|
68
|
+
};
|
|
69
|
+
return map[type.toLowerCase()] ?? "note";
|
|
70
|
+
}
|
|
71
|
+
function buildComponentMap(adapter) {
|
|
72
|
+
const map = { ...DOCUSAURUS_COMPONENTS };
|
|
73
|
+
const selfClosing = [...CORE_SELF_CLOSING];
|
|
74
|
+
if (adapter?.components) {
|
|
75
|
+
Object.assign(map, adapter.components);
|
|
76
|
+
}
|
|
77
|
+
for (const tag of adapter?.selfClosingTags ?? []) {
|
|
78
|
+
selfClosing.push(tag);
|
|
79
|
+
}
|
|
80
|
+
return { components: Object.keys(map), selfClosing, map };
|
|
81
|
+
}
|
|
82
|
+
// ── Pre-processing ──────────────────────────────────────
|
|
83
|
+
function applyInlineTransforms(source, adapter) {
|
|
84
|
+
if (!adapter?.inlineTransforms)
|
|
85
|
+
return source;
|
|
86
|
+
let result = source;
|
|
87
|
+
for (const [regex, replacement] of adapter.inlineTransforms) {
|
|
88
|
+
result = result.replace(regex, replacement);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Strip top-level `export ...` MDX statements (the mdx-jsx plugin already
|
|
94
|
+
* strips `import`). Fence-aware so `export` lines inside code blocks survive.
|
|
95
|
+
*/
|
|
96
|
+
function stripExports(source) {
|
|
97
|
+
const lines = source.split("\n");
|
|
98
|
+
const out = [];
|
|
99
|
+
let inFence = false;
|
|
100
|
+
let fenceMarker = "";
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
const fence = line.match(/^(\s*)(```+|~~~+)/);
|
|
103
|
+
if (fence) {
|
|
104
|
+
const marker = fence[2];
|
|
105
|
+
if (!inFence) {
|
|
106
|
+
inFence = true;
|
|
107
|
+
fenceMarker = marker[0];
|
|
108
|
+
}
|
|
109
|
+
else if (marker[0] === fenceMarker) {
|
|
110
|
+
inFence = false;
|
|
111
|
+
}
|
|
112
|
+
out.push(line);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!inFence && /^export\s+(const|let|var|default|function|async|\{)/.test(line)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
out.push(line);
|
|
119
|
+
}
|
|
120
|
+
return out.join("\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Rewrite native lowercase `<details>`/`<summary>` HTML tags to capitalized
|
|
124
|
+
* component tags so the uppercase-only mdx-jsx plugin tokenizes them. Fence-aware
|
|
125
|
+
* so literal `<details>` inside a code block is left intact.
|
|
126
|
+
*/
|
|
127
|
+
function rewriteNativeHtml(source) {
|
|
128
|
+
const lines = source.split("\n");
|
|
129
|
+
const out = [];
|
|
130
|
+
let inFence = false;
|
|
131
|
+
let fenceMarker = "";
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
const fence = line.match(/^(\s*)(```+|~~~+)/);
|
|
134
|
+
if (fence) {
|
|
135
|
+
const marker = fence[2][0];
|
|
136
|
+
if (!inFence) {
|
|
137
|
+
inFence = true;
|
|
138
|
+
fenceMarker = marker;
|
|
139
|
+
}
|
|
140
|
+
else if (marker === fenceMarker) {
|
|
141
|
+
inFence = false;
|
|
142
|
+
}
|
|
143
|
+
out.push(line);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (inFence) {
|
|
147
|
+
out.push(line);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
out.push(line
|
|
151
|
+
.replace(/<details(\s[^>]*)?>/g, "<Details>")
|
|
152
|
+
.replace(/<\/details>/g, "</Details>")
|
|
153
|
+
.replace(/<summary(\s[^>]*)?>/g, "<Summary>")
|
|
154
|
+
.replace(/<\/summary>/g, "</Summary>"));
|
|
155
|
+
}
|
|
156
|
+
return out.join("\n");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Convert Docusaurus `:::type[Title]` / `:::type Title` admonition directives
|
|
160
|
+
* to `<Admonition type="..." title="...">` so the JSX pipeline handles them.
|
|
161
|
+
* Supports 3+ colons (nested admonitions use more colons).
|
|
162
|
+
*/
|
|
163
|
+
function convertAdmonitions(source) {
|
|
164
|
+
const lines = source.split("\n");
|
|
165
|
+
const result = [];
|
|
166
|
+
const open = [];
|
|
167
|
+
for (const line of lines) {
|
|
168
|
+
const openMatch = line.match(/^(\s*)(:{3,})(note|tip|info|warning|danger|caution|important)(?:\[([^\]]*)\]|\s+(.+?))?\s*$/);
|
|
169
|
+
const closeMatch = line.match(/^(\s*)(:{3,})\s*$/);
|
|
170
|
+
if (openMatch) {
|
|
171
|
+
const indent = openMatch[1] || "";
|
|
172
|
+
const colons = openMatch[2];
|
|
173
|
+
const type = openMatch[3];
|
|
174
|
+
const title = (openMatch[4] ?? openMatch[5] ?? "").trim();
|
|
175
|
+
const titleAttr = title ? ` title="${title.replace(/"/g, """)}"` : "";
|
|
176
|
+
result.push(`${indent}<Admonition type="${type}"${titleAttr}>`);
|
|
177
|
+
open.push({ indent, colons });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (closeMatch && open.length > 0 && closeMatch[2] === open[open.length - 1].colons) {
|
|
181
|
+
const { indent } = open.pop();
|
|
182
|
+
result.push(`${indent}</Admonition>`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
result.push(line);
|
|
186
|
+
}
|
|
187
|
+
return result.join("\n");
|
|
188
|
+
}
|
|
189
|
+
// ── Token → TreeNode conversion ─────────────────────────
|
|
190
|
+
function tokensToTree(tokens, ctx) {
|
|
191
|
+
const { componentMap } = ctx;
|
|
192
|
+
const tree = [];
|
|
193
|
+
let i = 0;
|
|
194
|
+
while (i < tokens.length) {
|
|
195
|
+
const token = tokens[i];
|
|
196
|
+
// ── JSX self-closing ──
|
|
197
|
+
if (token.type === "jsx_self_closing") {
|
|
198
|
+
const mapping = componentMap[token.tag];
|
|
199
|
+
if (mapping && !mapping.strip) {
|
|
200
|
+
if (mapping.type === "__doc-card-list") {
|
|
201
|
+
tree.push(ctx.docCardListCards ?? docCardListPlaceholder(ctx));
|
|
202
|
+
}
|
|
203
|
+
else if (mapping.type === "__placeholder") {
|
|
204
|
+
tree.push(componentPlaceholder(ctx, token.tag));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const attrs = token.meta?.attrs || {};
|
|
208
|
+
const props = mapping.propsFromAttrs?.(attrs) || {};
|
|
209
|
+
tree.push({ type: mapping.type, props, children: [] });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
i++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// ── JSX open ──
|
|
216
|
+
if (token.type === "jsx_open") {
|
|
217
|
+
const tag = token.tag;
|
|
218
|
+
const mapping = componentMap[tag];
|
|
219
|
+
const children = [];
|
|
220
|
+
let depth = 1;
|
|
221
|
+
i++;
|
|
222
|
+
while (i < tokens.length && depth > 0) {
|
|
223
|
+
if (tokens[i].type === "jsx_open" && tokens[i].tag === tag)
|
|
224
|
+
depth++;
|
|
225
|
+
else if (tokens[i].type === "jsx_close" && tokens[i].tag === tag) {
|
|
226
|
+
depth--;
|
|
227
|
+
if (depth === 0) {
|
|
228
|
+
i++;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
children.push(tokens[i]);
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
if (mapping) {
|
|
236
|
+
if (mapping.type === "__doc-card-list") {
|
|
237
|
+
tree.push(ctx.docCardListCards ?? docCardListPlaceholder(ctx));
|
|
238
|
+
}
|
|
239
|
+
else if (mapping.type === "__placeholder") {
|
|
240
|
+
tree.push(componentPlaceholder(ctx, tag));
|
|
241
|
+
}
|
|
242
|
+
else if (mapping.strip) {
|
|
243
|
+
if (mapping.container && children.length > 0) {
|
|
244
|
+
tree.push(...tokensToTree(children, ctx));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (mapping.type === "code") {
|
|
248
|
+
// <CodeBlock>...</CodeBlock>: inner content is the code text.
|
|
249
|
+
const attrs = token.meta?.attrs || {};
|
|
250
|
+
const props = mapping.propsFromAttrs?.(attrs) || {};
|
|
251
|
+
const code = children
|
|
252
|
+
.filter((t) => t.type === "inline" || t.type === "fence" || t.type === "code_block")
|
|
253
|
+
.map((t) => t.content)
|
|
254
|
+
.join("");
|
|
255
|
+
tree.push({ type: "code", props: { ...props, code: code.replace(/\n$/, "") } });
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const attrs = token.meta?.attrs || {};
|
|
259
|
+
const props = mapping.propsFromAttrs?.(attrs) || {};
|
|
260
|
+
tree.push({ type: mapping.type, props, children: tokensToTree(children, ctx) });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Unknown component — keep inner content, drop the wrapper.
|
|
265
|
+
tree.push(...tokensToTree(children, ctx));
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (token.type === "jsx_close") {
|
|
270
|
+
i++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
// ── Standard markdown ──
|
|
274
|
+
if (token.type === "heading_open") {
|
|
275
|
+
const level = parseInt(token.tag.slice(1), 10);
|
|
276
|
+
const inline = tokens[i + 1];
|
|
277
|
+
const text = inline?.content || "";
|
|
278
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
279
|
+
tree.push({
|
|
280
|
+
type: "heading",
|
|
281
|
+
props: { level, text, slug },
|
|
282
|
+
inline: inline ? inlineTokensToInline(inline.children || []) : [],
|
|
283
|
+
});
|
|
284
|
+
i += 3;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (token.type === "paragraph_open") {
|
|
288
|
+
const inline = tokens[i + 1];
|
|
289
|
+
if (inline?.children?.length) {
|
|
290
|
+
tree.push({
|
|
291
|
+
type: "paragraph",
|
|
292
|
+
children: [{ type: "prose", inline: inlineTokensToInline(inline.children) }],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
i += 3;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (token.type === "fence") {
|
|
299
|
+
const meta = parseCodeMeta(token.info || "");
|
|
300
|
+
const props = {
|
|
301
|
+
code: token.content.replace(/\n$/, ""),
|
|
302
|
+
lang: meta.lang,
|
|
303
|
+
};
|
|
304
|
+
if (meta.title)
|
|
305
|
+
props.title = meta.title;
|
|
306
|
+
if (meta.highlights)
|
|
307
|
+
props.highlights = meta.highlights;
|
|
308
|
+
if (meta.showLineNumbers)
|
|
309
|
+
props.showLineNumbers = true;
|
|
310
|
+
tree.push({ type: "code", props });
|
|
311
|
+
i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (token.type === "code_block") {
|
|
315
|
+
tree.push({
|
|
316
|
+
type: "code",
|
|
317
|
+
props: { code: token.content.replace(/\n$/, ""), lang: "plaintext" },
|
|
318
|
+
});
|
|
319
|
+
i++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (token.type === "bullet_list_open" || token.type === "ordered_list_open") {
|
|
323
|
+
const listType = token.type === "ordered_list_open" ? "ordered-list" : "unordered-list";
|
|
324
|
+
const start = token.type === "ordered_list_open"
|
|
325
|
+
? parseInt(token.attrGet("start") || "1", 10)
|
|
326
|
+
: undefined;
|
|
327
|
+
const items = [];
|
|
328
|
+
i++;
|
|
329
|
+
while (i < tokens.length &&
|
|
330
|
+
tokens[i].type !== "bullet_list_close" &&
|
|
331
|
+
tokens[i].type !== "ordered_list_close") {
|
|
332
|
+
if (tokens[i].type === "list_item_open") {
|
|
333
|
+
i++;
|
|
334
|
+
const itemTokens = [];
|
|
335
|
+
let liDepth = 1;
|
|
336
|
+
while (i < tokens.length && liDepth > 0) {
|
|
337
|
+
if (tokens[i].type === "list_item_open")
|
|
338
|
+
liDepth++;
|
|
339
|
+
if (tokens[i].type === "list_item_close") {
|
|
340
|
+
liDepth--;
|
|
341
|
+
if (liDepth === 0)
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
itemTokens.push(tokens[i]);
|
|
345
|
+
i++;
|
|
346
|
+
}
|
|
347
|
+
items.push({ type: "list-item", children: tokensToTree(itemTokens, ctx) });
|
|
348
|
+
i++;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
i++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
i++;
|
|
355
|
+
const listProps = start && start !== 1 ? { start } : undefined;
|
|
356
|
+
tree.push({ type: listType, props: listProps, children: items });
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (token.type === "table_open") {
|
|
360
|
+
const { node, endIndex } = parseTable(tokens, i);
|
|
361
|
+
tree.push(node);
|
|
362
|
+
i = endIndex;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (token.type === "hr") {
|
|
366
|
+
tree.push({ type: "thematic-break" });
|
|
367
|
+
i++;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (token.type === "blockquote_open") {
|
|
371
|
+
const children = [];
|
|
372
|
+
let depth = 1;
|
|
373
|
+
i++;
|
|
374
|
+
const inner = [];
|
|
375
|
+
while (i < tokens.length && depth > 0) {
|
|
376
|
+
if (tokens[i].type === "blockquote_open")
|
|
377
|
+
depth++;
|
|
378
|
+
else if (tokens[i].type === "blockquote_close") {
|
|
379
|
+
depth--;
|
|
380
|
+
if (depth === 0)
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
inner.push(tokens[i]);
|
|
384
|
+
i++;
|
|
385
|
+
}
|
|
386
|
+
i++;
|
|
387
|
+
children.push(...tokensToTree(inner, ctx));
|
|
388
|
+
tree.push({ type: "blockquote", children });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (token.type === "html_block") {
|
|
392
|
+
const trimmed = token.content.trim();
|
|
393
|
+
if (trimmed.match(/^<[A-Z]/)) {
|
|
394
|
+
i++;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
tree.push({ type: "html", html: token.content });
|
|
398
|
+
i++;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
i++;
|
|
402
|
+
}
|
|
403
|
+
return tree;
|
|
404
|
+
}
|
|
405
|
+
function docCardListPlaceholder(ctx) {
|
|
406
|
+
ctx.onWarn("<DocCardList>/<DocCard> is auto-generated from the sidebar and cannot be reproduced statically — emitted a placeholder.");
|
|
407
|
+
return {
|
|
408
|
+
type: "callout",
|
|
409
|
+
props: { variant: "info" },
|
|
410
|
+
children: [
|
|
411
|
+
{
|
|
412
|
+
type: "paragraph",
|
|
413
|
+
children: [
|
|
414
|
+
{
|
|
415
|
+
type: "prose",
|
|
416
|
+
inline: [
|
|
417
|
+
{
|
|
418
|
+
type: "text",
|
|
419
|
+
text: "This section originally rendered an auto-generated card list of its sub-pages. Use the sidebar to navigate.",
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
meta: { docusaurus: "DocCardList" },
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Placeholder for a data-driven component an adapter can't reproduce statically
|
|
431
|
+
* (e.g. a React component that builds tables/steps from JSON). Emitting a note
|
|
432
|
+
* instead of stripping keeps containers (tabs, list items) non-empty — an empty
|
|
433
|
+
* tab otherwise loses its label — and flags the omission to the reader.
|
|
434
|
+
*/
|
|
435
|
+
function componentPlaceholder(ctx, name) {
|
|
436
|
+
ctx.onWarn(`<${name}> is a data-driven component and couldn't be reproduced from source — emitted a placeholder.`);
|
|
437
|
+
return {
|
|
438
|
+
type: "callout",
|
|
439
|
+
props: { variant: "info" },
|
|
440
|
+
children: [
|
|
441
|
+
{
|
|
442
|
+
type: "paragraph",
|
|
443
|
+
children: [
|
|
444
|
+
{
|
|
445
|
+
type: "prose",
|
|
446
|
+
inline: [
|
|
447
|
+
{ type: "text", text: "Content generated by the " },
|
|
448
|
+
{ type: "code", text: name },
|
|
449
|
+
{ type: "text", text: " component was not reproduced in this import." },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
meta: { docusaurus: name },
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function parseTable(tokens, start) {
|
|
459
|
+
const rows = [];
|
|
460
|
+
let i = start + 1;
|
|
461
|
+
let inHead = false;
|
|
462
|
+
while (i < tokens.length && tokens[i].type !== "table_close") {
|
|
463
|
+
if (tokens[i].type === "thead_open") {
|
|
464
|
+
inHead = true;
|
|
465
|
+
i++;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (tokens[i].type === "thead_close") {
|
|
469
|
+
inHead = false;
|
|
470
|
+
i++;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (tokens[i].type === "tbody_open" || tokens[i].type === "tbody_close") {
|
|
474
|
+
i++;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (tokens[i].type === "tr_open") {
|
|
478
|
+
const cells = [];
|
|
479
|
+
i++;
|
|
480
|
+
while (i < tokens.length && tokens[i].type !== "tr_close") {
|
|
481
|
+
if (tokens[i].type === "th_open" || tokens[i].type === "td_open") {
|
|
482
|
+
const cellType = inHead ? "th" : "td";
|
|
483
|
+
const inline = tokens[i + 1];
|
|
484
|
+
const inlineNodes = inline?.type === "inline" ? inlineTokensToInline(inline.children || []) : [];
|
|
485
|
+
cells.push({ type: cellType, children: [{ type: "prose", inline: inlineNodes }] });
|
|
486
|
+
i += 3;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
i++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
rows.push({ type: "tr", children: cells });
|
|
493
|
+
i++;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
i++;
|
|
497
|
+
}
|
|
498
|
+
return { node: { type: "table", children: rows }, endIndex: i + 1 };
|
|
499
|
+
}
|
|
500
|
+
// ── Inline conversion ───────────────────────────────────
|
|
501
|
+
function inlineTokensToInline(tokens) {
|
|
502
|
+
const result = [];
|
|
503
|
+
let bold = false;
|
|
504
|
+
let italic = false;
|
|
505
|
+
let inLink = false;
|
|
506
|
+
let linkHref = "";
|
|
507
|
+
let linkChildren = [];
|
|
508
|
+
const push = (node) => {
|
|
509
|
+
if (inLink)
|
|
510
|
+
linkChildren.push(node);
|
|
511
|
+
else
|
|
512
|
+
result.push(node);
|
|
513
|
+
};
|
|
514
|
+
for (const token of tokens) {
|
|
515
|
+
if (token.type === "text") {
|
|
516
|
+
push({ type: "text", text: token.content, bold, italic });
|
|
517
|
+
}
|
|
518
|
+
else if (token.type === "code_inline") {
|
|
519
|
+
push({ type: "code", text: token.content });
|
|
520
|
+
}
|
|
521
|
+
else if (token.type === "softbreak" || token.type === "hardbreak") {
|
|
522
|
+
push({ type: "text", text: "\n" });
|
|
523
|
+
}
|
|
524
|
+
else if (token.type === "strong_open") {
|
|
525
|
+
bold = true;
|
|
526
|
+
}
|
|
527
|
+
else if (token.type === "strong_close") {
|
|
528
|
+
bold = false;
|
|
529
|
+
}
|
|
530
|
+
else if (token.type === "em_open") {
|
|
531
|
+
italic = true;
|
|
532
|
+
}
|
|
533
|
+
else if (token.type === "em_close") {
|
|
534
|
+
italic = false;
|
|
535
|
+
}
|
|
536
|
+
else if (token.type === "link_open") {
|
|
537
|
+
linkHref = token.attrGet("href") || "";
|
|
538
|
+
linkChildren = [];
|
|
539
|
+
inLink = true;
|
|
540
|
+
}
|
|
541
|
+
else if (token.type === "link_close") {
|
|
542
|
+
if (inLink) {
|
|
543
|
+
result.push({ type: "link", href: linkHref, children: linkChildren });
|
|
544
|
+
inLink = false;
|
|
545
|
+
linkHref = "";
|
|
546
|
+
linkChildren = [];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else if (token.type === "image") {
|
|
550
|
+
push({
|
|
551
|
+
type: "image",
|
|
552
|
+
src: token.attrGet("src") || "",
|
|
553
|
+
alt: token.content || token.attrGet("alt") || "",
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
else if (token.type === "html_inline") {
|
|
557
|
+
push({ type: "text", text: token.content });
|
|
558
|
+
}
|
|
559
|
+
// Inline JSX tokens (jsx_inline_*) are intentionally skipped.
|
|
560
|
+
}
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
// ── Post-processing ─────────────────────────────────────
|
|
564
|
+
/** Hoist a <details>'s <summary> child into props.title. */
|
|
565
|
+
function hoistDetailsSummary(nodes) {
|
|
566
|
+
for (const node of nodes) {
|
|
567
|
+
if (node.children)
|
|
568
|
+
node.children = hoistDetailsSummary(node.children);
|
|
569
|
+
if (node.type === "details" && node.children) {
|
|
570
|
+
const idx = node.children.findIndex((c) => c.type === "summary");
|
|
571
|
+
if (idx !== -1) {
|
|
572
|
+
const summary = node.children[idx];
|
|
573
|
+
node.props = { ...node.props, title: summaryText(summary) };
|
|
574
|
+
node.children.splice(idx, 1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return nodes;
|
|
579
|
+
}
|
|
580
|
+
function summaryText(node) {
|
|
581
|
+
const parts = [];
|
|
582
|
+
const walk = (n) => {
|
|
583
|
+
if (n.inline) {
|
|
584
|
+
for (const inl of n.inline)
|
|
585
|
+
if (inl.type === "text" || inl.type === "code")
|
|
586
|
+
parts.push(inl.text);
|
|
587
|
+
}
|
|
588
|
+
if (n.children)
|
|
589
|
+
n.children.forEach(walk);
|
|
590
|
+
};
|
|
591
|
+
walk(node);
|
|
592
|
+
return parts.join("").trim();
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Rewrite internal doc links to served URLs.
|
|
596
|
+
*
|
|
597
|
+
* Docusaurus authors cross-references as links to source files — relative
|
|
598
|
+
* (`../foo/bar.mdx`) or root-absolute (`/calico/foo`). The browser can't follow
|
|
599
|
+
* a `.mdx` path, and relative depths don't match the served (trailing-slash) URL
|
|
600
|
+
* structure, so Docusaurus resolves these to absolute URLs at build time. We do
|
|
601
|
+
* the same: strip `.mdx`/`.md` (+ `/index`), resolve relatives against the
|
|
602
|
+
* current page's slug, and emit a root-absolute `<hrefPrefix>/<slug>` href.
|
|
603
|
+
*
|
|
604
|
+
* `currentSlug` is the importing file's slug (e.g.
|
|
605
|
+
* "getting-started/kubernetes/managed-public-cloud/eks") — relatives resolve
|
|
606
|
+
* against its directory.
|
|
607
|
+
*/
|
|
608
|
+
function rewriteLinks(nodes, routeBasePath, hrefPrefix, currentSlug) {
|
|
609
|
+
const base = "/" + routeBasePath.replace(/^\/|\/$/g, "");
|
|
610
|
+
const prefix = hrefPrefix.replace(/\/$/, "");
|
|
611
|
+
const currentDir = posix.dirname("/" + currentSlug).replace(/^\//, "");
|
|
612
|
+
const cleanSlug = (p) => p.replace(/\.mdx?$/, "").replace(/\/index$/, "");
|
|
613
|
+
const fix = (href) => {
|
|
614
|
+
if (/^[a-z]+:\/\//i.test(href) || href.startsWith("#") || href.startsWith("mailto:")) {
|
|
615
|
+
return href;
|
|
616
|
+
}
|
|
617
|
+
// Preserve a trailing #anchor / ?query.
|
|
618
|
+
const split = href.match(/^([^#?]*)([#?].*)?$/);
|
|
619
|
+
const path = split?.[1] ?? href;
|
|
620
|
+
const suffix = split?.[2] ?? "";
|
|
621
|
+
if (!path)
|
|
622
|
+
return href; // bare "#anchor" already handled above
|
|
623
|
+
const isDoc = /\.mdx?$/.test(path);
|
|
624
|
+
if (path.startsWith("/")) {
|
|
625
|
+
// Root-absolute. Map the instance's routeBasePath onto hrefPrefix; strip
|
|
626
|
+
// doc extensions either way (non-routeBasePath links are cross-product —
|
|
627
|
+
// left as-is beyond ext stripping, like Starlight).
|
|
628
|
+
let p = path;
|
|
629
|
+
if (base !== "/" && (p === base || p.startsWith(base + "/"))) {
|
|
630
|
+
p = prefix + cleanSlug(p.slice(base.length));
|
|
631
|
+
}
|
|
632
|
+
else if (isDoc) {
|
|
633
|
+
p = cleanSlug(p);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
return href;
|
|
637
|
+
}
|
|
638
|
+
return p + suffix;
|
|
639
|
+
}
|
|
640
|
+
if (isDoc) {
|
|
641
|
+
// Relative doc link → resolve against the current page's dir → absolute.
|
|
642
|
+
const resolved = cleanSlug(posix.normalize(posix.join(currentDir, path)));
|
|
643
|
+
return `${prefix}/${resolved}${suffix}`;
|
|
644
|
+
}
|
|
645
|
+
return href; // relative non-doc (rare) — leave untouched
|
|
646
|
+
};
|
|
647
|
+
const walkInline = (inl) => {
|
|
648
|
+
for (const node of inl) {
|
|
649
|
+
if (node.type === "link") {
|
|
650
|
+
node.href = fix(node.href);
|
|
651
|
+
walkInline(node.children);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const walk = (n) => {
|
|
656
|
+
if (n.inline)
|
|
657
|
+
walkInline(n.inline);
|
|
658
|
+
if (n.children)
|
|
659
|
+
n.children.forEach(walk);
|
|
660
|
+
};
|
|
661
|
+
nodes.forEach(walk);
|
|
662
|
+
return nodes;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Rewrite root-absolute image srcs onto the Dogsbay `_assets` convention.
|
|
666
|
+
* Docusaurus serves `static/` at the site root, so `static/img/x` is referenced
|
|
667
|
+
* as `/img/x`; mapped to `/_assets/img/x` (docs/images.md) so it gets correct
|
|
668
|
+
* basePath prefixing and the copied `static/` tree under `_assets/` resolves.
|
|
669
|
+
*/
|
|
670
|
+
function rewriteImageSrcs(nodes, assetPrefix) {
|
|
671
|
+
const prefix = assetPrefix.replace(/\/$/, "");
|
|
672
|
+
const fix = (src) => {
|
|
673
|
+
if (!src.startsWith("/") || src.startsWith("//"))
|
|
674
|
+
return src; // relative/protocol-relative/external
|
|
675
|
+
if (src.startsWith(prefix + "/"))
|
|
676
|
+
return src; // already mounted
|
|
677
|
+
return prefix + src;
|
|
678
|
+
};
|
|
679
|
+
const walkInline = (inl) => {
|
|
680
|
+
for (const node of inl) {
|
|
681
|
+
if (node.type === "image")
|
|
682
|
+
node.src = fix(node.src);
|
|
683
|
+
else if (node.type === "link")
|
|
684
|
+
walkInline(node.children);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
const walk = (n) => {
|
|
688
|
+
if (n.inline)
|
|
689
|
+
walkInline(n.inline);
|
|
690
|
+
if (n.children)
|
|
691
|
+
n.children.forEach(walk);
|
|
692
|
+
};
|
|
693
|
+
nodes.forEach(walk);
|
|
694
|
+
return nodes;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Resolve `card` nodes that carry a `docId` (e.g. from `<DocCardLink docId=…>`):
|
|
698
|
+
* fill in `href` and `title` from the target doc via the injected resolver, so
|
|
699
|
+
* the card isn't emitted empty. An explicit title/href already on the node wins.
|
|
700
|
+
*/
|
|
701
|
+
function resolveCardDocs(nodes, resolveDoc) {
|
|
702
|
+
const walk = (n) => {
|
|
703
|
+
if (n.type === "card" && typeof n.props?.docId === "string") {
|
|
704
|
+
const r = resolveDoc(n.props.docId);
|
|
705
|
+
if (r) {
|
|
706
|
+
if (!n.props.href)
|
|
707
|
+
n.props.href = r.href;
|
|
708
|
+
if (!n.props.title)
|
|
709
|
+
n.props.title = r.title;
|
|
710
|
+
}
|
|
711
|
+
delete n.props.docId;
|
|
712
|
+
}
|
|
713
|
+
n.children?.forEach(walk);
|
|
714
|
+
};
|
|
715
|
+
nodes.forEach(walk);
|
|
716
|
+
return nodes;
|
|
717
|
+
}
|
|
718
|
+
/** Build a `cards` grid TreeNode from a sidebar category's child nav items. */
|
|
719
|
+
function navItemsToCards(items) {
|
|
720
|
+
const cards = items
|
|
721
|
+
.filter((it) => it.href || (it.children && it.children.length))
|
|
722
|
+
.map((it) => ({
|
|
723
|
+
type: "card",
|
|
724
|
+
props: { title: it.label, href: it.href ?? it.children?.[0]?.href },
|
|
725
|
+
}));
|
|
726
|
+
return { type: "cards", children: cards };
|
|
727
|
+
}
|
|
728
|
+
export function docusaurusToTree(source, options = {}) {
|
|
729
|
+
const adapter = options.adapter;
|
|
730
|
+
const onWarn = options.onWarn ?? (() => { });
|
|
731
|
+
const { components, selfClosing, map: componentMap } = buildComponentMap(adapter);
|
|
732
|
+
const { body } = extractFrontmatter(source);
|
|
733
|
+
// Adapter preprocessing runs first: $[var] substitution, partial inlining, etc.
|
|
734
|
+
let cleaned = adapter?.preprocess
|
|
735
|
+
? adapter.preprocess(body, options.preprocessContext ?? {})
|
|
736
|
+
: body;
|
|
737
|
+
cleaned = cleaned.replace(/\t/g, " ");
|
|
738
|
+
cleaned = applyInlineTransforms(cleaned, adapter);
|
|
739
|
+
cleaned = stripExports(cleaned);
|
|
740
|
+
cleaned = convertAdmonitions(cleaned);
|
|
741
|
+
cleaned = rewriteNativeHtml(cleaned);
|
|
742
|
+
cleaned = normalizeJsxLines(cleaned);
|
|
743
|
+
const md = new MarkdownIt({ html: true, linkify: true }).use(mdxJsx, {
|
|
744
|
+
components,
|
|
745
|
+
selfClosing,
|
|
746
|
+
});
|
|
747
|
+
const tokens = md.parse(cleaned, {});
|
|
748
|
+
// If this page is a category index, build the DocCardList grid from its
|
|
749
|
+
// sidebar children up front so tokensToTree can emit it in place.
|
|
750
|
+
const cardListItems = options.resolveDocCardList?.(options.currentSlug ?? "");
|
|
751
|
+
const docCardListCards = cardListItems && cardListItems.length > 0 ? navItemsToCards(cardListItems) : undefined;
|
|
752
|
+
const ctx = { componentMap, onWarn, docCardListCards };
|
|
753
|
+
let tree = tokensToTree(tokens, ctx);
|
|
754
|
+
tree = hoistDetailsSummary(tree);
|
|
755
|
+
if (options.routeBasePath) {
|
|
756
|
+
tree = rewriteLinks(tree, options.routeBasePath, options.hrefPrefix ?? "/docs", options.currentSlug ?? "");
|
|
757
|
+
}
|
|
758
|
+
if (options.assetPrefix) {
|
|
759
|
+
tree = rewriteImageSrcs(tree, options.assetPrefix);
|
|
760
|
+
}
|
|
761
|
+
if (options.resolveDoc) {
|
|
762
|
+
tree = resolveCardDocs(tree, options.resolveDoc);
|
|
763
|
+
}
|
|
764
|
+
return tree;
|
|
765
|
+
}
|