@thallylabs/migrate 0.2.7 → 0.2.8
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 +37 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1020 -154
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
import { compileSync as compileSync2 } from "@mdx-js/mdx";
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import {
|
|
5
|
+
closeSync,
|
|
5
6
|
existsSync as existsSync5,
|
|
6
7
|
lstatSync as lstatSync5,
|
|
8
|
+
openSync,
|
|
7
9
|
readFileSync as readFileSync5,
|
|
8
|
-
|
|
10
|
+
readSync,
|
|
11
|
+
readdirSync as readdirSync2,
|
|
9
12
|
realpathSync as realpathSync2,
|
|
10
13
|
rmSync,
|
|
11
14
|
statSync
|
|
12
15
|
} from "fs";
|
|
13
16
|
import { createRequire } from "module";
|
|
14
|
-
import { basename as
|
|
17
|
+
import { basename as basename3, dirname as dirname4, extname as extname5, relative as relative5, resolve as resolvePath, sep as sep2 } from "path";
|
|
15
18
|
import { parse as parseYaml4 } from "yaml";
|
|
19
|
+
import * as ts2 from "typescript";
|
|
16
20
|
|
|
17
21
|
// src/components.ts
|
|
18
22
|
import { createHash } from "crypto";
|
|
@@ -159,19 +163,21 @@ function docusaurusAdmonitionTag(kind) {
|
|
|
159
163
|
function normalizeDocusaurusAdmonitions(body) {
|
|
160
164
|
const lines = body.split("\n");
|
|
161
165
|
const openAdmonitions = [];
|
|
166
|
+
const headingLines = [];
|
|
162
167
|
let codeFence = null;
|
|
163
|
-
|
|
164
|
-
const codeMatch = line.match(/^\s
|
|
168
|
+
const output = lines.map((line, index) => {
|
|
169
|
+
const codeMatch = line.match(/^\s{0,3}(`{3,}|~{3,})/);
|
|
165
170
|
if (codeMatch) {
|
|
166
|
-
if (!codeFence) codeFence = codeMatch[1]
|
|
167
|
-
else if (codeMatch[1][0] === codeFence) codeFence = null;
|
|
171
|
+
if (!codeFence) codeFence = codeMatch[1];
|
|
172
|
+
else if (codeMatch[1][0] === codeFence[0] && codeMatch[1].length >= codeFence.length && /^\s*$/.test(line.slice(codeMatch[0].length))) codeFence = null;
|
|
168
173
|
return line;
|
|
169
174
|
}
|
|
170
175
|
if (codeFence) return line;
|
|
176
|
+
if (/^#{1,6}\s/.test(line)) headingLines.push(index);
|
|
171
177
|
const opening = line.match(/^\s*(:{3,})(note|tip|info|warning|caution|danger)(?:\[([^\]]+)\]|\s+(.+))?\s*$/i);
|
|
172
178
|
if (opening) {
|
|
173
179
|
const tag = docusaurusAdmonitionTag(opening[2].toLowerCase());
|
|
174
|
-
openAdmonitions.push({ delimiter: opening[1], tag });
|
|
180
|
+
openAdmonitions.push({ delimiter: opening[1], tag, openedAt: index });
|
|
175
181
|
const title = (opening[3] ?? opening[4])?.trim();
|
|
176
182
|
return title ? `<${tag}>
|
|
177
183
|
**${title}**` : `<${tag}>`;
|
|
@@ -183,7 +189,74 @@ function normalizeDocusaurusAdmonitions(body) {
|
|
|
183
189
|
return `</${current.tag}>`;
|
|
184
190
|
}
|
|
185
191
|
return line;
|
|
186
|
-
})
|
|
192
|
+
});
|
|
193
|
+
const closingByLine = /* @__PURE__ */ new Map();
|
|
194
|
+
for (const open of openAdmonitions.reverse()) {
|
|
195
|
+
const at = headingLines.find((line) => line > open.openedAt) ?? output.length;
|
|
196
|
+
closingByLine.set(at, [...closingByLine.get(at) ?? [], `</${open.tag}>`]);
|
|
197
|
+
}
|
|
198
|
+
for (const [at, closing] of [...closingByLine].sort(([left], [right]) => right - left)) output.splice(at, 0, ...closing);
|
|
199
|
+
return output.join("\n");
|
|
200
|
+
}
|
|
201
|
+
function unwrapDocusaurusMdxCodeBlocks(body) {
|
|
202
|
+
const output = [];
|
|
203
|
+
let fence;
|
|
204
|
+
let isMdxCodeBlock = false;
|
|
205
|
+
for (const line of body.split("\n")) {
|
|
206
|
+
const opening = !fence && line.match(/^ {0,3}(`{3,}|~{3,})mdx-code-block\s*$/);
|
|
207
|
+
if (opening) {
|
|
208
|
+
fence = opening[1];
|
|
209
|
+
isMdxCodeBlock = true;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (fence) {
|
|
213
|
+
const closing = line.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
214
|
+
if (closing && closing[1][0] === fence[0] && closing[1].length >= fence.length) {
|
|
215
|
+
if (!isMdxCodeBlock) output.push(line);
|
|
216
|
+
fence = void 0;
|
|
217
|
+
isMdxCodeBlock = false;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (isMdxCodeBlock && /^\s*(?:import|export)\b/.test(line)) return body;
|
|
221
|
+
output.push(line);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const ordinaryFence = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
225
|
+
if (ordinaryFence) {
|
|
226
|
+
fence = ordinaryFence[1];
|
|
227
|
+
isMdxCodeBlock = false;
|
|
228
|
+
}
|
|
229
|
+
output.push(line);
|
|
230
|
+
}
|
|
231
|
+
return isMdxCodeBlock ? body : output.join("\n");
|
|
232
|
+
}
|
|
233
|
+
function normalizeExplicitHeadingIds(body) {
|
|
234
|
+
return replaceOutsideCode(body, (segment) => segment.split("\n").map((line) => {
|
|
235
|
+
if (!/^[ \t]{0,3}#{1,6}[ \t]+/.test(line)) return line;
|
|
236
|
+
const trimmed = line.trimEnd();
|
|
237
|
+
if (trimmed.endsWith("}")) {
|
|
238
|
+
const marker = trimmed.lastIndexOf(" {#");
|
|
239
|
+
if (marker >= 0) {
|
|
240
|
+
const id = trimmed.slice(marker + 3, -1);
|
|
241
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(id)) {
|
|
242
|
+
return `<a id="${id}"></a>
|
|
243
|
+
${trimmed.slice(0, marker).trimEnd()}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (trimmed.endsWith("*/}")) {
|
|
248
|
+
const marker = trimmed.lastIndexOf(" {/*");
|
|
249
|
+
if (marker >= 0) {
|
|
250
|
+
const comment = trimmed.slice(marker + 4, -3).trim();
|
|
251
|
+
const id = comment.startsWith("#") ? comment.slice(1) : "";
|
|
252
|
+
if (/^[A-Za-z][A-Za-z0-9_.:-]*$/.test(id)) {
|
|
253
|
+
return `<a id="${id}"></a>
|
|
254
|
+
${trimmed.slice(0, marker).trimEnd()}`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return line;
|
|
259
|
+
}).join("\n"));
|
|
187
260
|
}
|
|
188
261
|
var GLOBAL_DOCUSARUS_COMPONENTS = /* @__PURE__ */ new Set([
|
|
189
262
|
"Tabs",
|
|
@@ -192,19 +265,11 @@ var GLOBAL_DOCUSARUS_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
192
265
|
"DocCardList",
|
|
193
266
|
"TOCInline"
|
|
194
267
|
]);
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const component = trimmed.slice("import ".length, separator).trim();
|
|
201
|
-
if (!GLOBAL_DOCUSARUS_COMPONENTS.has(component)) return false;
|
|
202
|
-
const source = trimmed.slice(separator + " from ".length);
|
|
203
|
-
if (source.length < 3) return false;
|
|
204
|
-
const quote = source[0];
|
|
205
|
-
if (quote !== "'" && quote !== '"' || source.at(-1) !== quote) return false;
|
|
206
|
-
const moduleName = source.slice(1, -1);
|
|
207
|
-
return moduleName.startsWith("@theme/") || moduleName.startsWith("@docusaurus/");
|
|
268
|
+
function removeGlobalDocusaurusImports(body) {
|
|
269
|
+
return replaceOutsideCode(body, (segment) => segment.replace(
|
|
270
|
+
/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+(['"])(@(?:theme|docusaurus)\/[^'"]+)\2\s*;?/g,
|
|
271
|
+
(statement, component) => GLOBAL_DOCUSARUS_COMPONENTS.has(component) ? "" : statement
|
|
272
|
+
));
|
|
208
273
|
}
|
|
209
274
|
var JS_LITERAL_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "undefined"]);
|
|
210
275
|
var BARE_IDENTIFIER_PATH = /^[\p{ID_Start}$_][\p{ID_Continue}$]*(?:\.[\p{ID_Start}$_][\p{ID_Continue}$]*)*$/u;
|
|
@@ -418,7 +483,11 @@ function escapeFernLiteralBraces(raw) {
|
|
|
418
483
|
const end = node.position?.end.offset;
|
|
419
484
|
const root2 = value.split(".")[0];
|
|
420
485
|
const safeBuiltinPath = value.includes(".") && SAFE_BUILTIN_ROOTS.has(root2);
|
|
421
|
-
|
|
486
|
+
const isTemplatePlaceholder = /^\{\s*[A-Za-z_$][\w$.-]*\s*\}$/.test(value);
|
|
487
|
+
const isIssueList = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\s*,\s*[A-Za-z][A-Za-z0-9]*-\d+)*$/.test(value);
|
|
488
|
+
if ((isTemplatePlaceholder || isIssueList) && start !== void 0 && end !== void 0) {
|
|
489
|
+
edits.push({ start, end, value: body.slice(start, end).replace(/\\/g, "\\\\").replace(/[{}]/g, "\\$&") });
|
|
490
|
+
} else if (BARE_IDENTIFIER_PATH.test(value) && !JS_LITERAL_KEYWORDS.has(value) && !declared.has(root2) && !safeBuiltinPath && start !== void 0 && end !== void 0) {
|
|
422
491
|
edits.push({ start, end, value: `\\{${value}\\}` });
|
|
423
492
|
}
|
|
424
493
|
return;
|
|
@@ -532,10 +601,10 @@ function maskCode(body) {
|
|
|
532
601
|
let codeFence = null;
|
|
533
602
|
let fenceBuffer = [];
|
|
534
603
|
for (const line of source.split("\n")) {
|
|
535
|
-
const fenceMatch = line.match(/^\s
|
|
604
|
+
const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})/);
|
|
536
605
|
if (codeFence) {
|
|
537
606
|
fenceBuffer.push(line);
|
|
538
|
-
if (fenceMatch && fenceMatch[1][0] === codeFence) {
|
|
607
|
+
if (fenceMatch && fenceMatch[1][0] === codeFence[0] && fenceMatch[1].length >= codeFence.length && /^\s*$/.test(line.slice(fenceMatch[0].length))) {
|
|
539
608
|
lines.push(stash(fenceBuffer.join("\n")));
|
|
540
609
|
codeFence = null;
|
|
541
610
|
fenceBuffer = [];
|
|
@@ -543,7 +612,7 @@ function maskCode(body) {
|
|
|
543
612
|
continue;
|
|
544
613
|
}
|
|
545
614
|
if (fenceMatch) {
|
|
546
|
-
codeFence = fenceMatch[1]
|
|
615
|
+
codeFence = fenceMatch[1];
|
|
547
616
|
fenceBuffer = [line];
|
|
548
617
|
continue;
|
|
549
618
|
}
|
|
@@ -565,14 +634,104 @@ function normalizeHtmlComments(body) {
|
|
|
565
634
|
function normalizeFernCallouts(body) {
|
|
566
635
|
return replaceOutsideCode(body, (whole) => {
|
|
567
636
|
const openTags = [];
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
637
|
+
const output = [];
|
|
638
|
+
let cursor = 0;
|
|
639
|
+
while (cursor < whole.length) {
|
|
640
|
+
const opening = whole.indexOf("<Callout", cursor);
|
|
641
|
+
const closing = whole.indexOf("</Callout>", cursor);
|
|
642
|
+
const next = opening < 0 ? closing : closing < 0 ? opening : Math.min(opening, closing);
|
|
643
|
+
if (next < 0) break;
|
|
644
|
+
output.push(whole.slice(cursor, next));
|
|
645
|
+
if (next === closing) {
|
|
646
|
+
const tag = openTags.pop();
|
|
647
|
+
output.push(tag ? `</${tag}>` : "</Callout>");
|
|
648
|
+
cursor = next + "</Callout>".length;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const attributeStart = next + "<Callout".length;
|
|
652
|
+
if (!/[\s/>]/.test(whole[attributeStart] ?? "")) {
|
|
653
|
+
output.push("<Callout");
|
|
654
|
+
cursor = attributeStart;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
let end = attributeStart;
|
|
658
|
+
let quote = null;
|
|
659
|
+
let expressionDepth = 0;
|
|
660
|
+
for (; end < whole.length; end++) {
|
|
661
|
+
const character = whole[end];
|
|
662
|
+
if (quote) {
|
|
663
|
+
if (character === quote && whole[end - 1] !== "\\") quote = null;
|
|
664
|
+
} else if (character === '"' || character === "'") {
|
|
665
|
+
quote = character;
|
|
666
|
+
} else if (character === "{") {
|
|
667
|
+
expressionDepth++;
|
|
668
|
+
} else if (character === "}" && expressionDepth > 0) {
|
|
669
|
+
expressionDepth--;
|
|
670
|
+
} else if (character === ">" && expressionDepth === 0) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (end === whole.length) {
|
|
675
|
+
output.push(whole.slice(next));
|
|
676
|
+
cursor = whole.length;
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
const attributes = whole.slice(attributeStart, end);
|
|
680
|
+
const intent = findStaticCalloutIntent(attributes);
|
|
681
|
+
const isSelfClosing = attributes.trimEnd().endsWith("/");
|
|
682
|
+
if (!intent) {
|
|
683
|
+
output.push(whole.slice(next, end + 1));
|
|
684
|
+
if (!isSelfClosing) openTags.push(null);
|
|
685
|
+
} else {
|
|
686
|
+
const value = intent.value.toLowerCase();
|
|
687
|
+
const tag = value === "warning" ? "Warning" : value === "success" || value === "tip" ? "Tip" : value === "error" || value === "danger" ? "Error" : "Note";
|
|
688
|
+
output.push(`<${tag}${attributes.slice(0, intent.start)}${attributes.slice(intent.end)}>`);
|
|
689
|
+
if (!isSelfClosing) openTags.push(tag);
|
|
690
|
+
}
|
|
691
|
+
cursor = end + 1;
|
|
692
|
+
}
|
|
693
|
+
output.push(whole.slice(cursor));
|
|
694
|
+
return output.join("");
|
|
574
695
|
});
|
|
575
696
|
}
|
|
697
|
+
function findStaticCalloutIntent(attributes) {
|
|
698
|
+
let cursor = 0;
|
|
699
|
+
while (cursor < attributes.length) {
|
|
700
|
+
const start = cursor;
|
|
701
|
+
while (/\s/.test(attributes[cursor] ?? "")) cursor++;
|
|
702
|
+
if (cursor === start) {
|
|
703
|
+
cursor++;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const nameStart = cursor;
|
|
707
|
+
while (/[A-Za-z0-9_-]/.test(attributes[cursor] ?? "")) cursor++;
|
|
708
|
+
const name = attributes.slice(nameStart, cursor);
|
|
709
|
+
while (/\s/.test(attributes[cursor] ?? "")) cursor++;
|
|
710
|
+
if (attributes[cursor] !== "=") continue;
|
|
711
|
+
cursor++;
|
|
712
|
+
while (/\s/.test(attributes[cursor] ?? "")) cursor++;
|
|
713
|
+
const quote = attributes[cursor];
|
|
714
|
+
if (quote !== '"' && quote !== "'") {
|
|
715
|
+
if (quote === "{") {
|
|
716
|
+
let depth = 0;
|
|
717
|
+
do {
|
|
718
|
+
if (attributes[cursor] === "{") depth++;
|
|
719
|
+
if (attributes[cursor] === "}") depth--;
|
|
720
|
+
cursor++;
|
|
721
|
+
} while (cursor < attributes.length && depth > 0);
|
|
722
|
+
} else {
|
|
723
|
+
while (cursor < attributes.length && !/\s/.test(attributes[cursor])) cursor++;
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const valueStart = ++cursor;
|
|
728
|
+
while (cursor < attributes.length && attributes[cursor] !== quote) cursor++;
|
|
729
|
+
const value = attributes.slice(valueStart, cursor);
|
|
730
|
+
if (cursor < attributes.length) cursor++;
|
|
731
|
+
if (name === "intent") return { start, end: cursor, value };
|
|
732
|
+
}
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
576
735
|
function unwrapFernCodeBlockTabs(body) {
|
|
577
736
|
return replaceOutsideCode(body, (whole) => {
|
|
578
737
|
let groupDepth = 0;
|
|
@@ -679,25 +838,31 @@ ${result}`;
|
|
|
679
838
|
return result;
|
|
680
839
|
}
|
|
681
840
|
function normalizeDocusaurusTabs(body) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
|
|
841
|
+
let result = body;
|
|
842
|
+
for (let depth = 0; depth < 20; depth += 1) {
|
|
843
|
+
const updated = result.replace(/<Tabs\b([^>]*)>((?:(?!<Tabs\b)[\s\S])*?)<\/Tabs>/g, (_match, attributes, inner) => {
|
|
844
|
+
const labelByValue = /* @__PURE__ */ new Map();
|
|
845
|
+
const valuesSource = attributes.match(/\bvalues=\{(\[[\s\S]*?\])\s*\}/)?.[1];
|
|
846
|
+
if (valuesSource) {
|
|
847
|
+
for (const entry of valuesSource.matchAll(/\{([^{}]*)\}/g)) {
|
|
848
|
+
const label = entry[1].match(/\blabel:\s*(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean);
|
|
849
|
+
const value = entry[1].match(/\bvalue:\s*(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean);
|
|
850
|
+
if (value) labelByValue.set(value, label ?? value);
|
|
851
|
+
}
|
|
690
852
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
853
|
+
const cleanedAttributes = attributes.replace(/\s*defaultValue=(?:\{[^}]*\}|"[^"]*"|'[^']*')/g, "").replace(/\s*values=\{[\s\S]*?\]\s*\}/g, "").replace(/\s*values=\{[^{}]*\}/g, "").replace(/\s*groupId=(?:"[^"]*"|'[^']*')/g, "").replace(/\s*queryString(?:=(?:\{[^}]*\}|"[^"]*"|'[^']*'))?/g, "");
|
|
854
|
+
const rewrittenInner = inner.replace(/<TabItem\b([^>]*)>/g, (_itemMatch, itemAttributes) => {
|
|
855
|
+
const label = itemAttributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean);
|
|
856
|
+
const value = itemAttributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean);
|
|
857
|
+
const title = label ?? (value && labelByValue.get(value)) ?? value ?? "Tab";
|
|
858
|
+
return `<Tab title="${title.replace(/"/g, """)}">`;
|
|
859
|
+
}).replace(/<\/TabItem>/g, "</Tab>");
|
|
860
|
+
return `<ThallyTabs${cleanedAttributes}>${rewrittenInner}</ThallyTabs>`;
|
|
861
|
+
});
|
|
862
|
+
if (updated === result) break;
|
|
863
|
+
result = updated;
|
|
864
|
+
}
|
|
865
|
+
return result.replace(/<ThallyTabs\b/g, "<Tabs").replace(/<\/ThallyTabs>/g, "</Tabs>");
|
|
701
866
|
}
|
|
702
867
|
var TAB_FENCE_OPEN = /^(`{3,}|~{3,})(\S*)\s+tab\b(.*)$/;
|
|
703
868
|
function normalizeDocusaurusTabBlocks(body) {
|
|
@@ -783,15 +948,68 @@ function convertHtmlStyleAttributes(body) {
|
|
|
783
948
|
(_match, tag, before, _quote, styleText, after, selfClose) => `<${tag}${before} style={${cssTextToStyleObjectLiteral(styleText)}}${after}${selfClose ? " /" : ""}>`
|
|
784
949
|
);
|
|
785
950
|
}
|
|
786
|
-
function
|
|
951
|
+
function normalizeMintlifyUpdateLabels(body) {
|
|
952
|
+
return body.replace(/(<Update\b[^>]*\blabel=)\{(<\>[\s\S]*?<\/\>)\}/g, (match, prefix, fragment) => {
|
|
953
|
+
let root;
|
|
954
|
+
try {
|
|
955
|
+
root = descriptionParser.parse(fragment);
|
|
956
|
+
} catch {
|
|
957
|
+
return match;
|
|
958
|
+
}
|
|
959
|
+
const plainText = (node) => {
|
|
960
|
+
if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
|
|
961
|
+
if (node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" || node.type === "mdxjsEsm") return null;
|
|
962
|
+
const parts = (node.children ?? []).map(plainText);
|
|
963
|
+
return parts.some((part) => part === null) ? null : parts.join("");
|
|
964
|
+
};
|
|
965
|
+
const label = plainText(root)?.replace(/\s+/g, " ").trim();
|
|
966
|
+
return label && !/[{}]/.test(label) ? `${prefix}{${JSON.stringify(label)}}` : match;
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
function normalizeMintlifyParameterAnchors(body) {
|
|
970
|
+
const seen = /* @__PURE__ */ new Set();
|
|
971
|
+
const anchorFor = (name) => {
|
|
972
|
+
const id = `param-${name.toLowerCase().replace(/[._]/g, "-")}`;
|
|
973
|
+
if (seen.has(id)) return "";
|
|
974
|
+
seen.add(id);
|
|
975
|
+
return `<a id="${id}"></a>
|
|
976
|
+
`;
|
|
977
|
+
};
|
|
978
|
+
let fence;
|
|
979
|
+
const withHeadings = body.split("\n").map((line) => {
|
|
980
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
981
|
+
if (marker) {
|
|
982
|
+
if (!fence) fence = marker[1];
|
|
983
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && /^\s*$/.test(line.slice(marker[0].length))) fence = void 0;
|
|
984
|
+
return line;
|
|
985
|
+
}
|
|
986
|
+
if (fence) return line;
|
|
987
|
+
const heading = line.match(/^ {0,3}#{1,6}\s+`([A-Za-z][A-Za-z0-9._-]*)`(?:\s|$)/);
|
|
988
|
+
return heading ? `${anchorFor(heading[1])}${line}` : line;
|
|
989
|
+
}).join("\n");
|
|
990
|
+
return replaceOutsideCode(withHeadings, (segment) => segment.replace(
|
|
991
|
+
/<(ResponseField|ParamField)\b([^>]*?)\bname=(?:"([A-Za-z][A-Za-z0-9._-]*)"|'([A-Za-z][A-Za-z0-9._-]*)')([^>]*)>/g,
|
|
992
|
+
(match, _tag, _before, doubleQuoted, singleQuoted) => `${anchorFor(doubleQuoted ?? singleQuoted)}${match}`
|
|
993
|
+
));
|
|
994
|
+
}
|
|
995
|
+
function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
787
996
|
const runFern = platform === "fern";
|
|
788
997
|
const runMintlify = platform === "mintlify";
|
|
789
998
|
const runDocusaurus = platform === "docusaurus";
|
|
790
|
-
|
|
999
|
+
const sourceBody = runDocusaurus && unwrapMdxCodeBlocks ? unwrapDocusaurusMdxCodeBlocks(body) : body;
|
|
1000
|
+
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernCallouts(sourceBody) : sourceBody));
|
|
791
1001
|
if (runDocusaurus) {
|
|
792
|
-
|
|
793
|
-
rewritten =
|
|
1002
|
+
rewritten = normalizeDocusaurusTabBlocks(normalizeDocusaurusTabs(removeGlobalDocusaurusImports(rewritten)));
|
|
1003
|
+
rewritten = replaceOutsideCode(rewritten, (segment) => segment.replace(
|
|
1004
|
+
/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+(['"])(@site\/[^'"]+)\2\s*;?/g,
|
|
1005
|
+
(statement, binding, _quote, _source, offset) => {
|
|
1006
|
+
const withoutStatement = segment.slice(0, offset) + segment.slice(offset + statement.length);
|
|
1007
|
+
return new RegExp(`\\b${binding}\\s*(?:\\.|\\[|\\})`).test(withoutStatement) ? statement : "";
|
|
1008
|
+
}
|
|
1009
|
+
));
|
|
1010
|
+
rewritten = rewritten.replace(/^\n{2,}/, "\n");
|
|
794
1011
|
}
|
|
1012
|
+
if (runMintlify) rewritten = normalizeMintlifyParameterAnchors(rewritten);
|
|
795
1013
|
rewritten = replaceOutsideCode(rewritten, (segment) => {
|
|
796
1014
|
let result = convertHtmlStyleAttributes(segment).replace(/<!--([\s\S]*?)-->/g, (_match, content) => `{/*${content}*/}`).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Warn(\s[^>]*)?>/g, "<Warning$1>").replace(/<\/Warn>/g, "</Warning>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
|
|
797
1015
|
if (runDocusaurus) {
|
|
@@ -801,7 +1019,7 @@ function normalizeMdx(body, platform) {
|
|
|
801
1019
|
}).replace(/<\/TabItem>/g, "</Tab>").replace(/<Link\b([^>]*)\bto=(?:"([^"]*)"|'([^']*)')([^>]*)>/g, (_match, before, doubleQuoted, singleQuoted, after) => `<a${before}href="${doubleQuoted ?? singleQuoted}"${after}>`).replace(/<\/Link>/g, "</a>").replace(/<(?:DocCardList|TOCInline)\b[^>]*\/>/g, "");
|
|
802
1020
|
}
|
|
803
1021
|
if (runMintlify) {
|
|
804
|
-
result = result.replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
|
|
1022
|
+
result = normalizeMintlifyUpdateLabels(result).replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
|
|
805
1023
|
}
|
|
806
1024
|
if (runFern) {
|
|
807
1025
|
result = result.replace(/<CodeBlocks(\s[^>]*)?>/g, "<CodeGroup$1>").replace(/<\/CodeBlocks>/g, "</CodeGroup>").replace(/<ParameterField\b/g, "<ParamField").replace(/<\/ParameterField>/g, "</ParamField>").replace(/<Cards(\s[^>]*)?>/g, "<CardGroup$1>").replace(/<\/Cards>/g, "</CardGroup>").replace(/<Success(\s[^>]*)?>/g, "<Tip$1>").replace(/<\/Success>/g, "</Tip>").replace(/<Launch(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Launch>/g, "</Note>").replace(/<Files(\s[^>]*)?>\n?/g, "").replace(/<\/Files>/g, "");
|
|
@@ -809,7 +1027,15 @@ function normalizeMdx(body, platform) {
|
|
|
809
1027
|
return result;
|
|
810
1028
|
});
|
|
811
1029
|
if (runFern) rewritten = unwrapFernCodeBlockTabs(rewritten);
|
|
812
|
-
|
|
1030
|
+
const normalized = provideScopedGlobalReferences(rewritten);
|
|
1031
|
+
if (runDocusaurus && sourceBody !== body) {
|
|
1032
|
+
try {
|
|
1033
|
+
compileSync(normalized, { outputFormat: "program" });
|
|
1034
|
+
} catch {
|
|
1035
|
+
return normalizeMdx(body, platform, false);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return normalized;
|
|
813
1039
|
}
|
|
814
1040
|
function parseMarkdownPage(input) {
|
|
815
1041
|
const parsed = parseFrontmatter(input.raw);
|
|
@@ -919,6 +1145,73 @@ function normalizeAssetPath(value) {
|
|
|
919
1145
|
}
|
|
920
1146
|
|
|
921
1147
|
// src/components.ts
|
|
1148
|
+
function normalizeIndentedFences(content) {
|
|
1149
|
+
const lines = content.split("\n");
|
|
1150
|
+
let active;
|
|
1151
|
+
let ordinaryFence;
|
|
1152
|
+
let indentedJsx;
|
|
1153
|
+
let listIndent;
|
|
1154
|
+
return lines.map((line) => {
|
|
1155
|
+
const trimmed = line.trimStart();
|
|
1156
|
+
if (indentedJsx) {
|
|
1157
|
+
const normalized2 = line.startsWith(indentedJsx.indent) ? line.slice(indentedJsx.indent.length) : line;
|
|
1158
|
+
if (normalized2.trimStart().startsWith(`</${indentedJsx.tag}>`)) indentedJsx = void 0;
|
|
1159
|
+
return normalized2;
|
|
1160
|
+
}
|
|
1161
|
+
if (ordinaryFence) {
|
|
1162
|
+
const closing2 = trimmed.match(/^(`{3,}|~{3,})\s*$/)?.[1];
|
|
1163
|
+
if (closing2 && closing2[0] === ordinaryFence[0] && closing2.length >= ordinaryFence.length) ordinaryFence = void 0;
|
|
1164
|
+
return line;
|
|
1165
|
+
}
|
|
1166
|
+
if (!active) {
|
|
1167
|
+
const listItem = line.match(/^([ \t]*)(?:[-*+]|\d+[.)])\s/);
|
|
1168
|
+
const indentation = line.length - trimmed.length;
|
|
1169
|
+
if (listItem) listIndent = listItem[1].length;
|
|
1170
|
+
else if (trimmed && listIndent !== void 0 && indentation <= listIndent) listIndent = void 0;
|
|
1171
|
+
const jsx = line.match(/^([ \t]{2,})<([A-Z][\w.]*)\b[^>]*>\s*$/);
|
|
1172
|
+
if (jsx && listIndent !== void 0 && jsx[1].length > listIndent && !trimmed.endsWith("/>")) {
|
|
1173
|
+
indentedJsx = { tag: jsx[2], indent: jsx[1] };
|
|
1174
|
+
return line.slice(jsx[1].length);
|
|
1175
|
+
}
|
|
1176
|
+
const match = line.match(/^([ \t]{4,})(`{3,}|~{3,})/);
|
|
1177
|
+
if (match) {
|
|
1178
|
+
active = { indent: match[1], marker: match[2] };
|
|
1179
|
+
return line.slice(match[1].length);
|
|
1180
|
+
}
|
|
1181
|
+
const ordinary = line.match(/^[ \t]{0,3}(`{3,}|~{3,})/);
|
|
1182
|
+
if (ordinary) ordinaryFence = ordinary[1];
|
|
1183
|
+
return line;
|
|
1184
|
+
}
|
|
1185
|
+
const normalized = line.startsWith(active.indent) ? line.slice(active.indent.length) : line;
|
|
1186
|
+
const closing = normalized.trimStart().match(/^(`{3,}|~{3,})\s*$/)?.[1];
|
|
1187
|
+
if (closing && closing[0] === active.marker[0] && closing.length >= active.marker.length) active = void 0;
|
|
1188
|
+
return normalized;
|
|
1189
|
+
}).join("\n");
|
|
1190
|
+
}
|
|
1191
|
+
function staticServerHelper(path, imported, local) {
|
|
1192
|
+
if (imported === "default") return null;
|
|
1193
|
+
const parsed = ts.createSourceFile(path, readFileSync(path, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
1194
|
+
const isLiteral = (node) => {
|
|
1195
|
+
if (ts.isParenthesizedExpression(node)) return isLiteral(node.expression);
|
|
1196
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return true;
|
|
1197
|
+
if ([ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.NullKeyword].includes(node.kind)) return true;
|
|
1198
|
+
if (ts.isPrefixUnaryExpression(node)) return [ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(node.operator) && ts.isNumericLiteral(node.operand);
|
|
1199
|
+
if (ts.isArrayLiteralExpression(node)) return node.elements.every((element) => ts.isExpression(element) && isLiteral(element));
|
|
1200
|
+
if (ts.isObjectLiteralExpression(node)) return node.properties.every((property) => ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name)) && isLiteral(property.initializer));
|
|
1201
|
+
return false;
|
|
1202
|
+
};
|
|
1203
|
+
for (const statement of parsed.statements) {
|
|
1204
|
+
if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue;
|
|
1205
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1206
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== imported || !declaration.initializer || !ts.isArrowFunction(declaration.initializer)) continue;
|
|
1207
|
+
const arrow = declaration.initializer;
|
|
1208
|
+
if (arrow.parameters.length > 0) continue;
|
|
1209
|
+
const result = ts.isBlock(arrow.body) ? arrow.body.statements.length === 1 && ts.isReturnStatement(arrow.body.statements[0]) ? arrow.body.statements[0].expression : void 0 : arrow.body;
|
|
1210
|
+
if (result && isLiteral(result)) return `export const ${local} = () => (${result.getText(parsed)});`;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
922
1215
|
var YOUTUBE_VIDEO_ID = /^[\w-]{1,64}$/;
|
|
923
1216
|
var YOUTUBE_ID_PACKAGES = /* @__PURE__ */ new Set(["react-lite-youtube-embed", "react-youtube", "@justinribeiro/lite-youtube"]);
|
|
924
1217
|
var YOUTUBE_URL = /(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{1,64})/i;
|
|
@@ -1474,7 +1767,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1474
1767
|
function transform(raw, currentFile) {
|
|
1475
1768
|
const parsedFrontmatter = parseFrontmatter(raw).content;
|
|
1476
1769
|
const frontmatter = raw.slice(0, raw.length - parsedFrontmatter.length);
|
|
1477
|
-
const content = normalizeHtmlComments(parsedFrontmatter);
|
|
1770
|
+
const content = normalizeIndentedFences(normalizeHtmlComments(parsedFrontmatter));
|
|
1478
1771
|
let tree;
|
|
1479
1772
|
try {
|
|
1480
1773
|
tree = parser.parse(content);
|
|
@@ -1488,6 +1781,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1488
1781
|
const declarations = [];
|
|
1489
1782
|
const moduleImports = [];
|
|
1490
1783
|
const realPageImports = [];
|
|
1784
|
+
const serverPageDeclarations = [];
|
|
1491
1785
|
const sharedImportEdits = [];
|
|
1492
1786
|
let hasUnsupportedImports = false;
|
|
1493
1787
|
function hasExpressionReference(names, options = {}) {
|
|
@@ -1553,13 +1847,19 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1553
1847
|
const rawSpecifier = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
1554
1848
|
const specifier = rawSpecifier.startsWith("@site/") ? `/${rawSpecifier.slice("@site/".length)}` : rawSpecifier;
|
|
1555
1849
|
if (isScaffoldProvidedImport(specifier)) {
|
|
1850
|
+
if (specifier === "react" && bindings.length > 0 && bindings.every((binding) => REACT_GLOBALS.has(binding.imported)) && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
|
|
1851
|
+
edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1556
1854
|
moduleImports.push(statement.getText(ast));
|
|
1557
1855
|
continue;
|
|
1558
1856
|
}
|
|
1559
1857
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
1560
1858
|
if (SHARED_IMPORTS.has(specifier)) {
|
|
1561
|
-
|
|
1562
|
-
|
|
1859
|
+
const isUnusedReactHookImport = specifier === "react" && bindings.length > 0 && bindings.every((binding) => REACT_GLOBALS.has(binding.imported)) && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)));
|
|
1860
|
+
if (!isUnusedReactHookImport) moduleImports.push(statement.getText(ast));
|
|
1861
|
+
if (isUnusedReactHookImport) edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
1862
|
+
else sharedImportEdits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
1563
1863
|
} else if (specifier.startsWith("@theme/") || specifier.startsWith("@docusaurus/")) {
|
|
1564
1864
|
edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
1565
1865
|
} else if (hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
|
|
@@ -1595,18 +1895,19 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1595
1895
|
hasUnsupportedImports = true;
|
|
1596
1896
|
continue;
|
|
1597
1897
|
}
|
|
1598
|
-
if (bindings.some((binding) => !/^[A-Z]/.test(binding.local))) {
|
|
1599
|
-
warn("MDX imports containing non-component values require manual migration; the import was preserved.", currentFile);
|
|
1600
|
-
hasUnsupportedImports = true;
|
|
1601
|
-
continue;
|
|
1602
|
-
}
|
|
1603
1898
|
try {
|
|
1604
|
-
const
|
|
1899
|
+
const sourcePath = resolveDependency(specifier, currentFile);
|
|
1900
|
+
const path = copyGraph(sourcePath);
|
|
1605
1901
|
const isSvgUsedAsTag = extname(path).toLowerCase() === ".svg" && bindings.some((binding) => usedAsJsxTagName(binding.local));
|
|
1606
1902
|
for (const binding of bindings) {
|
|
1607
1903
|
const registerPath = isSvgUsedAsTag ? wrapSvgAsComponent(path) : path;
|
|
1608
1904
|
const registerImported = isSvgUsedAsTag ? "default" : binding.imported;
|
|
1609
1905
|
if (!isSvgUsedAsTag && hasExpressionReference(/* @__PURE__ */ new Set([binding.local]))) {
|
|
1906
|
+
const helper = staticServerHelper(sourcePath, binding.imported, binding.local);
|
|
1907
|
+
if (helper) {
|
|
1908
|
+
serverPageDeclarations.push(helper);
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1610
1911
|
const aliasSpecifier = portableSpecifier(`@/${registerPath.replace(/^src\//, "").replace(/\\/g, "/")}`);
|
|
1611
1912
|
realPageImports.push(`import { ${registerImported} as ${binding.local} } from ${JSON.stringify(aliasSpecifier)};`);
|
|
1612
1913
|
continue;
|
|
@@ -1617,6 +1918,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1617
1918
|
}
|
|
1618
1919
|
edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
1619
1920
|
} catch (error) {
|
|
1921
|
+
if (hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
|
|
1922
|
+
warnings.push({
|
|
1923
|
+
code: "skipped-file",
|
|
1924
|
+
message: `This page was excluded because local import ${JSON.stringify(rawSpecifier)} could not be copied and is used in an expression: ${error instanceof Error ? error.message : "unsupported dependency"}.`,
|
|
1925
|
+
source: relative2(root, currentFile).replace(/\\/g, "/")
|
|
1926
|
+
});
|
|
1927
|
+
hasUnsupportedImports = true;
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1620
1930
|
const failedPackage = error instanceof Error ? error.message.match(/^external package (\S+) requires manual installation and review$/)?.[1] : void 0;
|
|
1621
1931
|
if (failedPackage && YOUTUBE_ID_PACKAGES.has(failedPackage)) {
|
|
1622
1932
|
for (const binding of bindings) unsupportedImports.set(binding.local, failedPackage);
|
|
@@ -1758,9 +2068,64 @@ ${inlineSource}` });
|
|
|
1758
2068
|
}
|
|
1759
2069
|
edits.push(...declarations.map(({ start, end }) => ({ start, end, value: "" })), ...sharedImportEdits);
|
|
1760
2070
|
}
|
|
2071
|
+
const pageFunctions = functionDeclaredNames(content);
|
|
2072
|
+
const passiveRoots = [];
|
|
2073
|
+
const wrappedRoots = /* @__PURE__ */ new Set();
|
|
2074
|
+
walk(tree, (node) => {
|
|
2075
|
+
if (!node.name || !aliases.has(node.name) || !functionValuedAttribute(node, pageFunctions) || node.position?.start.offset === void 0 || node.position.end.offset === void 0) return;
|
|
2076
|
+
if (passiveRoots.some((root2) => node.position.start.offset >= root2.position.start.offset && node.position.end.offset <= root2.position.end.offset)) return;
|
|
2077
|
+
const functionAttributes = (node.attributes ?? []).filter((attribute) => {
|
|
2078
|
+
const value = attribute.value && typeof attribute.value === "object" ? attribute.value.value?.trim() : void 0;
|
|
2079
|
+
return !!value && pageFunctions.has(value);
|
|
2080
|
+
});
|
|
2081
|
+
if (functionAttributes.length === 1 && functionAttributes[0].name && functionAttributes[0].position?.start.offset !== void 0 && functionAttributes[0].position.end.offset !== void 0) {
|
|
2082
|
+
const functionName = functionAttributes[0].value.value.trim();
|
|
2083
|
+
const declaration = declarations.find((entry) => new RegExp(`^export const ${functionName}\\s*=`).test(entry.source));
|
|
2084
|
+
const safeDeclaration = declaration?.source.match(/^export const \w+\s*=\s*\(\s*\{\s*children(?:\s*,\s*\.\.\.props)?\s*\}\s*\)\s*=>\s*\([\s\S]*\)\s*;?$/) && !/\b(?:window|document|process|fetch|use[A-Z]\w*)\b/.test(declaration.source) && [...declaration.source.matchAll(/<([A-Z]\w*)\b/g)].every((match) => match[1] === "CodeBlock") && [...declaration.source.matchAll(/\{([^{}]*)\}/g)].every((match) => ["children", "...props", "children, ...props"].includes(match[1].trim()));
|
|
2085
|
+
const binding = registrations.get(aliases.get(node.name));
|
|
2086
|
+
if (safeDeclaration && binding) {
|
|
2087
|
+
const fallbackDeclaration = declaration.source.replace(/<CodeBlock\b[^>]*>/g, "<pre><code>").replace(/<\/CodeBlock>/g, "</code></pre>");
|
|
2088
|
+
const wrapperPath = `${destinationRoot}/callback-${hash(`${currentFile}:${node.position.start.offset}`)}.jsx`;
|
|
2089
|
+
const imported = binding.imported === "default" ? "default" : binding.imported;
|
|
2090
|
+
const importClause = imported === "default" ? "import Original" : `import { ${imported} as Original }`;
|
|
2091
|
+
const specifier = portableSpecifier(`./${relative2(destinationRoot, binding.path).replace(/\\/g, "/")}`);
|
|
2092
|
+
const wrapperName = register(wrapperPath, "CallbackWrapper");
|
|
2093
|
+
copied.set(wrapperPath, { path: wrapperPath, content: [
|
|
2094
|
+
"'use client';",
|
|
2095
|
+
`${importClause} from ${JSON.stringify(specifier)};`,
|
|
2096
|
+
fallbackDeclaration,
|
|
2097
|
+
`export function CallbackWrapper({ children, ...props }) { return <Original {...props} ${functionAttributes[0].name}={${functionName}}>{children}</Original>; }`
|
|
2098
|
+
].join("\n") });
|
|
2099
|
+
const start = node.position.start.offset;
|
|
2100
|
+
const end = node.position.end.offset;
|
|
2101
|
+
const source2 = content.slice(start, end);
|
|
2102
|
+
const opening = source2.indexOf(`<${node.name}`);
|
|
2103
|
+
const closing = source2.lastIndexOf(`</${node.name}`);
|
|
2104
|
+
if (opening >= 0) edits.push({ start: start + opening + 1, end: start + opening + 1 + node.name.length, value: wrapperName });
|
|
2105
|
+
if (closing >= 0) edits.push({ start: start + closing + 2, end: start + closing + 2 + node.name.length, value: wrapperName });
|
|
2106
|
+
edits.push({ start: functionAttributes[0].position.start.offset, end: functionAttributes[0].position.end.offset, value: "" });
|
|
2107
|
+
wrappedRoots.add(node);
|
|
2108
|
+
if (declaration.source.includes("<CodeBlock")) warn(`Interactive <${node.name}> was retained with a basic code block in place of Mintlify's CodeBlock.`, currentFile);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
const source = content.slice(node.position.start.offset, node.position.end.offset);
|
|
2113
|
+
const openingEnd = source.indexOf(">");
|
|
2114
|
+
const closingStart = source.lastIndexOf(`</${node.name}`);
|
|
2115
|
+
const children = openingEnd >= 0 && closingStart > openingEnd ? source.slice(openingEnd + 1, closingStart) : "";
|
|
2116
|
+
edits.push({
|
|
2117
|
+
start: node.position.start.offset,
|
|
2118
|
+
end: node.position.end.offset,
|
|
2119
|
+
value: `<div data-migration-interactive-fallback="${node.name}">${children}</div>`
|
|
2120
|
+
});
|
|
2121
|
+
passiveRoots.push(node);
|
|
2122
|
+
warn(`Interactive <${node.name}> passes a page-local function into a client component; its child content was retained without the interactive control. Move the callback into a client component to restore it.`, currentFile);
|
|
2123
|
+
});
|
|
1761
2124
|
walk(tree, (node) => {
|
|
1762
2125
|
const replacement = node.name ? aliases.get(node.name) : void 0;
|
|
1763
2126
|
if (!replacement || node.position?.start.offset === void 0 || node.position.end.offset === void 0) return;
|
|
2127
|
+
if (wrappedRoots.has(node)) return;
|
|
2128
|
+
if (passiveRoots.some((root2) => node.position.start.offset >= root2.position.start.offset && node.position.end.offset <= root2.position.end.offset)) return;
|
|
1764
2129
|
const start = node.position.start.offset;
|
|
1765
2130
|
const end = node.position.end.offset;
|
|
1766
2131
|
if (extracted.some((item) => start >= item.node.position.start.offset && end <= item.node.position.end.offset)) return;
|
|
@@ -1780,7 +2145,8 @@ ${inlineSource}` });
|
|
|
1780
2145
|
edits.push({ start, end, value });
|
|
1781
2146
|
});
|
|
1782
2147
|
const rendered = applyReplacements(content, edits);
|
|
1783
|
-
const
|
|
2148
|
+
const pageStatements = [.../* @__PURE__ */ new Set([...realPageImports, ...serverPageDeclarations])];
|
|
2149
|
+
const realImportsBlock = pageStatements.length ? `${pageStatements.join("\n")}
|
|
1784
2150
|
|
|
1785
2151
|
` : "";
|
|
1786
2152
|
return frontmatter + realImportsBlock + rendered;
|
|
@@ -2695,7 +3061,7 @@ function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
|
2695
3061
|
function sourceReferenceKey(value) {
|
|
2696
3062
|
return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").replace(/\/$/, "");
|
|
2697
3063
|
}
|
|
2698
|
-
function rewriteDocusaurusLinks(body, current, descriptors) {
|
|
3064
|
+
function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
2699
3065
|
const routes = /* @__PURE__ */ new Map();
|
|
2700
3066
|
for (const descriptor of descriptors) {
|
|
2701
3067
|
const sourceKey = sourceReferenceKey(descriptor.sourcePath);
|
|
@@ -2711,26 +3077,47 @@ function rewriteDocusaurusLinks(body, current, descriptors) {
|
|
|
2711
3077
|
const suffixIndex = target.search(/[?#]/);
|
|
2712
3078
|
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
2713
3079
|
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
2714
|
-
const candidates = path.startsWith("/") ? [
|
|
3080
|
+
const candidates = path.startsWith("/") ? [
|
|
3081
|
+
sourceReferenceKey(path),
|
|
3082
|
+
sourceReferenceKey(path).replace(/^docs\//, ""),
|
|
3083
|
+
sourceReferenceKey(path).replace(/^docs\/(?:next|current|latest)\//, "")
|
|
3084
|
+
] : [
|
|
2715
3085
|
sourceReferenceKey(posix.normalize(posix.join(currentDirectory, path))),
|
|
2716
3086
|
sourceReferenceKey(path)
|
|
2717
3087
|
];
|
|
2718
3088
|
const route = candidates.map((candidate) => routes.get(candidate)).find(Boolean);
|
|
2719
|
-
if (!route)
|
|
3089
|
+
if (!route) {
|
|
3090
|
+
if (path.startsWith("/") && path !== "/" && options.sourceOrigin) {
|
|
3091
|
+
options.onExternalLink?.(target);
|
|
3092
|
+
return new URL(target, options.sourceOrigin).toString();
|
|
3093
|
+
}
|
|
3094
|
+
return target;
|
|
3095
|
+
}
|
|
2720
3096
|
return `${route === "introduction" ? "/" : `/${route}`}${suffix}`;
|
|
2721
3097
|
}
|
|
2722
3098
|
let codeFence = null;
|
|
2723
3099
|
return body.split("\n").map((line) => {
|
|
2724
|
-
const fence = line.match(/^\s
|
|
3100
|
+
const fence = line.match(/^\s{0,3}(`{3,}|~{3,})/);
|
|
2725
3101
|
if (fence) {
|
|
2726
|
-
if (!codeFence) codeFence = fence[1]
|
|
2727
|
-
else if (fence[1][0] === codeFence) codeFence = null;
|
|
3102
|
+
if (!codeFence) codeFence = fence[1];
|
|
3103
|
+
else if (fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length && /^\s*$/.test(line.slice(fence[0].length))) codeFence = null;
|
|
2728
3104
|
return line;
|
|
2729
3105
|
}
|
|
2730
3106
|
if (codeFence) return line;
|
|
2731
3107
|
return line.replace(/(\]\()([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(target)}`).replace(/(\bhref=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(target)}${suffix}`);
|
|
2732
3108
|
}).join("\n");
|
|
2733
3109
|
}
|
|
3110
|
+
function readDocusaurusSiteOrigin(repositoryRoot) {
|
|
3111
|
+
const configPath = findDocusaurusConfigPath(repositoryRoot);
|
|
3112
|
+
if (!configPath) return void 0;
|
|
3113
|
+
const address = readBoundedText(configPath).match(/\burl['"]?\s*:\s*(['"])(https?:\/\/[^'"]+)\1/)?.[2];
|
|
3114
|
+
if (!address) return void 0;
|
|
3115
|
+
try {
|
|
3116
|
+
return new URL(address).origin;
|
|
3117
|
+
} catch {
|
|
3118
|
+
return void 0;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
2734
3121
|
function readBoundedText(path) {
|
|
2735
3122
|
if (lstatSync3(path).size > MAX_CONFIG_BYTES) {
|
|
2736
3123
|
throw new Error("Docusaurus sidebar config exceeded the 1 MB static-parser limit.");
|
|
@@ -2900,9 +3287,12 @@ function configuredSidebarPath(repositoryRoot) {
|
|
|
2900
3287
|
}
|
|
2901
3288
|
return null;
|
|
2902
3289
|
}
|
|
2903
|
-
function readDocusaurusSidebars(repositoryRoot) {
|
|
2904
|
-
const configured = configuredSidebarPath(repositoryRoot);
|
|
2905
|
-
const sourcePath = [configured, ...SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) =>
|
|
3290
|
+
function readDocusaurusSidebars(repositoryRoot, versionedSidebarPath) {
|
|
3291
|
+
const configured = versionedSidebarPath ?? configuredSidebarPath(repositoryRoot);
|
|
3292
|
+
const sourcePath = [configured, ...versionedSidebarPath ? [] : SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) => {
|
|
3293
|
+
const path = resolveWithin(repositoryRoot, candidate);
|
|
3294
|
+
return existsSync3(path) && lstatSync3(path).isFile();
|
|
3295
|
+
});
|
|
2906
3296
|
if (!sourcePath) return null;
|
|
2907
3297
|
const absolutePath = resolveWithin(repositoryRoot, sourcePath);
|
|
2908
3298
|
const source = readBoundedText(absolutePath);
|
|
@@ -3188,8 +3578,8 @@ function projectDocusaurusNavigation(input) {
|
|
|
3188
3578
|
}
|
|
3189
3579
|
|
|
3190
3580
|
// src/fern.ts
|
|
3191
|
-
import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync4 } from "fs";
|
|
3192
|
-
import { dirname as dirname3, relative as relative4 } from "path";
|
|
3581
|
+
import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
3582
|
+
import { basename as basename2, dirname as dirname3, extname as extname4, relative as relative4 } from "path";
|
|
3193
3583
|
import { parse as parseYaml3 } from "yaml";
|
|
3194
3584
|
var MAX_CONFIG_BYTES2 = 2e6;
|
|
3195
3585
|
function objectValue3(value) {
|
|
@@ -3243,8 +3633,9 @@ function fernLabelSlug(label) {
|
|
|
3243
3633
|
}
|
|
3244
3634
|
function segmentFor(value, label, context) {
|
|
3245
3635
|
if (value["skip-slug"] === true) return null;
|
|
3246
|
-
|
|
3247
|
-
|
|
3636
|
+
if (typeof value.slug !== "string") return fernLabelSlug(label) || null;
|
|
3637
|
+
const explicit = value.slug.trim();
|
|
3638
|
+
if (!explicit) return null;
|
|
3248
3639
|
const slugified = fernBasicSlug(explicit);
|
|
3249
3640
|
if (context && explicit !== slugified && FERN_SEGMENT_ALIAS.test(explicit)) {
|
|
3250
3641
|
context.segmentAliases.set(slugified, explicit);
|
|
@@ -3258,8 +3649,18 @@ function uniqueNavigationId(base, context) {
|
|
|
3258
3649
|
return `${base}-${index}`;
|
|
3259
3650
|
}
|
|
3260
3651
|
function registerPageAt(rawPath, base, context) {
|
|
3261
|
-
|
|
3262
|
-
|
|
3652
|
+
let sourcePath;
|
|
3653
|
+
try {
|
|
3654
|
+
const absolute = resolveWithinRoot(
|
|
3655
|
+
context.pathPrefix ? resolveWithin(context.fernRoot, context.pathPrefix) : context.fernRoot,
|
|
3656
|
+
rawPath.trim(),
|
|
3657
|
+
context.repositoryRoot
|
|
3658
|
+
);
|
|
3659
|
+
sourcePath = relative4(context.fernRoot, absolute).replace(/\\/g, "/");
|
|
3660
|
+
} catch {
|
|
3661
|
+
warnOnce2(context, `fern-unsafe-page-${rawPath}`, `Fern page path "${rawPath}" escapes the repository and was skipped.`);
|
|
3662
|
+
return "";
|
|
3663
|
+
}
|
|
3263
3664
|
const navigationId = uniqueNavigationId(base || "introduction", context);
|
|
3264
3665
|
context.seenNavigationIds.add(navigationId);
|
|
3265
3666
|
context.descriptors.push({ sourcePath, navigationId });
|
|
@@ -3268,17 +3669,62 @@ function registerPageAt(rawPath, base, context) {
|
|
|
3268
3669
|
context.sourcePathToNavigationId.set(sourcePath, navigationId);
|
|
3269
3670
|
return navigationId;
|
|
3270
3671
|
}
|
|
3672
|
+
function registerFolder(object, parentSegments, context) {
|
|
3673
|
+
const rawPath = String(object.folder).trim();
|
|
3674
|
+
let folder;
|
|
3675
|
+
try {
|
|
3676
|
+
folder = resolveWithinRoot(
|
|
3677
|
+
context.pathPrefix ? resolveWithin(context.fernRoot, context.pathPrefix) : context.fernRoot,
|
|
3678
|
+
rawPath,
|
|
3679
|
+
context.repositoryRoot
|
|
3680
|
+
);
|
|
3681
|
+
} catch {
|
|
3682
|
+
warnOnce2(context, `fern-unsafe-folder-${rawPath}`, `Fern folder "${rawPath}" escapes the repository and was skipped.`);
|
|
3683
|
+
return null;
|
|
3684
|
+
}
|
|
3685
|
+
if (!existsSync4(folder) || !lstatSync4(folder).isDirectory()) {
|
|
3686
|
+
warnOnce2(context, `fern-missing-folder-${rawPath}`, `Fern folder "${rawPath}" does not exist and was skipped.`);
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
const label = typeof object.title === "string" && object.title.trim() ? object.title.trim() : titleCase3(basename2(folder));
|
|
3690
|
+
const segment = typeof object.slug === "string" || object["skip-slug"] === true ? segmentFor(object, label, context) : fernBasicSlug(basename2(folder));
|
|
3691
|
+
const segments = segment ? [...parentSegments, segment] : parentSegments;
|
|
3692
|
+
const visit = (directory, routeSegments, depth) => {
|
|
3693
|
+
if (depth > 12 || context.descriptors.length >= 5e3) return [];
|
|
3694
|
+
const pages2 = [];
|
|
3695
|
+
const entries = readdirSync(directory, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".") && !entry.isSymbolicLink()).sort((left, right) => Number(left.name !== "index.mdx" && left.name !== "index.md") - Number(right.name !== "index.mdx" && right.name !== "index.md") || left.name.localeCompare(right.name));
|
|
3696
|
+
for (const entry of entries) {
|
|
3697
|
+
const path = resolveWithin(directory, entry.name);
|
|
3698
|
+
if (entry.isDirectory()) {
|
|
3699
|
+
const nested = visit(path, [...routeSegments, fernBasicSlug(entry.name)], depth + 1);
|
|
3700
|
+
if (nested.length) pages2.push({ group: titleCase3(entry.name), pages: nested });
|
|
3701
|
+
} else if (entry.isFile() && [".md", ".mdx"].includes(extname4(entry.name).toLowerCase())) {
|
|
3702
|
+
const stem = basename2(entry.name, extname4(entry.name));
|
|
3703
|
+
const pageSegment = stem.toLowerCase() === "index" ? null : fernBasicSlug(stem);
|
|
3704
|
+
const route = [...routeSegments, ...pageSegment ? [pageSegment] : []].join("/");
|
|
3705
|
+
const registered = registerPageAt(relative4(context.fernRoot, path), route, { ...context, pathPrefix: "" });
|
|
3706
|
+
if (registered) pages2.push(registered);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
return pages2;
|
|
3710
|
+
};
|
|
3711
|
+
const pages = visit(folder, segments, 0);
|
|
3712
|
+
if (!pages.length) return null;
|
|
3713
|
+
addBareRouteRedirect(segments, pages, context);
|
|
3714
|
+
return { group: label, pages };
|
|
3715
|
+
}
|
|
3271
3716
|
function registerPage(object, parentSegments, context) {
|
|
3272
3717
|
if (typeof object.path !== "string" || !object.path.trim()) return null;
|
|
3273
3718
|
const label = typeof object.page === "string" && object.page.trim() ? object.page.trim() : "Untitled";
|
|
3274
3719
|
const segment = segmentFor(object, label, context);
|
|
3275
3720
|
const base = [...parentSegments, segment].filter(Boolean).join("/");
|
|
3276
|
-
return registerPageAt(object.path, base, context);
|
|
3721
|
+
return registerPageAt(object.path, base, context) || null;
|
|
3277
3722
|
}
|
|
3278
3723
|
function convertNode(node, parentSegments, context) {
|
|
3279
3724
|
const object = objectValue3(node);
|
|
3280
3725
|
if (!object) return null;
|
|
3281
3726
|
if (typeof object.page === "string") return registerPage(object, parentSegments, context);
|
|
3727
|
+
if (typeof object.folder === "string") return registerFolder(object, parentSegments, context);
|
|
3282
3728
|
if (typeof object.section === "string") {
|
|
3283
3729
|
const label = object.section;
|
|
3284
3730
|
const segment = segmentFor(object, label, context);
|
|
@@ -3287,7 +3733,8 @@ function convertNode(node, parentSegments, context) {
|
|
|
3287
3733
|
const pages = [];
|
|
3288
3734
|
const hasOwnPath = typeof object.path === "string" && object.path.trim().length > 0;
|
|
3289
3735
|
if (hasOwnPath) {
|
|
3290
|
-
|
|
3736
|
+
const registered = registerPageAt(object.path, segments.join("/"), context);
|
|
3737
|
+
if (registered) pages.push(registered);
|
|
3291
3738
|
}
|
|
3292
3739
|
for (const child of contents) {
|
|
3293
3740
|
const converted = convertNode(child, segments, context);
|
|
@@ -3364,6 +3811,7 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
|
|
|
3364
3811
|
}
|
|
3365
3812
|
const versionConfig = objectValue3(readBoundedYaml(versionPath));
|
|
3366
3813
|
if (!versionConfig) return config;
|
|
3814
|
+
context.pathPrefix = relative4(fernRoot, dirname3(versionPath)).replace(/\\/g, "/");
|
|
3367
3815
|
return { ...config, navigation: versionConfig.navigation, tabs: versionConfig.tabs ?? config.tabs };
|
|
3368
3816
|
} catch (error) {
|
|
3369
3817
|
warnOnce2(
|
|
@@ -3463,6 +3911,8 @@ function projectFernProducts(rawProducts, fernRoot, context) {
|
|
|
3463
3911
|
}
|
|
3464
3912
|
function projectFernNavigation(input) {
|
|
3465
3913
|
const context = {
|
|
3914
|
+
fernRoot: input.fernRoot,
|
|
3915
|
+
repositoryRoot: input.repositoryRoot ?? input.fernRoot,
|
|
3466
3916
|
descriptors: [],
|
|
3467
3917
|
seenNavigationIds: /* @__PURE__ */ new Set(),
|
|
3468
3918
|
apiSections: [],
|
|
@@ -3618,7 +4068,8 @@ var REPOSITORY_ONLY_DOCUMENTS = /* @__PURE__ */ new Set([
|
|
|
3618
4068
|
"security.md"
|
|
3619
4069
|
]);
|
|
3620
4070
|
var SNIPPET_DIRECTORIES = /* @__PURE__ */ new Set(["snippets", "_snippets", "partials", "_partials"]);
|
|
3621
|
-
var SNIPPET_IMPORT_PATTERN =
|
|
4071
|
+
var SNIPPET_IMPORT_PATTERN = /\bimport\s+(?:\{\s*(?:default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}|([A-Z][A-Za-z0-9_]*))\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?/g;
|
|
4072
|
+
var SNIPPET_VALUE_IMPORT_PATTERN = /\bimport\s*\{([^{}]+)\}\s*from\s+['"]([^'"]+\.mdx?)['"]\s*;?/g;
|
|
3622
4073
|
var SNIPPET_TAG_PATTERN = /<Snippet\s+file=(?:"([^"]+)"|'([^']+)')\s*(?:\/>|>[\s\S]*?<\/Snippet>)/g;
|
|
3623
4074
|
var MINTIGNORE_FILENAME = ".mintignore";
|
|
3624
4075
|
var createIgnoreMatcher = createRequire(import.meta.url)("ignore");
|
|
@@ -3664,7 +4115,7 @@ function countMarkdownPages(directory, limit = 2e4) {
|
|
|
3664
4115
|
const current = stack.pop();
|
|
3665
4116
|
let entries;
|
|
3666
4117
|
try {
|
|
3667
|
-
entries =
|
|
4118
|
+
entries = readdirSync2(current, { withFileTypes: true });
|
|
3668
4119
|
} catch {
|
|
3669
4120
|
continue;
|
|
3670
4121
|
}
|
|
@@ -3683,7 +4134,7 @@ function pickPreferredDocsRoot(candidates, repositoryDir, platformLabel, warning
|
|
|
3683
4134
|
const ranked = [...candidates].sort((left, right) => {
|
|
3684
4135
|
const byRank = (rank?.(right) ?? 0) - (rank?.(left) ?? 0);
|
|
3685
4136
|
if (byRank !== 0) return byRank;
|
|
3686
|
-
const byName = (
|
|
4137
|
+
const byName = (basename3(right) === "docs" ? 1 : 0) - (basename3(left) === "docs" ? 1 : 0);
|
|
3687
4138
|
if (byName !== 0) return byName;
|
|
3688
4139
|
return countMarkdownPages(right) - countMarkdownPages(left);
|
|
3689
4140
|
});
|
|
@@ -3717,7 +4168,7 @@ function findMintlifyProjectRoot(repositoryDir, docsDir, warnings) {
|
|
|
3717
4168
|
visited++;
|
|
3718
4169
|
if (hasMintlifyConfig(current.directory)) candidates.push(current.directory);
|
|
3719
4170
|
if (current.depth >= 4) continue;
|
|
3720
|
-
for (const entry of
|
|
4171
|
+
for (const entry of readdirSync2(current.directory, { withFileTypes: true })) {
|
|
3721
4172
|
if (!entry.isDirectory() || entry.isSymbolicLink() || isIgnoredDirectory(entry.name)) continue;
|
|
3722
4173
|
queue.push({ directory: resolveWithin(current.directory, entry.name), depth: current.depth + 1 });
|
|
3723
4174
|
}
|
|
@@ -3749,7 +4200,7 @@ function findFernProjectRoot(repositoryDir, docsDir) {
|
|
|
3749
4200
|
visited++;
|
|
3750
4201
|
if (hasFernConfig(current.directory)) return current.directory;
|
|
3751
4202
|
if (current.depth >= 4) continue;
|
|
3752
|
-
for (const entry of
|
|
4203
|
+
for (const entry of readdirSync2(current.directory, { withFileTypes: true })) {
|
|
3753
4204
|
if (!entry.isDirectory() || entry.isSymbolicLink() || isIgnoredDirectory(entry.name)) continue;
|
|
3754
4205
|
queue.push({ directory: resolveWithin(current.directory, entry.name), depth: current.depth + 1 });
|
|
3755
4206
|
}
|
|
@@ -3775,7 +4226,7 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir, warnings) {
|
|
|
3775
4226
|
visited++;
|
|
3776
4227
|
if (hasDocusaurusConfig(current.directory)) candidates.push(current.directory);
|
|
3777
4228
|
if (current.depth >= 4) continue;
|
|
3778
|
-
for (const entry of
|
|
4229
|
+
for (const entry of readdirSync2(current.directory, { withFileTypes: true })) {
|
|
3779
4230
|
if (!entry.isDirectory() || entry.isSymbolicLink() || isIgnoredDirectory(entry.name)) continue;
|
|
3780
4231
|
queue.push({
|
|
3781
4232
|
directory: resolveWithin(current.directory, entry.name),
|
|
@@ -3796,6 +4247,52 @@ function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
|
3796
4247
|
const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
|
|
3797
4248
|
return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
|
|
3798
4249
|
}
|
|
4250
|
+
function docusaurusArchiveRoots(projectRoot, repositoryDir, warnings) {
|
|
4251
|
+
const archives = [];
|
|
4252
|
+
const relativeRoot = relative5(repositoryDir, projectRoot).replace(/\\/g, "/");
|
|
4253
|
+
const directoryNames = (directory) => {
|
|
4254
|
+
const path = resolveWithin(projectRoot, directory);
|
|
4255
|
+
if (!existsSync5(path) || !lstatSync5(path).isDirectory()) return [];
|
|
4256
|
+
return readdirSync2(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => entry.name).sort();
|
|
4257
|
+
};
|
|
4258
|
+
let versions = directoryNames("versioned_docs").filter((name) => /^version-[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name));
|
|
4259
|
+
const versionsPath = resolveWithin(projectRoot, "versions.json");
|
|
4260
|
+
if (existsSync5(versionsPath) && lstatSync5(versionsPath).isFile()) {
|
|
4261
|
+
try {
|
|
4262
|
+
if (lstatSync5(versionsPath).size > 1e5) throw new Error("file exceeds the 100 KB limit");
|
|
4263
|
+
const declared = JSON.parse(readFileSync5(versionsPath, "utf8"));
|
|
4264
|
+
if (!Array.isArray(declared) || !declared.every((value) => typeof value === "string")) throw new Error("expected an array of version names");
|
|
4265
|
+
const declaredOrder = declared.map((value) => `version-${value}`);
|
|
4266
|
+
versions = [...declaredOrder.filter((name) => versions.includes(name)), ...versions.filter((name) => !declaredOrder.includes(name))];
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
warnings.push({ code: "unsupported-config", message: `Docusaurus versions.json could not be read safely: ${error instanceof Error ? error.message : String(error)}` });
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
const sidebarPath = (version) => {
|
|
4272
|
+
const candidates = ["json", "js", "cjs", "mjs", "ts"].map((extension) => `versioned_sidebars/${version}-sidebars.${extension}`);
|
|
4273
|
+
return candidates.find((candidate) => {
|
|
4274
|
+
const path = resolveWithin(projectRoot, candidate);
|
|
4275
|
+
return existsSync5(path) && lstatSync5(path).isFile();
|
|
4276
|
+
});
|
|
4277
|
+
};
|
|
4278
|
+
const add = (directory, routePrefix, label, sidebar) => {
|
|
4279
|
+
const docsDir = [relativeRoot, directory].filter(Boolean).join("/");
|
|
4280
|
+
archives.push({ docsDir, routePrefix, label, ...sidebar ? { sidebarPath: sidebar } : {} });
|
|
4281
|
+
};
|
|
4282
|
+
for (const version of versions) add(`versioned_docs/${version}`, version.slice("version-".length), `Version ${version.slice("version-".length)}`, sidebarPath(version));
|
|
4283
|
+
for (const locale of directoryNames("i18n").filter((name) => /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*$/.test(name))) {
|
|
4284
|
+
const localizedRoot = `i18n/${locale}/docusaurus-plugin-content-docs`;
|
|
4285
|
+
const current = resolveWithin(projectRoot, `${localizedRoot}/current`);
|
|
4286
|
+
if (existsSync5(current) && lstatSync5(current).isDirectory()) add(`${localizedRoot}/current`, locale, `${locale} Documentation`);
|
|
4287
|
+
const localizedVersions = directoryNames(localizedRoot).filter((name) => /^version-[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name));
|
|
4288
|
+
for (const version of localizedVersions) {
|
|
4289
|
+
const path = resolveWithin(projectRoot, `${localizedRoot}/${version}`);
|
|
4290
|
+
if (existsSync5(path) && lstatSync5(path).isDirectory()) add(`${localizedRoot}/${version}`, `${locale}/${version.slice("version-".length)}`, `${locale} Version ${version.slice("version-".length)}`);
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (archives.length > 48) warnings.push({ code: "limit-reached", message: `Only the first 48 Docusaurus archived or localized docs roots were imported (${archives.length} found).` });
|
|
4294
|
+
return archives.slice(0, 48);
|
|
4295
|
+
}
|
|
3799
4296
|
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot, warnings) {
|
|
3800
4297
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
3801
4298
|
const plugins = [];
|
|
@@ -4022,10 +4519,10 @@ function detectRepositoryPlatform(repositoryDir, docsDir) {
|
|
|
4022
4519
|
}
|
|
4023
4520
|
function containsMarkdown(directory, depth = 0) {
|
|
4024
4521
|
if (depth > 4) return false;
|
|
4025
|
-
for (const entry of
|
|
4522
|
+
for (const entry of readdirSync2(directory, { withFileTypes: true })) {
|
|
4026
4523
|
if (isIgnoredDirectory(entry.name) || entry.isSymbolicLink()) continue;
|
|
4027
4524
|
const path = resolveWithin(directory, entry.name);
|
|
4028
|
-
if (entry.isFile() && [".md", ".mdx"].includes(
|
|
4525
|
+
if (entry.isFile() && [".md", ".mdx"].includes(extname5(entry.name).toLowerCase())) return true;
|
|
4029
4526
|
if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
|
|
4030
4527
|
}
|
|
4031
4528
|
return false;
|
|
@@ -4058,7 +4555,7 @@ function scanFiles(root, confinementRoot = root, warnings) {
|
|
|
4058
4555
|
visitedRealPaths.add(directoryReal);
|
|
4059
4556
|
let entries;
|
|
4060
4557
|
try {
|
|
4061
|
-
entries =
|
|
4558
|
+
entries = readdirSync2(directory, { withFileTypes: true });
|
|
4062
4559
|
} catch {
|
|
4063
4560
|
return;
|
|
4064
4561
|
}
|
|
@@ -4151,7 +4648,25 @@ function exactReferenceKey(value) {
|
|
|
4151
4648
|
return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
|
|
4152
4649
|
}
|
|
4153
4650
|
function findOpenApi(files) {
|
|
4154
|
-
return files.find((file) => OPENAPI_FILENAMES.has(
|
|
4651
|
+
return files.find((file) => OPENAPI_FILENAMES.has(basename3(file.relativePath).toLowerCase()) && classifyApiSpec(file.absolutePath) === "openapi") ?? null;
|
|
4652
|
+
}
|
|
4653
|
+
function classifyApiSpec(path) {
|
|
4654
|
+
const buffer = Buffer.alloc(64e3);
|
|
4655
|
+
let bytesRead = 0;
|
|
4656
|
+
try {
|
|
4657
|
+
const descriptor = openSync(path, "r");
|
|
4658
|
+
try {
|
|
4659
|
+
bytesRead = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
4660
|
+
} finally {
|
|
4661
|
+
closeSync(descriptor);
|
|
4662
|
+
}
|
|
4663
|
+
} catch {
|
|
4664
|
+
return "unknown";
|
|
4665
|
+
}
|
|
4666
|
+
const text = buffer.toString("utf8", 0, bytesRead);
|
|
4667
|
+
const topLevel = text.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
|
|
4668
|
+
if (!topLevel) return "unknown";
|
|
4669
|
+
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
4155
4670
|
}
|
|
4156
4671
|
function mintlifyTopLevelApiReferences(config) {
|
|
4157
4672
|
const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
|
|
@@ -4200,7 +4715,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
|
4200
4715
|
});
|
|
4201
4716
|
continue;
|
|
4202
4717
|
}
|
|
4203
|
-
specs.push({ filename:
|
|
4718
|
+
specs.push({ filename: basename3(match.relativePath), content: readFileSync5(match.absolutePath), tabLabel: reference.tabLabel });
|
|
4204
4719
|
}
|
|
4205
4720
|
return specs;
|
|
4206
4721
|
}
|
|
@@ -4267,6 +4782,12 @@ function findFernConfiguredOpenApi(fernRoot, repositoryDir, apiName, apiNameExpl
|
|
|
4267
4782
|
try {
|
|
4268
4783
|
const absolute = resolveWithinRoot(dir, specPath, repositoryDir);
|
|
4269
4784
|
if (!existsSync5(absolute) || !lstatSync5(absolute).isFile()) continue;
|
|
4785
|
+
const kind = classifyApiSpec(absolute);
|
|
4786
|
+
if (kind === "asyncapi") {
|
|
4787
|
+
unsupported.push({ kind: "asyncapi", path: specPath });
|
|
4788
|
+
continue;
|
|
4789
|
+
}
|
|
4790
|
+
if (kind !== "openapi") continue;
|
|
4270
4791
|
return { spec: { absolutePath: absolute, relativePath: relative5(repositoryDir, absolute).replace(/\\/g, "/") }, unsupported };
|
|
4271
4792
|
} catch {
|
|
4272
4793
|
warnings.push({
|
|
@@ -4304,9 +4825,11 @@ function staticSnippetProperties(attributes) {
|
|
|
4304
4825
|
}
|
|
4305
4826
|
return properties;
|
|
4306
4827
|
}
|
|
4307
|
-
function interpolateSnippet(snippet, attributes) {
|
|
4828
|
+
function interpolateSnippet(snippet, attributes, children) {
|
|
4308
4829
|
const properties = staticSnippetProperties(attributes);
|
|
4830
|
+
if (children !== void 0) properties.set("children", children.trim());
|
|
4309
4831
|
if (properties.size === 0) return snippet;
|
|
4832
|
+
const isJsxSnippet = /<[A-Za-z][A-Za-z0-9]*/.test(snippet);
|
|
4310
4833
|
let fence = "";
|
|
4311
4834
|
return snippet.split("\n").map((line) => {
|
|
4312
4835
|
const marker = line.match(/^\s*(`{3,}|~{3,})/)?.[1];
|
|
@@ -4317,11 +4840,234 @@ function interpolateSnippet(snippet, attributes) {
|
|
|
4317
4840
|
}
|
|
4318
4841
|
if (fence) return line;
|
|
4319
4842
|
return line.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (original, name) => {
|
|
4320
|
-
|
|
4843
|
+
const value = properties.get(name);
|
|
4844
|
+
if (value === void 0) return original;
|
|
4845
|
+
if (name === "children") return value;
|
|
4846
|
+
return isJsxSnippet ? `{${JSON.stringify(value)}}` : value;
|
|
4321
4847
|
});
|
|
4322
4848
|
}).join("\n");
|
|
4323
4849
|
}
|
|
4850
|
+
function snippetComponentBody(source, componentName) {
|
|
4851
|
+
const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
4852
|
+
for (const statement of parsed.statements) {
|
|
4853
|
+
if (!ts2.isVariableStatement(statement)) continue;
|
|
4854
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
4855
|
+
if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== componentName) continue;
|
|
4856
|
+
const initializer = declaration.initializer;
|
|
4857
|
+
if (!initializer || !ts2.isArrowFunction(initializer)) continue;
|
|
4858
|
+
const returned = ts2.isBlock(initializer.body) ? initializer.body.statements.find(ts2.isReturnStatement)?.expression : initializer.body;
|
|
4859
|
+
if (!returned) continue;
|
|
4860
|
+
let expression = returned;
|
|
4861
|
+
while (ts2.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
4862
|
+
if (ts2.isJsxElement(expression) || ts2.isJsxSelfClosingElement(expression) || ts2.isJsxFragment(expression)) {
|
|
4863
|
+
return expression.getText(parsed);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
return source;
|
|
4868
|
+
}
|
|
4869
|
+
function statefulSnippetDeclaration(source, componentName) {
|
|
4870
|
+
const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
4871
|
+
if (parsed.statements.length !== 1) return null;
|
|
4872
|
+
const statement = parsed.statements[0];
|
|
4873
|
+
if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword) || statement.declarationList.declarations.length !== 1) return null;
|
|
4874
|
+
const declaration = statement.declarationList.declarations[0];
|
|
4875
|
+
if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== componentName || !declaration.initializer || !ts2.isArrowFunction(declaration.initializer) || !ts2.isBlock(declaration.initializer.body)) return null;
|
|
4876
|
+
const body = declaration.initializer.body.statements;
|
|
4877
|
+
if (body.length < 2 || !body.some(ts2.isReturnStatement)) return null;
|
|
4878
|
+
return source.trim();
|
|
4879
|
+
}
|
|
4880
|
+
function staticNamedSnippetValues(source) {
|
|
4881
|
+
const values = /* @__PURE__ */ new Map();
|
|
4882
|
+
const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
4883
|
+
for (const statement of parsed.statements) {
|
|
4884
|
+
if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
|
|
4885
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
4886
|
+
if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
4887
|
+
const expression = declaration.initializer;
|
|
4888
|
+
if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
|
|
4889
|
+
values.set(declaration.name.text, JSON.stringify(expression.text));
|
|
4890
|
+
} else if (ts2.isNumericLiteral(expression)) {
|
|
4891
|
+
values.set(declaration.name.text, expression.text);
|
|
4892
|
+
} else if (expression.kind === ts2.SyntaxKind.TrueKeyword || expression.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
4893
|
+
values.set(declaration.name.text, expression.kind === ts2.SyntaxKind.TrueKeyword ? "true" : "false");
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
return values;
|
|
4898
|
+
}
|
|
4899
|
+
function hoistMdxImports(source) {
|
|
4900
|
+
const imports2 = /* @__PURE__ */ new Set();
|
|
4901
|
+
const withoutImports = replaceOutsideCode(source, (body) => body.replace(
|
|
4902
|
+
/^[ \t]*import\s+(?:\{[^}\n]+\}|[A-Za-z_$][\w$]*)\s+from\s+(['"])[^'"\n]+\1[ \t]*;?/gm,
|
|
4903
|
+
(statement) => {
|
|
4904
|
+
imports2.add(statement.trim().replace(/;?$/, ";"));
|
|
4905
|
+
return "";
|
|
4906
|
+
}
|
|
4907
|
+
));
|
|
4908
|
+
if (imports2.size === 0) return source;
|
|
4909
|
+
const frontmatter = withoutImports.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
4910
|
+
const prefix = frontmatter?.[0] ?? "";
|
|
4911
|
+
return `${prefix}${[...imports2].join("\n")}
|
|
4912
|
+
|
|
4913
|
+
${withoutImports.slice(prefix.length).replace(/^(?:\r?\n)*/, "")}`;
|
|
4914
|
+
}
|
|
4915
|
+
function fernSourceBasePath(config) {
|
|
4916
|
+
const instance = Array.isArray(config.instances) ? config.instances[0] : null;
|
|
4917
|
+
if (!instance || typeof instance !== "object" || Array.isArray(instance)) return "";
|
|
4918
|
+
const value = instance;
|
|
4919
|
+
const address = typeof value["custom-domain"] === "string" ? value["custom-domain"] : value.url;
|
|
4920
|
+
if (typeof address !== "string") return "";
|
|
4921
|
+
try {
|
|
4922
|
+
const path = new URL(/^https?:\/\//i.test(address) ? address : `https://${address}`).pathname.replace(/\/+$/, "");
|
|
4923
|
+
return path === "/" ? "" : path;
|
|
4924
|
+
} catch {
|
|
4925
|
+
return "";
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
function fernDefaultVersionPath(config) {
|
|
4929
|
+
if (!Array.isArray(config.versions)) return "";
|
|
4930
|
+
const versions = config.versions.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry));
|
|
4931
|
+
const chosen = versions.find((version) => version.default === true) ?? versions[0];
|
|
4932
|
+
return typeof chosen?.slug === "string" && /^[A-Za-z0-9_-]+$/.test(chosen.slug) ? `/${chosen.slug}` : "";
|
|
4933
|
+
}
|
|
4934
|
+
function fernSourceSiteUrl(config) {
|
|
4935
|
+
const instance = Array.isArray(config.instances) ? config.instances[0] : null;
|
|
4936
|
+
if (!instance || typeof instance !== "object" || Array.isArray(instance)) return void 0;
|
|
4937
|
+
const value = instance;
|
|
4938
|
+
const address = typeof value["custom-domain"] === "string" ? value["custom-domain"] : value.url;
|
|
4939
|
+
if (typeof address !== "string") return void 0;
|
|
4940
|
+
try {
|
|
4941
|
+
const url = new URL(/^https?:\/\//i.test(address) ? address : `https://${address}`);
|
|
4942
|
+
return `${url.origin}${fernSourceBasePath(config)}${fernDefaultVersionPath(config)}`;
|
|
4943
|
+
} catch {
|
|
4944
|
+
return void 0;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized) {
|
|
4948
|
+
const rewrite = (target) => {
|
|
4949
|
+
const path = target.split(/[?#]/, 1)[0].replace(/^\//, "").replace(/\/$/, "");
|
|
4950
|
+
if (!path || pageIds.has(path)) return target;
|
|
4951
|
+
try {
|
|
4952
|
+
const base = new URL(`${sourceSiteUrl}/`);
|
|
4953
|
+
const sourceLinkUrl = new URL(target.slice(1), base);
|
|
4954
|
+
if (sourceLinkUrl.origin !== base.origin) return target;
|
|
4955
|
+
const sourceLink = sourceLinkUrl.toString();
|
|
4956
|
+
externalized.add(target);
|
|
4957
|
+
return sourceLink;
|
|
4958
|
+
} catch {
|
|
4959
|
+
return target;
|
|
4960
|
+
}
|
|
4961
|
+
};
|
|
4962
|
+
return replaceOutsideCode(body, (text) => text.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
|
|
4963
|
+
}
|
|
4964
|
+
function stripFernBasePathFromLinks(body, basePath) {
|
|
4965
|
+
if (!basePath) return body;
|
|
4966
|
+
const escaped = basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4967
|
+
return replaceOutsideCode(body, (text) => text.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
|
|
4968
|
+
}
|
|
4969
|
+
function rewriteMintlifyMountedLinks(body, pageIds) {
|
|
4970
|
+
const rewrite = (target) => {
|
|
4971
|
+
const suffixAt = target.search(/[?#]/);
|
|
4972
|
+
const pathname = suffixAt < 0 ? target : target.slice(0, suffixAt);
|
|
4973
|
+
const suffix = suffixAt < 0 ? "" : target.slice(suffixAt);
|
|
4974
|
+
if (!pathname.startsWith("/") || pathname.startsWith("//") || pathname.includes("\\")) return target;
|
|
4975
|
+
const segments = pathname.slice(1).split("/");
|
|
4976
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return target;
|
|
4977
|
+
if (pageIds.has(segments.join("/"))) return target;
|
|
4978
|
+
for (let count = 1; count < segments.length && count <= 3; count++) {
|
|
4979
|
+
const candidate = segments.slice(count).join("/");
|
|
4980
|
+
if (pageIds.has(candidate)) return `/${candidate}${suffix}`;
|
|
4981
|
+
}
|
|
4982
|
+
return target;
|
|
4983
|
+
};
|
|
4984
|
+
return replaceOutsideCode(body, (text) => text.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
|
|
4985
|
+
}
|
|
4986
|
+
function preserveDocusaurusLinkedAnchors(pages) {
|
|
4987
|
+
const pageById = new Map(pages.map((page) => [page.id, page]));
|
|
4988
|
+
const requested = /* @__PURE__ */ new Map();
|
|
4989
|
+
const crossPageRequests = /* @__PURE__ */ new Set();
|
|
4990
|
+
const addTarget = (target, currentId) => {
|
|
4991
|
+
const hash2 = target.indexOf("#");
|
|
4992
|
+
if (hash2 < 0) return;
|
|
4993
|
+
const fragment = target.slice(hash2 + 1);
|
|
4994
|
+
if (!/^[A-Za-z0-9_.:-]{1,120}$/.test(fragment)) return;
|
|
4995
|
+
const path = target.slice(0, hash2).split("?")[0];
|
|
4996
|
+
const id = target.startsWith("#") ? currentId : path.startsWith("/") && !path.startsWith("//") ? path.replace(/^\//, "").replace(/\/$/, "") || "introduction" : "";
|
|
4997
|
+
if (!pageById.has(id)) return;
|
|
4998
|
+
if (!requested.has(id)) requested.set(id, /* @__PURE__ */ new Set());
|
|
4999
|
+
requested.get(id).add(fragment);
|
|
5000
|
+
if (id !== currentId) crossPageRequests.add(`${id}#${fragment}`);
|
|
5001
|
+
};
|
|
5002
|
+
for (const page of pages) {
|
|
5003
|
+
replaceOutsideCode(page.body, (text) => {
|
|
5004
|
+
for (const match of text.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
|
|
5005
|
+
for (const match of text.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
|
|
5006
|
+
return text;
|
|
5007
|
+
});
|
|
5008
|
+
}
|
|
5009
|
+
const comparable = (value) => value.replace(/&(?:lt|gt|amp|quot);/g, " ").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
5010
|
+
for (const page of pages) {
|
|
5011
|
+
const fragments = requested.get(page.id);
|
|
5012
|
+
if (!fragments?.size) continue;
|
|
5013
|
+
const lines = page.body.split("\n");
|
|
5014
|
+
let fence;
|
|
5015
|
+
const tableCells = [];
|
|
5016
|
+
const headings = lines.map((line, index) => {
|
|
5017
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
5018
|
+
if (marker) {
|
|
5019
|
+
if (!fence) fence = marker[1];
|
|
5020
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && /^\s*$/.test(line.slice(marker[0].length))) fence = void 0;
|
|
5021
|
+
return null;
|
|
5022
|
+
}
|
|
5023
|
+
if (fence) return null;
|
|
5024
|
+
const tableCell = line.match(/^ {0,3}\|\s*([^|]+?)\s*\|/);
|
|
5025
|
+
if (tableCell && !/^[:\s-]+$/.test(tableCell[1])) tableCells.push({ index, text: tableCell[1] });
|
|
5026
|
+
const match = line.match(/^ {0,3}#{1,6}\s+(.+?)\s*#*\s*$/);
|
|
5027
|
+
return match ? { index, text: match[1] } : null;
|
|
5028
|
+
}).filter((entry) => entry !== null);
|
|
5029
|
+
const existing = /* @__PURE__ */ new Set();
|
|
5030
|
+
replaceOutsideCode(page.body, (text) => {
|
|
5031
|
+
for (const match of text.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
|
|
5032
|
+
return text;
|
|
5033
|
+
});
|
|
5034
|
+
const candidateIndexes = /* @__PURE__ */ new Map();
|
|
5035
|
+
for (const heading of headings) {
|
|
5036
|
+
const key = comparable(heading.text);
|
|
5037
|
+
candidateIndexes.set(key, [...candidateIndexes.get(key) ?? [], { index: heading.index, kind: "heading" }]);
|
|
5038
|
+
}
|
|
5039
|
+
for (const cell of tableCells) {
|
|
5040
|
+
const key = comparable(cell.text);
|
|
5041
|
+
candidateIndexes.set(key, [...candidateIndexes.get(key) ?? [], { index: cell.index, kind: "table" }]);
|
|
5042
|
+
}
|
|
5043
|
+
for (const heading of headings) existing.add(heading.text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""));
|
|
5044
|
+
const headingAliases = /* @__PURE__ */ new Map();
|
|
5045
|
+
const tableAliases = /* @__PURE__ */ new Map();
|
|
5046
|
+
for (const fragment of fragments) {
|
|
5047
|
+
if (existing.has(fragment)) continue;
|
|
5048
|
+
const matches = candidateIndexes.get(comparable(fragment)) ?? [];
|
|
5049
|
+
let match = matches.length === 1 ? matches[0] : void 0;
|
|
5050
|
+
if (!match && matches.length > 1 && !crossPageRequests.has(`${page.id}#${fragment}`)) {
|
|
5051
|
+
const escaped = fragment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5052
|
+
const linkPattern = new RegExp(`\\]\\(#${escaped}\\)|\\b(?:href|to)=["']#${escaped}["']`);
|
|
5053
|
+
const linkIndexes = lines.flatMap((line, index2) => linkPattern.test(line) ? [index2] : []);
|
|
5054
|
+
const selections = linkIndexes.map((index2) => matches.filter((candidate) => candidate.index < index2).at(-1));
|
|
5055
|
+
if (selections.length && selections.every((candidate) => candidate?.index === selections[0]?.index)) match = selections[0];
|
|
5056
|
+
}
|
|
5057
|
+
if (!match) continue;
|
|
5058
|
+
const { index, kind } = match;
|
|
5059
|
+
const aliases = kind === "heading" ? headingAliases : tableAliases;
|
|
5060
|
+
aliases.set(index, [...aliases.get(index) ?? [], fragment]);
|
|
5061
|
+
existing.add(fragment);
|
|
5062
|
+
}
|
|
5063
|
+
if (headingAliases.size || tableAliases.size) page.body = lines.flatMap((line, index) => [
|
|
5064
|
+
...(headingAliases.get(index) ?? []).map((fragment) => `<a id="${fragment}"></a>`),
|
|
5065
|
+
tableAliases.has(index) ? line.replace(/^( {0,3}\|\s*)/, (prefix) => `${prefix}${tableAliases.get(index).map((fragment) => `<a id="${fragment}"></a>`).join("")}`) : line
|
|
5066
|
+
]).join("\n");
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
4324
5069
|
function resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot) {
|
|
5070
|
+
sourcePath = sourcePath.replace(/\\(?=_)/g, "");
|
|
4325
5071
|
const repositoryRelative = sourcePath.startsWith("@site/") ? relative5(repositoryRoot, resolveWithin(siteRoot, sourcePath.slice("@site/".length))).replace(/\\/g, "/") : sourcePath.startsWith("/") ? relative5(repositoryRoot, resolveWithin(siteRoot, sourcePath.replace(/^\/+/, ""))).replace(/\\/g, "/") : relative5(repositoryRoot, resolvePath(dirname4(currentFile), sourcePath)).replace(/\\/g, "/");
|
|
4326
5072
|
const candidate = resolveWithin(repositoryRoot, repositoryRelative);
|
|
4327
5073
|
resolveWithin(repositoryRoot, relative5(repositoryRoot, candidate));
|
|
@@ -4332,12 +5078,12 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
|
4332
5078
|
for (const file of files) {
|
|
4333
5079
|
const segments = file.relativePath.split("/");
|
|
4334
5080
|
if (!segments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()))) continue;
|
|
4335
|
-
const basenameWithoutExtension =
|
|
5081
|
+
const basenameWithoutExtension = basename3(file.relativePath).replace(/\.mdx?$/i, "");
|
|
4336
5082
|
const conventionalAlias = basenameWithoutExtension.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
4337
5083
|
if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
|
|
4338
5084
|
}
|
|
4339
5085
|
for (const file of files) {
|
|
4340
|
-
if (![".md", ".mdx"].includes(
|
|
5086
|
+
if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) continue;
|
|
4341
5087
|
const raw = readFileSync5(file.absolutePath, "utf8");
|
|
4342
5088
|
for (const match of raw.matchAll(SNIPPET_IMPORT_PATTERN)) {
|
|
4343
5089
|
try {
|
|
@@ -4365,7 +5111,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
|
4365
5111
|
} catch {
|
|
4366
5112
|
return null;
|
|
4367
5113
|
}
|
|
4368
|
-
if (!ASSET_EXTENSIONS.has(
|
|
5114
|
+
if (!ASSET_EXTENSIONS.has(extname5(decodedPath).toLowerCase())) return null;
|
|
4369
5115
|
try {
|
|
4370
5116
|
const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(dirname4(currentFile), decodedPath);
|
|
4371
5117
|
const siteRelative = relative5(siteRoot, candidate).replace(/\\/g, "/");
|
|
@@ -4381,15 +5127,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
|
4381
5127
|
}
|
|
4382
5128
|
}
|
|
4383
5129
|
function rewriteFernIdRenameLinks(body, renames) {
|
|
4384
|
-
|
|
4385
|
-
return body.split("\n").map((line) => {
|
|
4386
|
-
const fence = line.match(/^\s*(`{3,}|~{3,})/);
|
|
4387
|
-
if (fence) {
|
|
4388
|
-
if (!codeFence) codeFence = fence[1][0];
|
|
4389
|
-
else if (fence[1][0] === codeFence) codeFence = null;
|
|
4390
|
-
return line;
|
|
4391
|
-
}
|
|
4392
|
-
if (codeFence) return line;
|
|
5130
|
+
return replaceOutsideCode(body, (text) => {
|
|
4393
5131
|
const rewriteTarget = (target) => {
|
|
4394
5132
|
const suffixIndex = target.search(/[?#]/);
|
|
4395
5133
|
const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "");
|
|
@@ -4397,8 +5135,8 @@ function rewriteFernIdRenameLinks(body, renames) {
|
|
|
4397
5135
|
const renamed = renames.get(path);
|
|
4398
5136
|
return renamed ? `/${renamed}${suffix}` : target;
|
|
4399
5137
|
};
|
|
4400
|
-
return
|
|
4401
|
-
})
|
|
5138
|
+
return text.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
|
|
5139
|
+
});
|
|
4402
5140
|
}
|
|
4403
5141
|
function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced) {
|
|
4404
5142
|
return body.replace(/(!?\[[^\]]*\]\()(<[^>]+>|[^)\s]+)([^)]*\))/g, (original, opening, destination, closing) => {
|
|
@@ -4428,8 +5166,10 @@ function locallyDeclaredNames(raw) {
|
|
|
4428
5166
|
}
|
|
4429
5167
|
function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map()) {
|
|
4430
5168
|
if (depth >= 8) return raw;
|
|
5169
|
+
raw = normalizeIndentedFences(raw);
|
|
4431
5170
|
const snippets = /* @__PURE__ */ new Map();
|
|
4432
|
-
const
|
|
5171
|
+
const preservedDeclarations = /* @__PURE__ */ new Map();
|
|
5172
|
+
let withoutImports = replaceOutsideCode(raw, (source) => source.replace(
|
|
4433
5173
|
SNIPPET_IMPORT_PATTERN,
|
|
4434
5174
|
(_statement, namedComponent, defaultComponent, sourcePath) => {
|
|
4435
5175
|
const componentName = namedComponent ?? defaultComponent;
|
|
@@ -4445,7 +5185,9 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
4445
5185
|
siteRoot,
|
|
4446
5186
|
globalAliases
|
|
4447
5187
|
);
|
|
4448
|
-
|
|
5188
|
+
const declaration = statefulSnippetDeclaration(nested, componentName);
|
|
5189
|
+
if (declaration) preservedDeclarations.set(componentName, declaration);
|
|
5190
|
+
else snippets.set(componentName, snippetComponentBody(nested, componentName));
|
|
4449
5191
|
return "";
|
|
4450
5192
|
} catch {
|
|
4451
5193
|
warnings.push({
|
|
@@ -4457,7 +5199,40 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
4457
5199
|
return "";
|
|
4458
5200
|
}
|
|
4459
5201
|
}
|
|
4460
|
-
);
|
|
5202
|
+
));
|
|
5203
|
+
withoutImports = replaceOutsideCode(withoutImports, (source) => source.replace(
|
|
5204
|
+
SNIPPET_VALUE_IMPORT_PATTERN,
|
|
5205
|
+
(statement, names, sourcePath) => {
|
|
5206
|
+
const bindings = names.split(",").map((name) => name.trim()).filter(Boolean).map((name) => {
|
|
5207
|
+
const parsed = name.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
|
|
5208
|
+
return parsed ? { exported: parsed[1], local: parsed[2] ?? parsed[1] } : null;
|
|
5209
|
+
});
|
|
5210
|
+
if (bindings.length === 0 || bindings.some((binding) => !binding)) return statement;
|
|
5211
|
+
try {
|
|
5212
|
+
const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
|
|
5213
|
+
if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) return statement;
|
|
5214
|
+
const snippetSource = readFileSync5(candidate, "utf8");
|
|
5215
|
+
const values = staticNamedSnippetValues(snippetSource);
|
|
5216
|
+
const declarations = [];
|
|
5217
|
+
const components = [];
|
|
5218
|
+
for (const binding of bindings) {
|
|
5219
|
+
const value = values.get(binding.exported);
|
|
5220
|
+
if (value !== void 0) {
|
|
5221
|
+
declarations.push(`export const ${binding.local} = ${value};`);
|
|
5222
|
+
continue;
|
|
5223
|
+
}
|
|
5224
|
+
if (!/^[A-Z]/.test(binding.exported)) return statement;
|
|
5225
|
+
const body = snippetComponentBody(snippetSource, binding.exported);
|
|
5226
|
+
if (body === snippetSource) return statement;
|
|
5227
|
+
components.push([binding.local, body]);
|
|
5228
|
+
}
|
|
5229
|
+
for (const [name, body] of components) snippets.set(name, body);
|
|
5230
|
+
return declarations.join("\n");
|
|
5231
|
+
} catch {
|
|
5232
|
+
return statement;
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
));
|
|
4461
5236
|
const localNames = locallyDeclaredNames(withoutImports);
|
|
4462
5237
|
for (const [componentName, candidate] of globalAliases) {
|
|
4463
5238
|
if (snippets.has(componentName) || localNames.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
|
|
@@ -4470,14 +5245,16 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
4470
5245
|
siteRoot,
|
|
4471
5246
|
globalAliases
|
|
4472
5247
|
);
|
|
4473
|
-
|
|
5248
|
+
const declaration = statefulSnippetDeclaration(nested, componentName);
|
|
5249
|
+
if (declaration) preservedDeclarations.set(componentName, declaration);
|
|
5250
|
+
else snippets.set(componentName, snippetComponentBody(nested, componentName));
|
|
4474
5251
|
}
|
|
4475
5252
|
let result = withoutImports;
|
|
4476
5253
|
for (const [componentName, snippet] of snippets) {
|
|
4477
5254
|
result = result.replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)\\s*/>`, "g"), (_tag, attributes) => {
|
|
4478
5255
|
return interpolateSnippet(snippet, attributes);
|
|
4479
|
-
}).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>(
|
|
4480
|
-
return interpolateSnippet(snippet, attributes);
|
|
5256
|
+
}).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>([\\s\\S]*?)<\\/${componentName}>`, "g"), (_tag, attributes, children) => {
|
|
5257
|
+
return interpolateSnippet(snippet, attributes, children);
|
|
4481
5258
|
});
|
|
4482
5259
|
}
|
|
4483
5260
|
result = result.replace(SNIPPET_TAG_PATTERN, (_tag, doubleQuoted, singleQuoted) => {
|
|
@@ -4503,13 +5280,25 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
4503
5280
|
return `{/* Missing snippet: ${filePath} */}`;
|
|
4504
5281
|
}
|
|
4505
5282
|
});
|
|
4506
|
-
|
|
5283
|
+
if (preservedDeclarations.size > 0) {
|
|
5284
|
+
const frontmatter = result.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
5285
|
+
const prefix = frontmatter?.[0] ?? "";
|
|
5286
|
+
result = `${prefix}${[...preservedDeclarations.values()].join("\n\n")}
|
|
5287
|
+
|
|
5288
|
+
${result.slice(prefix.length)}`;
|
|
5289
|
+
}
|
|
5290
|
+
return depth === 0 ? hoistMdxImports(result) : result;
|
|
4507
5291
|
}
|
|
4508
5292
|
function injectOpenApiSpecs(config, specs) {
|
|
4509
5293
|
let tabs = config.tabs.map((tab) => ({ ...tab }));
|
|
4510
5294
|
for (const spec of specs) {
|
|
4511
5295
|
const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
|
|
4512
|
-
if (apiTab) apiTab.api = {
|
|
5296
|
+
if (apiTab) apiTab.api = {
|
|
5297
|
+
source: `/${spec.filename}`,
|
|
5298
|
+
// An API-only tab needs generated endpoint navigation; an authored
|
|
5299
|
+
// page tab keeps its own groups alongside the bound spec.
|
|
5300
|
+
...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
|
|
5301
|
+
};
|
|
4513
5302
|
else tabs = [...tabs, { tab: spec.tabLabel ?? "API Reference", api: { source: `/${spec.filename}` } }];
|
|
4514
5303
|
}
|
|
4515
5304
|
return { ...config, tabs };
|
|
@@ -4547,30 +5336,6 @@ function migrateRepository(options) {
|
|
|
4547
5336
|
}
|
|
4548
5337
|
return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
|
|
4549
5338
|
})();
|
|
4550
|
-
if (platform === "docusaurus" && docusaurusProjectRoot) {
|
|
4551
|
-
for (const [directory, label] of [
|
|
4552
|
-
["versioned_docs", "versioned docs"],
|
|
4553
|
-
["versioned_sidebars", "versioned sidebars"]
|
|
4554
|
-
]) {
|
|
4555
|
-
const path = resolveWithin(docusaurusProjectRoot, directory);
|
|
4556
|
-
if (!existsSync5(path) || !lstatSync5(path).isDirectory()) continue;
|
|
4557
|
-
const versions = readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
4558
|
-
if (versions.length) warnings.push({
|
|
4559
|
-
code: "unsupported-config",
|
|
4560
|
-
message: `Docusaurus ${label} were skipped; only current docs were imported. Skipped: ${versions.join(", ")}.`,
|
|
4561
|
-
source: directory
|
|
4562
|
-
});
|
|
4563
|
-
}
|
|
4564
|
-
const i18nPath = resolveWithin(docusaurusProjectRoot, "i18n");
|
|
4565
|
-
if (existsSync5(i18nPath) && lstatSync5(i18nPath).isDirectory()) {
|
|
4566
|
-
const locales = readdirSync(i18nPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
4567
|
-
if (locales.length) warnings.push({
|
|
4568
|
-
code: "unsupported-config",
|
|
4569
|
-
message: `Docusaurus localized content was skipped; only the default locale was imported. Skipped locales: ${locales.join(", ")}.`,
|
|
4570
|
-
source: "i18n"
|
|
4571
|
-
});
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
5339
|
const componentRoot = mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir;
|
|
4575
5340
|
const componentMigrator = platform === "mintlify" || platform === "docusaurus" || platform === "fern" ? createComponentMigrator(componentRoot, repositoryDir, warnings, componentSourceIdentity(options.sourceUrl, repositoryDir, componentRoot)) : void 0;
|
|
4576
5341
|
let docsConfig = { tabs: [] };
|
|
@@ -4580,7 +5345,11 @@ function migrateRepository(options) {
|
|
|
4580
5345
|
let docusaurusSidebars = null;
|
|
4581
5346
|
let mintlifyConfig = null;
|
|
4582
5347
|
let fernRawConfig = null;
|
|
5348
|
+
let fernBasePath = "";
|
|
5349
|
+
let fernVersionPath = "";
|
|
4583
5350
|
let fernApiSections = [];
|
|
5351
|
+
const fernReferencedPaths = /* @__PURE__ */ new Set();
|
|
5352
|
+
const fernSourceLinkAliases = /* @__PURE__ */ new Map();
|
|
4584
5353
|
if (platform === "mintlify") {
|
|
4585
5354
|
try {
|
|
4586
5355
|
const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
|
|
@@ -4609,11 +5378,23 @@ function migrateRepository(options) {
|
|
|
4609
5378
|
const fernConfig = readFernConfig(fernProjectRoot);
|
|
4610
5379
|
if (fernConfig) {
|
|
4611
5380
|
fernRawConfig = fernConfig.config;
|
|
4612
|
-
|
|
5381
|
+
fernBasePath = fernSourceBasePath(fernConfig.config);
|
|
5382
|
+
fernVersionPath = fernDefaultVersionPath(fernConfig.config);
|
|
5383
|
+
const projected = projectFernNavigation({ config: fernConfig.config, fernRoot: fernProjectRoot, repositoryRoot: repositoryDir });
|
|
4613
5384
|
docsConfig = projected.docsConfig;
|
|
4614
5385
|
warnings.push(...projected.warnings);
|
|
4615
5386
|
fernApiSections = projected.apiSections;
|
|
4616
5387
|
for (const [index, descriptor] of projected.descriptors.entries()) {
|
|
5388
|
+
fernReferencedPaths.add(descriptor.sourcePath);
|
|
5389
|
+
const sourceTail = descriptor.sourcePath.match(/(?:^|\/)pages\/(.+)\.mdx?$/i)?.[1]?.replace(/\/index$/i, "");
|
|
5390
|
+
if (sourceTail) {
|
|
5391
|
+
const routeSegments = descriptor.navigationId.split("/");
|
|
5392
|
+
const sourceSegments = sourceTail.split("/");
|
|
5393
|
+
if (routeSegments.length === sourceSegments.length + 1) {
|
|
5394
|
+
const alias = `${routeSegments[0]}/${sourceTail}`;
|
|
5395
|
+
if (alias !== descriptor.navigationId && !fernSourceLinkAliases.has(alias)) fernSourceLinkAliases.set(alias, descriptor.navigationId);
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
4617
5398
|
const key = normalizedReferenceKey(descriptor.sourcePath);
|
|
4618
5399
|
if (!referenceMap.has(key)) referenceMap.set(key, { navigationId: descriptor.navigationId });
|
|
4619
5400
|
const exactKey = exactReferenceKey(descriptor.sourcePath);
|
|
@@ -4630,7 +5411,7 @@ function migrateRepository(options) {
|
|
|
4630
5411
|
}
|
|
4631
5412
|
if (platform === "docusaurus") {
|
|
4632
5413
|
try {
|
|
4633
|
-
docusaurusSidebars = options.docusaurusSkipSidebar || !docusaurusProjectRoot ? null : readDocusaurusSidebars(docusaurusProjectRoot);
|
|
5414
|
+
docusaurusSidebars = options.docusaurusSkipSidebar || !docusaurusProjectRoot ? null : readDocusaurusSidebars(docusaurusProjectRoot, options.docusaurusSidebarPath);
|
|
4634
5415
|
} catch (error) {
|
|
4635
5416
|
warnings.push({
|
|
4636
5417
|
code: "unsupported-config",
|
|
@@ -4644,6 +5425,19 @@ function migrateRepository(options) {
|
|
|
4644
5425
|
}
|
|
4645
5426
|
const mintignoreMatcher = platform === "mintlify" && mintlifyProjectRoot ? readMintignoreMatcher(mintlifyProjectRoot) : null;
|
|
4646
5427
|
const files = mintignoreMatcher ? scanFiles(contentRoot, repositoryDir, warnings).filter((file) => !mintignoreMatcher.ignores(file.relativePath)) : scanFiles(contentRoot, repositoryDir, warnings);
|
|
5428
|
+
if (platform === "fern" && fernProjectRoot) {
|
|
5429
|
+
const scanned = new Set(files.map((file) => file.absolutePath));
|
|
5430
|
+
for (const sourcePath of fernReferencedPaths) {
|
|
5431
|
+
try {
|
|
5432
|
+
const absolutePath = resolveWithinRoot(fernProjectRoot, sourcePath, repositoryDir);
|
|
5433
|
+
if (scanned.has(absolutePath) || !existsSync5(absolutePath) || !lstatSync5(absolutePath).isFile()) continue;
|
|
5434
|
+
files.push({ absolutePath, relativePath: sourcePath });
|
|
5435
|
+
scanned.add(absolutePath);
|
|
5436
|
+
} catch {
|
|
5437
|
+
warnings.push({ code: "missing-page", message: "A Fern navigation path could not be read within this repository.", source: sourcePath });
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
4647
5441
|
const pages = [];
|
|
4648
5442
|
const assets = [];
|
|
4649
5443
|
const referencedAssetPaths = /* @__PURE__ */ new Map();
|
|
@@ -4673,16 +5467,16 @@ function migrateRepository(options) {
|
|
|
4673
5467
|
continue;
|
|
4674
5468
|
}
|
|
4675
5469
|
const sourceSegments = file.relativePath.split("/");
|
|
4676
|
-
if (sourceSegments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase())) || platform === "docusaurus" &&
|
|
5470
|
+
if (sourceSegments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase())) || platform === "docusaurus" && basename3(file.relativePath).startsWith("_")) {
|
|
4677
5471
|
skipped++;
|
|
4678
5472
|
continue;
|
|
4679
5473
|
}
|
|
4680
|
-
const rootFilename = !file.relativePath.includes("/") ?
|
|
5474
|
+
const rootFilename = !file.relativePath.includes("/") ? basename3(file.relativePath).toLowerCase() : "";
|
|
4681
5475
|
if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename) && !exactReferenceMap.has(exactReferenceKey(file.relativePath))) {
|
|
4682
5476
|
skipped++;
|
|
4683
5477
|
continue;
|
|
4684
5478
|
}
|
|
4685
|
-
if (![".md", ".mdx"].includes(
|
|
5479
|
+
if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) {
|
|
4686
5480
|
skipped++;
|
|
4687
5481
|
warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
|
|
4688
5482
|
continue;
|
|
@@ -4802,6 +5596,8 @@ function migrateRepository(options) {
|
|
|
4802
5596
|
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, fernProjectRoot, (assetPath) => {
|
|
4803
5597
|
addAssetReference(assetPath, file.relativePath);
|
|
4804
5598
|
});
|
|
5599
|
+
page.body = stripFernBasePathFromLinks(page.body, fernBasePath);
|
|
5600
|
+
page.body = stripFernBasePathFromLinks(page.body, fernVersionPath);
|
|
4805
5601
|
}
|
|
4806
5602
|
if (platform === "mintlify" || platform === "docusaurus" || platform === "fern") {
|
|
4807
5603
|
page.body = replaceLinkWithAnchor(page.body);
|
|
@@ -4882,7 +5678,7 @@ function migrateRepository(options) {
|
|
|
4882
5678
|
const assetCandidates = [];
|
|
4883
5679
|
for (const file of [...files, ...repositoryAssets]) {
|
|
4884
5680
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
4885
|
-
if (!ASSET_EXTENSIONS.has(
|
|
5681
|
+
if (!ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase())) continue;
|
|
4886
5682
|
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
|
|
4887
5683
|
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
4888
5684
|
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
@@ -4938,10 +5734,19 @@ function migrateRepository(options) {
|
|
|
4938
5734
|
const descriptorByNavigationId = new Map(
|
|
4939
5735
|
docusaurusDescriptors.map((descriptor) => [descriptor.navigationId, descriptor])
|
|
4940
5736
|
);
|
|
5737
|
+
const sourceOrigin = docusaurusProjectRoot ? readDocusaurusSiteOrigin(docusaurusProjectRoot) : void 0;
|
|
5738
|
+
const externalizedLinks = /* @__PURE__ */ new Set();
|
|
4941
5739
|
for (const page of pages) {
|
|
4942
5740
|
const descriptor = descriptorByNavigationId.get(page.navigationId);
|
|
4943
|
-
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors
|
|
5741
|
+
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors, {
|
|
5742
|
+
sourceOrigin,
|
|
5743
|
+
onExternalLink: (target) => externalizedLinks.add(target)
|
|
5744
|
+
});
|
|
4944
5745
|
}
|
|
5746
|
+
if (externalizedLinks.size > 0) warnings.push({
|
|
5747
|
+
code: "unsupported-config",
|
|
5748
|
+
message: `${externalizedLinks.size} link(s) outside the imported Docusaurus docs still point to ${sourceOrigin}; migrate those sections separately if this site must be fully independent.`
|
|
5749
|
+
});
|
|
4945
5750
|
if (docusaurusProjectRoot && !options.docusaurusSkipRedirects) {
|
|
4946
5751
|
const redirects = readDocusaurusRedirects(docusaurusProjectRoot, warnings);
|
|
4947
5752
|
if (redirects.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...redirects] };
|
|
@@ -4972,6 +5777,11 @@ function migrateRepository(options) {
|
|
|
4972
5777
|
} : {}
|
|
4973
5778
|
};
|
|
4974
5779
|
}
|
|
5780
|
+
if (platform === "fern" && fernSourceLinkAliases.size > 0) {
|
|
5781
|
+
const imported = new Set(pages.map((page) => page.id));
|
|
5782
|
+
const aliases = new Map([...fernSourceLinkAliases].map(([source, target]) => [source, fernIdRenames.get(target) ?? target]).filter(([, target]) => imported.has(target)));
|
|
5783
|
+
for (const page of pages) page.body = rewriteFernIdRenameLinks(page.body, aliases);
|
|
5784
|
+
}
|
|
4975
5785
|
if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
|
|
4976
5786
|
if (platform === "fern" && fernProjectRoot) {
|
|
4977
5787
|
const sections = fernApiSections.length > 0 ? fernApiSections : [{ nameExplicit: false }];
|
|
@@ -4981,7 +5791,7 @@ function migrateRepository(options) {
|
|
|
4981
5791
|
const resolution = findFernConfiguredOpenApi(fernProjectRoot, repositoryDir, section.name, section.nameExplicit, warnings);
|
|
4982
5792
|
const spec = resolution.spec ?? (!section.nameExplicit ? findOpenApi(files) : null);
|
|
4983
5793
|
if (spec) {
|
|
4984
|
-
let filename =
|
|
5794
|
+
let filename = basename3(spec.relativePath);
|
|
4985
5795
|
const existingSource = specFilenameSources.get(filename);
|
|
4986
5796
|
if (existingSource && existingSource !== spec.absolutePath) {
|
|
4987
5797
|
const prefix = section.name ? `${section.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")}-` : `${specFilenameSources.size + 1}-`;
|
|
@@ -5032,7 +5842,7 @@ function migrateRepository(options) {
|
|
|
5032
5842
|
} else {
|
|
5033
5843
|
const fallback = findOpenApi(files);
|
|
5034
5844
|
if (fallback) {
|
|
5035
|
-
const filename =
|
|
5845
|
+
const filename = basename3(fallback.relativePath);
|
|
5036
5846
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
5037
5847
|
assets.push({ path: filename, content: readFileSync5(fallback.absolutePath) });
|
|
5038
5848
|
}
|
|
@@ -5045,9 +5855,25 @@ function migrateRepository(options) {
|
|
|
5045
5855
|
const aliases = routeAliases.filter((redirect) => !sources.has(redirect.source));
|
|
5046
5856
|
if (aliases.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...aliases] };
|
|
5047
5857
|
docsConfig = addMintlifyHomepageRedirects(addMintlifyDirectoryRedirects(docsConfig, pages), pages);
|
|
5858
|
+
const reachablePaths = /* @__PURE__ */ new Set([
|
|
5859
|
+
...pages.map((page) => page.id),
|
|
5860
|
+
...(docsConfig.redirects ?? []).map((redirect) => redirect.source.replace(/^\//, ""))
|
|
5861
|
+
]);
|
|
5862
|
+
for (const page of pages) page.body = rewriteMintlifyMountedLinks(page.body, reachablePaths);
|
|
5048
5863
|
}
|
|
5049
5864
|
if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
|
|
5050
|
-
|
|
5865
|
+
const additionalRoots = [
|
|
5866
|
+
...additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot, warnings).map((plugin) => ({
|
|
5867
|
+
...plugin,
|
|
5868
|
+
label: plugin.routePrefix.split(/[-_/]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")
|
|
5869
|
+
})),
|
|
5870
|
+
...docusaurusArchiveRoots(docusaurusProjectRoot, repositoryDir, warnings)
|
|
5871
|
+
];
|
|
5872
|
+
for (const plugin of additionalRoots) {
|
|
5873
|
+
if (discovered >= MAX_SOURCE_FILES) {
|
|
5874
|
+
warnings.push({ code: "limit-reached", message: `Docusaurus docs import stopped at the ${MAX_SOURCE_FILES}-file discovery budget.` });
|
|
5875
|
+
break;
|
|
5876
|
+
}
|
|
5051
5877
|
if (plugin.docsDir === configuredDocsDir) continue;
|
|
5052
5878
|
const pluginBundle = migrateRepository({
|
|
5053
5879
|
...options,
|
|
@@ -5055,12 +5881,17 @@ function migrateRepository(options) {
|
|
|
5055
5881
|
platform: "docusaurus",
|
|
5056
5882
|
docusaurusRoutePrefix: plugin.routePrefix,
|
|
5057
5883
|
docusaurusSkipPlugins: true,
|
|
5058
|
-
docusaurusSkipSidebar:
|
|
5884
|
+
docusaurusSkipSidebar: !plugin.sidebarPath,
|
|
5885
|
+
docusaurusSidebarPath: plugin.sidebarPath,
|
|
5059
5886
|
docusaurusSkipAssets: true,
|
|
5060
5887
|
docusaurusSkipRedirects: true
|
|
5061
5888
|
});
|
|
5062
5889
|
for (const page of pluginBundle.pages) {
|
|
5063
|
-
if (seenPageIds.has(page.id))
|
|
5890
|
+
if (seenPageIds.has(page.id)) {
|
|
5891
|
+
skipped++;
|
|
5892
|
+
warnings.push({ code: "collision", message: `Docusaurus docs roots both map to ${page.id}; the first page was kept.`, source: page.source });
|
|
5893
|
+
continue;
|
|
5894
|
+
}
|
|
5064
5895
|
seenPageIds.add(page.id);
|
|
5065
5896
|
pages.push(page);
|
|
5066
5897
|
}
|
|
@@ -5070,7 +5901,7 @@ function migrateRepository(options) {
|
|
|
5070
5901
|
assetPaths.add(asset.path);
|
|
5071
5902
|
assets.push(asset);
|
|
5072
5903
|
}
|
|
5073
|
-
const pluginLabel = plugin.
|
|
5904
|
+
const pluginLabel = plugin.label;
|
|
5074
5905
|
docsConfig.tabs.push(...pluginBundle.docsConfig.tabs.map((tab, index) => ({
|
|
5075
5906
|
...tab,
|
|
5076
5907
|
tab: index === 0 ? pluginLabel : `${pluginLabel}: ${tab.tab}`
|
|
@@ -5081,6 +5912,41 @@ function migrateRepository(options) {
|
|
|
5081
5912
|
}
|
|
5082
5913
|
}
|
|
5083
5914
|
docsConfig = pruneMissingNavigationPages(docsConfig, new Set(pages.map((page) => page.navigationId)));
|
|
5915
|
+
if (platform === "docusaurus") preserveDocusaurusLinkedAnchors(pages);
|
|
5916
|
+
if (platform === "fern" && fernRawConfig) {
|
|
5917
|
+
const sourceSiteUrl = fernSourceSiteUrl(fernRawConfig);
|
|
5918
|
+
if (sourceSiteUrl) {
|
|
5919
|
+
const externalized = /* @__PURE__ */ new Set();
|
|
5920
|
+
const pageIds = new Set(pages.map((page) => page.id));
|
|
5921
|
+
for (const page of pages) page.body = externalizeMissingFernLinks(page.body, pageIds, sourceSiteUrl, externalized);
|
|
5922
|
+
if (externalized.size > 0) warnings.push({
|
|
5923
|
+
code: "unsupported-config",
|
|
5924
|
+
message: `${externalized.size} unresolved Fern link(s) still point to ${sourceSiteUrl}; import those sections separately for a fully independent site.`
|
|
5925
|
+
});
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
if (docsConfig.redirects) {
|
|
5929
|
+
docsConfig = {
|
|
5930
|
+
...docsConfig,
|
|
5931
|
+
redirects: docsConfig.redirects.filter((redirect) => {
|
|
5932
|
+
const literalRoute = (route) => route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route.replace(/\/+$/, "") || "/" : null;
|
|
5933
|
+
const sourceRoute = literalRoute(redirect.source);
|
|
5934
|
+
if (sourceRoute && sourceRoute === literalRoute(redirect.destination)) {
|
|
5935
|
+
warnings.push({
|
|
5936
|
+
code: "unsupported-config",
|
|
5937
|
+
message: `Self-redirect from ${redirect.source} to ${redirect.destination} was omitted.`
|
|
5938
|
+
});
|
|
5939
|
+
return false;
|
|
5940
|
+
}
|
|
5941
|
+
if (!/[+?()[\]{}]/.test(redirect.source)) return true;
|
|
5942
|
+
warnings.push({
|
|
5943
|
+
code: "unsupported-config",
|
|
5944
|
+
message: `Redirect from ${redirect.source} contains route-pattern syntax Next.js cannot express literally and was omitted.`
|
|
5945
|
+
});
|
|
5946
|
+
return false;
|
|
5947
|
+
})
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5084
5950
|
if (pages.length === 0) {
|
|
5085
5951
|
const reasons = warnings.map((warning) => warning.message);
|
|
5086
5952
|
throw new Error(reasons.length ? `No importable Markdown or MDX pages were found in the repository. Warnings encountered during migration:
|