@thallylabs/migrate 0.2.9 → 0.2.11
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 +19 -0
- package/dist/index.d.ts +33 -2
- package/dist/index.js +1159 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,20 @@ import { unified as unified2 } from "unified";
|
|
|
31
31
|
|
|
32
32
|
// src/frontmatter.ts
|
|
33
33
|
import { parse as parseYaml } from "yaml";
|
|
34
|
+
function salvageFrontmatterLines(matter) {
|
|
35
|
+
const data = {};
|
|
36
|
+
for (const line of matter.split(/\r?\n/)) {
|
|
37
|
+
if (!/^[A-Za-z_][\w-]*:\s?/.test(line)) continue;
|
|
38
|
+
try {
|
|
39
|
+
const parsedLine = parseYaml(line);
|
|
40
|
+
if (parsedLine && typeof parsedLine === "object" && !Array.isArray(parsedLine)) {
|
|
41
|
+
Object.assign(data, parsedLine);
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
34
48
|
function parseFrontmatter(raw) {
|
|
35
49
|
const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
36
50
|
const opening = /^---([^\r\n]*)\r?\n/.exec(source);
|
|
@@ -45,14 +59,23 @@ function parseFrontmatter(raw) {
|
|
|
45
59
|
if (matter.trim() === "" || !["", "yaml", "yml"].includes(language)) {
|
|
46
60
|
return { content, data: {} };
|
|
47
61
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
try {
|
|
63
|
+
const parsed = parseYaml(matter);
|
|
64
|
+
return {
|
|
65
|
+
content,
|
|
66
|
+
data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
|
|
67
|
+
};
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
content,
|
|
71
|
+
data: salvageFrontmatterLines(matter),
|
|
72
|
+
error: err instanceof Error ? err.message : String(err)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
// src/mdx.ts
|
|
78
|
+
import { posix } from "path";
|
|
56
79
|
import { compileSync } from "@mdx-js/mdx";
|
|
57
80
|
import { nameToEmoji } from "gemoji";
|
|
58
81
|
import remarkGfm from "remark-gfm";
|
|
@@ -232,8 +255,9 @@ function unwrapDocusaurusMdxCodeBlocks(body) {
|
|
|
232
255
|
}
|
|
233
256
|
return isMdxCodeBlock ? body : output.join("\n");
|
|
234
257
|
}
|
|
235
|
-
function normalizeExplicitHeadingIds(
|
|
236
|
-
|
|
258
|
+
function normalizeExplicitHeadingIds(raw) {
|
|
259
|
+
const { front, body } = splitFrontmatterBlock(raw);
|
|
260
|
+
return front + replaceOutsideCode(body, (segment) => segment.split("\n").map((line) => {
|
|
237
261
|
if (!/^[ \t]{0,3}#{1,6}[ \t]+/.test(line)) return line;
|
|
238
262
|
const trimmed = line.trimEnd();
|
|
239
263
|
if (trimmed.endsWith("}")) {
|
|
@@ -275,6 +299,7 @@ function removeGlobalDocusaurusImports(body) {
|
|
|
275
299
|
}
|
|
276
300
|
var JS_LITERAL_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "undefined"]);
|
|
277
301
|
var BARE_IDENTIFIER_PATH = /^[\p{ID_Start}$_][\p{ID_Continue}$]*(?:\.[\p{ID_Start}$_][\p{ID_Continue}$]*)*$/u;
|
|
302
|
+
var BARE_OBJECT_SHORTHAND = /^\{\s*([\p{ID_Start}$_][\p{ID_Continue}$]*)\s*\}$/u;
|
|
278
303
|
var SAFE_BUILTIN_ROOTS = /* @__PURE__ */ new Set([
|
|
279
304
|
"Math",
|
|
280
305
|
"JSON",
|
|
@@ -499,12 +524,13 @@ function escapeFernLiteralBraces(raw) {
|
|
|
499
524
|
const end = node.position?.end.offset;
|
|
500
525
|
const root2 = value.split(".")[0];
|
|
501
526
|
const safeBuiltinPath = value.includes(".") && SAFE_BUILTIN_ROOTS.has(root2);
|
|
502
|
-
const
|
|
527
|
+
const isBareIdentifier = BARE_IDENTIFIER_PATH.test(value) && !JS_LITERAL_KEYWORDS.has(value) && !declared.has(root2) && !safeBuiltinPath;
|
|
528
|
+
const shorthandMatch = value.match(BARE_OBJECT_SHORTHAND);
|
|
529
|
+
const isDoubleBraceTemplate = shorthandMatch !== null && !declared.has(shorthandMatch[1]);
|
|
503
530
|
const isIssueList = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\s*,\s*[A-Za-z][A-Za-z0-9]*-\d+)*$/.test(value);
|
|
504
|
-
if ((
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
edits.push({ start, end, value: `\\{${value}\\}` });
|
|
531
|
+
if ((isBareIdentifier || isDoubleBraceTemplate || isIssueList) && start !== void 0 && end !== void 0) {
|
|
532
|
+
const rawSpan = body.slice(start, end);
|
|
533
|
+
edits.push({ start, end, value: rawSpan.replace(/\\/g, "\\\\").replace(/[{}]/g, "\\$&") });
|
|
508
534
|
}
|
|
509
535
|
return;
|
|
510
536
|
}
|
|
@@ -644,6 +670,66 @@ function replaceOutsideCode(body, transform) {
|
|
|
644
670
|
const { masked, unmask } = maskCode(body);
|
|
645
671
|
return unmask(transform(masked));
|
|
646
672
|
}
|
|
673
|
+
function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePath) {
|
|
674
|
+
const rewrite = (target) => {
|
|
675
|
+
if (!target || target.startsWith("/") || target.startsWith("#") || target.startsWith("?") || target.includes("\\") || /[\u0000-\u001f\u007f]/.test(target) || /^[A-Za-z][A-Za-z\d+.-]*:/.test(target)) return target;
|
|
676
|
+
const suffixAt = target.search(/[?#]/);
|
|
677
|
+
const pathname = suffixAt < 0 ? target : target.slice(0, suffixAt);
|
|
678
|
+
const suffix = suffixAt < 0 ? "" : target.slice(suffixAt);
|
|
679
|
+
if (!pathname || pathname.startsWith("//") || pathname.split("/").some((part) => /%2f|%5c/i.test(part))) return target;
|
|
680
|
+
const source = posix.normalize(posix.join(posix.dirname(currentSourcePath.replace(/\\/g, "/")), pathname));
|
|
681
|
+
const candidates = /\.mdx?$/i.test(source) ? [source] : [source, `${source}.mdx`, `${source}.md`, `${source}/index.mdx`, `${source}/index.md`];
|
|
682
|
+
const route = candidates.map((candidate) => routesBySourcePath.get(candidate)).find(Boolean);
|
|
683
|
+
return route ? `/${route.replace(/^\/+/, "")}${suffix}` : target;
|
|
684
|
+
};
|
|
685
|
+
return replaceOutsideCode(body, (text) => text.replace(
|
|
686
|
+
/(\]\()(<[^>\n]+>|[^\s)]+)(?=\s|\))/g,
|
|
687
|
+
(_match, opening, target) => `${opening}${target.startsWith("<") && target.endsWith(">") ? `<${rewrite(target.slice(1, -1))}>` : rewrite(target)}`
|
|
688
|
+
).replace(
|
|
689
|
+
/(\b(?:href|to)\s*=\s*)(["'])([^"'\n]+)\2/g,
|
|
690
|
+
(_match, opening, quote, target) => `${opening}${quote}${rewrite(target)}${quote}`
|
|
691
|
+
).replace(
|
|
692
|
+
/(\b(?:href|to)\s*=\s*\{\s*)(["'])([^"'\n]+)\2(?=\s*\})/g,
|
|
693
|
+
(_match, opening, quote, target) => `${opening}${quote}${rewrite(target)}${quote}`
|
|
694
|
+
));
|
|
695
|
+
}
|
|
696
|
+
function normalizeFernFileTrees(body) {
|
|
697
|
+
return replaceOutsideCode(body, (whole) => whole.replace(
|
|
698
|
+
/<FileTree\s*>\s*\n([\s\S]*?)\n\s*<\/FileTree>/g,
|
|
699
|
+
(original, contents) => {
|
|
700
|
+
const lines = contents.split(/\r?\n/).filter((line) => line.trim());
|
|
701
|
+
if (!lines.length) return original;
|
|
702
|
+
const nodes = [];
|
|
703
|
+
for (const line of lines) {
|
|
704
|
+
const match = /^( *)(?:-|\*|\+)\s+(.+?)\s*$/.exec(line);
|
|
705
|
+
if (!match || match[1].length % 2 !== 0 || match[2].includes("\0")) return original;
|
|
706
|
+
nodes.push({ indent: match[1].length / 2, name: match[2], isFolder: match[2].endsWith("/") });
|
|
707
|
+
}
|
|
708
|
+
const output = ["<Tree>"];
|
|
709
|
+
const folders = [];
|
|
710
|
+
for (const node of nodes) {
|
|
711
|
+
while (folders.length > node.indent) {
|
|
712
|
+
output.push("</Folder>");
|
|
713
|
+
folders.pop();
|
|
714
|
+
}
|
|
715
|
+
if (node.indent > folders.length || node.indent > 0 && folders.length === 0) return original;
|
|
716
|
+
const name = `{${JSON.stringify(node.name)}}`;
|
|
717
|
+
if (node.isFolder) {
|
|
718
|
+
output.push(`<Folder name=${name} defaultOpen>`);
|
|
719
|
+
folders.push(node.indent);
|
|
720
|
+
} else {
|
|
721
|
+
output.push(`<File name=${name} />`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
while (folders.length) {
|
|
725
|
+
output.push("</Folder>");
|
|
726
|
+
folders.pop();
|
|
727
|
+
}
|
|
728
|
+
output.push("</Tree>");
|
|
729
|
+
return output.join("\n");
|
|
730
|
+
}
|
|
731
|
+
));
|
|
732
|
+
}
|
|
647
733
|
function normalizeHtmlComments(body) {
|
|
648
734
|
return replaceOutsideCode(body, (whole) => whole.replace(/<!--([\s\S]*?)-->/g, (_match, comment) => `{/*${comment}*/}`));
|
|
649
735
|
}
|
|
@@ -1013,7 +1099,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
|
1013
1099
|
const runMintlify = platform === "mintlify";
|
|
1014
1100
|
const runDocusaurus = platform === "docusaurus";
|
|
1015
1101
|
const sourceBody = runDocusaurus && unwrapMdxCodeBlocks ? unwrapDocusaurusMdxCodeBlocks(body) : body;
|
|
1016
|
-
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernCallouts(sourceBody) : sourceBody));
|
|
1102
|
+
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(sourceBody)) : sourceBody));
|
|
1017
1103
|
if (runDocusaurus) {
|
|
1018
1104
|
rewritten = replaceOutsideCode(rewritten, (segment) => segment.replace(
|
|
1019
1105
|
/:([+\w-]+):/g,
|
|
@@ -1092,7 +1178,8 @@ function parseMarkdownPage(input) {
|
|
|
1092
1178
|
noindex: parsed.data.noindex === true || parsed.data.noindex === "true" ? true : void 0,
|
|
1093
1179
|
openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
|
|
1094
1180
|
body,
|
|
1095
|
-
source: input.source
|
|
1181
|
+
source: input.source,
|
|
1182
|
+
...parsed.error ? { frontmatterError: parsed.error } : {}
|
|
1096
1183
|
};
|
|
1097
1184
|
}
|
|
1098
1185
|
|
|
@@ -1145,6 +1232,11 @@ function mintlifyLocalizedReference(value, locale, defaultPageIds = /* @__PURE__
|
|
|
1145
1232
|
const candidateId = pageIdFromReference(candidate, true);
|
|
1146
1233
|
if (candidateId && defaultPageIds.has(candidateId)) return candidate;
|
|
1147
1234
|
}
|
|
1235
|
+
const segments = reference.split("/");
|
|
1236
|
+
const localeIndex = segments.findIndex((segment) => segment.toLowerCase() === locale.toLowerCase());
|
|
1237
|
+
if (localeIndex > 0) {
|
|
1238
|
+
return [...segments.slice(0, localeIndex), ...segments.slice(localeIndex + 1)].join("/");
|
|
1239
|
+
}
|
|
1148
1240
|
return value;
|
|
1149
1241
|
}
|
|
1150
1242
|
function resolveWithin(root, candidate) {
|
|
@@ -1493,9 +1585,14 @@ function implicitReactImports(source) {
|
|
|
1493
1585
|
visit(source);
|
|
1494
1586
|
const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
|
|
1495
1587
|
const usesMintlifyComponents = references.has("MintlifyComponents") && !bindings.has("MintlifyComponents");
|
|
1588
|
+
const implicitComponents = MINTLIFY_COMPONENT_MODULES.map(({ module, names }) => {
|
|
1589
|
+
const needed = names.filter((name) => references.has(name) && !bindings.has(name));
|
|
1590
|
+
return needed.length ? `import { ${needed.join(", ")} } from '${module}';` : "";
|
|
1591
|
+
}).filter(Boolean);
|
|
1496
1592
|
return [
|
|
1497
1593
|
references.has("React") && !bindings.has("React") ? "import * as React from 'react';" : "",
|
|
1498
1594
|
hooks.length ? `import { ${hooks.join(", ")} } from 'react';` : "",
|
|
1595
|
+
...implicitComponents,
|
|
1499
1596
|
usesMintlifyComponents ? mintlifyComponentsShim() : ""
|
|
1500
1597
|
].filter(Boolean).join("\n");
|
|
1501
1598
|
}
|
|
@@ -1598,6 +1695,19 @@ function declarationsReferenceBrowserGlobal(body) {
|
|
|
1598
1695
|
}
|
|
1599
1696
|
return found;
|
|
1600
1697
|
}
|
|
1698
|
+
function neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn) {
|
|
1699
|
+
const importPattern = /^import\s+(?:\{\s*(?:default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}|([A-Z][A-Za-z0-9_]*))\s+from\s+(['"])((?:@site\/|\.\.?\/|\/)[^'"]+)\3\s*;?[ \t]*$/gm;
|
|
1700
|
+
const dropped = [];
|
|
1701
|
+
let result = content.replace(importPattern, (_match, namedComponent, defaultComponent, _quote, specifier) => {
|
|
1702
|
+
dropped.push({ name: namedComponent ?? defaultComponent, specifier });
|
|
1703
|
+
return "";
|
|
1704
|
+
});
|
|
1705
|
+
for (const { name, specifier } of dropped) {
|
|
1706
|
+
warn(`Component import ${JSON.stringify(specifier)} could not be resolved because this page has invalid MDX (see the previous warning), so the import and its usage were removed to keep the site building.`, currentFile);
|
|
1707
|
+
result = result.replace(new RegExp(`<${name}(?:\\s[^>]*)?/>`, "g"), `{/* Removed <${name}>: unresolved during fallback migration */}`).replace(new RegExp(`<${name}(?:\\s[^>]*)?>[\\s\\S]*?<\\/${name}>`, "g"), `{/* Removed <${name}>: unresolved during fallback migration */}`);
|
|
1708
|
+
}
|
|
1709
|
+
return result;
|
|
1710
|
+
}
|
|
1601
1711
|
function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIdentity) {
|
|
1602
1712
|
const root = resolve2(siteRoot);
|
|
1603
1713
|
const confined = resolve2(confinementRoot);
|
|
@@ -1792,16 +1902,17 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1792
1902
|
function transform(raw, currentFile) {
|
|
1793
1903
|
const parsedFrontmatter = parseFrontmatter(raw).content;
|
|
1794
1904
|
const frontmatter = raw.slice(0, raw.length - parsedFrontmatter.length);
|
|
1795
|
-
const content = normalizeIndentedFences(normalizeHtmlComments(parsedFrontmatter));
|
|
1905
|
+
const content = normalizeExplicitHeadingIds(normalizeIndentedFences(normalizeHtmlComments(parsedFrontmatter)));
|
|
1796
1906
|
let tree;
|
|
1797
1907
|
try {
|
|
1798
1908
|
tree = parser.parse(content);
|
|
1799
1909
|
} catch {
|
|
1800
1910
|
warn("Custom component analysis could not parse this MDX; its source was preserved for manual migration.", currentFile);
|
|
1801
|
-
return
|
|
1911
|
+
return frontmatter + neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn);
|
|
1802
1912
|
}
|
|
1803
1913
|
const aliases = /* @__PURE__ */ new Map();
|
|
1804
1914
|
const unsupportedImports = /* @__PURE__ */ new Map();
|
|
1915
|
+
const droppedMdxDataBindings = /* @__PURE__ */ new Set();
|
|
1805
1916
|
const edits = [];
|
|
1806
1917
|
const declarations = [];
|
|
1807
1918
|
const moduleImports = [];
|
|
@@ -1901,14 +2012,31 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1901
2012
|
}
|
|
1902
2013
|
continue;
|
|
1903
2014
|
}
|
|
1904
|
-
if (/\.mdx?$/.test(specifier))
|
|
2015
|
+
if (/\.mdx?$/.test(specifier)) {
|
|
2016
|
+
if (bindings.length > 0 && bindings.every((binding) => !/^[A-Z]/.test(binding.local))) {
|
|
2017
|
+
warn(`MDX named import of ${JSON.stringify(rawSpecifier)} isn't a component and has no equivalent in the migrated site (for example Docusaurus's generated 'toc' export), so it was removed, along with any '...${bindings.map((binding) => binding.local).join(", '...")}' spread of it.`, currentFile);
|
|
2018
|
+
edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
2019
|
+
for (const binding of bindings) droppedMdxDataBindings.add(binding.local);
|
|
2020
|
+
}
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
1905
2023
|
if (bindings.length && RESCUABLE_ASSET_EXTENSIONS.has(extname(specifier).toLowerCase()) && !bindings.some((binding) => usedAsJsxTagName(binding.local))) {
|
|
1906
2024
|
try {
|
|
1907
2025
|
const href = copyPublicAsset(resolveDependency(specifier, currentFile));
|
|
1908
2026
|
edits.push({
|
|
1909
2027
|
start: node.position.start.offset + statement.getStart(ast),
|
|
1910
2028
|
end: node.position.start.offset + statement.end,
|
|
1911
|
-
|
|
2029
|
+
// `export`, not a bare `const`: this replaces an `import`
|
|
2030
|
+
// statement in place, and the final MDX compile (a fresh
|
|
2031
|
+
// parse of this text, not a reuse of this pass's own AST)
|
|
2032
|
+
// only recognizes a top-level JS block as ESM — evaluated and
|
|
2033
|
+
// its bindings put in scope — when it starts with `import` or
|
|
2034
|
+
// `export`. A bare `const` there is just prose to that parser
|
|
2035
|
+
// and renders as literal paragraph text instead, leaving
|
|
2036
|
+
// every reference to this binding a `ReferenceError` at
|
|
2037
|
+
// render (reproduced against hasura/graphql-engine's
|
|
2038
|
+
// databases/overview.mdx, which binds asset URLs this way).
|
|
2039
|
+
value: bindings.map((binding) => `export const ${binding.local} = ${JSON.stringify(href)};`).join("\n")
|
|
1912
2040
|
});
|
|
1913
2041
|
warn(`Asset import ${JSON.stringify(rawSpecifier)} was copied to ${href} and bound to that URL instead of importing it as a component.`, currentFile);
|
|
1914
2042
|
continue;
|
|
@@ -1976,7 +2104,9 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1976
2104
|
edits.push({
|
|
1977
2105
|
start: importStart,
|
|
1978
2106
|
end: importEnd,
|
|
1979
|
-
|
|
2107
|
+
// See the identical `export const` note on the other asset
|
|
2108
|
+
// rescue above — same reason.
|
|
2109
|
+
value: bindings.map((binding) => `export const ${binding.local} = ${JSON.stringify(rescuedAssetHref)};`).join("\n")
|
|
1980
2110
|
});
|
|
1981
2111
|
warn(`Asset import ${JSON.stringify(rawSpecifier)} could not be copied as a component; it was copied to ${rescuedAssetHref} and bound to that URL instead.`, currentFile);
|
|
1982
2112
|
} else {
|
|
@@ -1986,6 +2116,27 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1986
2116
|
}
|
|
1987
2117
|
}
|
|
1988
2118
|
}
|
|
2119
|
+
if (droppedMdxDataBindings.size > 0) {
|
|
2120
|
+
for (const node of esmNodes) {
|
|
2121
|
+
let stripDroppedSpreads2 = function(inner) {
|
|
2122
|
+
if (ts.isSpreadElement(inner) && ts.isIdentifier(inner.expression) && droppedMdxDataBindings.has(inner.expression.text)) {
|
|
2123
|
+
const start = nodeStart + inner.getStart(ast);
|
|
2124
|
+
let end = nodeStart + inner.end;
|
|
2125
|
+
const following = nodeText.slice(inner.end).match(/^\s*,/);
|
|
2126
|
+
if (following) end += following[0].length;
|
|
2127
|
+
edits.push({ start, end, value: "" });
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
ts.forEachChild(inner, stripDroppedSpreads2);
|
|
2131
|
+
};
|
|
2132
|
+
var stripDroppedSpreads = stripDroppedSpreads2;
|
|
2133
|
+
if (node.value === void 0 || node.position?.start.offset === void 0) continue;
|
|
2134
|
+
const nodeStart = node.position.start.offset;
|
|
2135
|
+
const nodeText = node.value;
|
|
2136
|
+
const ast = sourceFile(nodeText, "inline.tsx");
|
|
2137
|
+
for (const statement of ast.statements) stripDroppedSpreads2(statement);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
1989
2140
|
walk(tree, (node) => {
|
|
1990
2141
|
for (const attribute of node.attributes ?? []) {
|
|
1991
2142
|
if (attribute.type !== "mdxJsxAttribute" || !attribute.value || typeof attribute.value !== "object") continue;
|
|
@@ -2242,14 +2393,14 @@ import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
|
|
|
2242
2393
|
|
|
2243
2394
|
// src/docusaurus.ts
|
|
2244
2395
|
import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3 } from "fs";
|
|
2245
|
-
import { extname as extname3, posix } from "path";
|
|
2396
|
+
import { extname as extname3, posix as posix2 } from "path";
|
|
2246
2397
|
import JSON5 from "json5";
|
|
2247
2398
|
import { parse as parseYaml2 } from "yaml";
|
|
2248
2399
|
|
|
2249
2400
|
// src/navigation.ts
|
|
2250
2401
|
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
2251
2402
|
import { dirname as dirname2, extname as extname2, relative as relative3 } from "path";
|
|
2252
|
-
var MAX_MINTLIFY_CONFIG_BYTES =
|
|
2403
|
+
var MAX_MINTLIFY_CONFIG_BYTES = 2e7;
|
|
2253
2404
|
function addMintlifyDirectoryRedirects(config, pages) {
|
|
2254
2405
|
const pageIds = new Set(pages.map((page) => page.id));
|
|
2255
2406
|
const redirects = [...config.redirects ?? []];
|
|
@@ -2297,15 +2448,10 @@ function addMintlifyDirectoryRedirects(config, pages) {
|
|
|
2297
2448
|
}
|
|
2298
2449
|
}
|
|
2299
2450
|
}
|
|
2300
|
-
for (const
|
|
2301
|
-
|
|
2302
|
-
if (!destination.startsWith("/") || destination.startsWith("//") || /[:*()[\]{}]/.test(destination)) continue;
|
|
2303
|
-
const directory = trimEdgeSlashes(destination);
|
|
2304
|
-
if (!directory || pageIds.has(directory)) continue;
|
|
2451
|
+
for (const [directory, landing] of firstDescendantByDirectory) {
|
|
2452
|
+
if (pageIds.has(directory)) continue;
|
|
2305
2453
|
const source = `/${directory}`;
|
|
2306
2454
|
if (hasAuthoredRedirect(source)) continue;
|
|
2307
|
-
const landing = firstDescendantByDirectory.get(directory);
|
|
2308
|
-
if (!landing) continue;
|
|
2309
2455
|
redirects.push({ source, destination: `/${landing.id}`, permanent: false });
|
|
2310
2456
|
redirectSources.add(source);
|
|
2311
2457
|
}
|
|
@@ -2356,6 +2502,18 @@ var LANGUAGE_LABELS = {
|
|
|
2356
2502
|
function objectValue(value) {
|
|
2357
2503
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2358
2504
|
}
|
|
2505
|
+
function withFlattenedProductGroups(container) {
|
|
2506
|
+
if (!Array.isArray(container.productGroups)) return container;
|
|
2507
|
+
const flattened = container.productGroups.flatMap((entry) => {
|
|
2508
|
+
const group = objectValue(entry);
|
|
2509
|
+
return group && Array.isArray(group.products) ? group.products : [];
|
|
2510
|
+
});
|
|
2511
|
+
if (flattened.length === 0) return container;
|
|
2512
|
+
return {
|
|
2513
|
+
...container,
|
|
2514
|
+
products: [...Array.isArray(container.products) ? container.products : [], ...flattened]
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2359
2517
|
function projectedHref(value) {
|
|
2360
2518
|
if (typeof value !== "string") return void 0;
|
|
2361
2519
|
const href = value.trim();
|
|
@@ -2443,12 +2601,23 @@ function mintlifyNavigationApiReferences(config) {
|
|
|
2443
2601
|
const references = [];
|
|
2444
2602
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus", "languages"];
|
|
2445
2603
|
const nestedKeys = ["groups", "pages"];
|
|
2446
|
-
function visit(
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2604
|
+
function visit(rawNode, tabLabel) {
|
|
2605
|
+
const node = withFlattenedProductGroups(rawNode);
|
|
2606
|
+
for (const kind of ["openapi", "asyncapi"]) {
|
|
2607
|
+
const value = node[kind];
|
|
2608
|
+
if (typeof value === "string" && value.trim()) {
|
|
2609
|
+
references.push({ value: value.trim(), kind, tabLabel });
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
const object = objectValue(value);
|
|
2613
|
+
if (object && typeof object.source === "string" && object.source.trim()) {
|
|
2614
|
+
references.push({
|
|
2615
|
+
value: object.source.trim(),
|
|
2616
|
+
kind,
|
|
2617
|
+
tabLabel,
|
|
2618
|
+
...typeof object.directory === "string" && object.directory.trim() ? { directory: object.directory.trim() } : {}
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2452
2621
|
}
|
|
2453
2622
|
for (const key of containerKeys) {
|
|
2454
2623
|
const entries = node[key];
|
|
@@ -2509,8 +2678,19 @@ function registerReference(value, context) {
|
|
|
2509
2678
|
}
|
|
2510
2679
|
return navigationId;
|
|
2511
2680
|
}
|
|
2681
|
+
var OPENAPI_OPERATION_REF = /^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+\S/;
|
|
2512
2682
|
function convertPage(value, context) {
|
|
2513
|
-
if (typeof value === "string")
|
|
2683
|
+
if (typeof value === "string") {
|
|
2684
|
+
if (OPENAPI_OPERATION_REF.test(value)) {
|
|
2685
|
+
warnOnce(
|
|
2686
|
+
context,
|
|
2687
|
+
"openapi-operation-list",
|
|
2688
|
+
'One or more navigation groups hand-pick or reorder individual OpenAPI operations (e.g. "GET /users"); Thally always renders every operation of a bound spec and has no manual-selection equivalent, so that list was dropped. The operations themselves are still available through the bound API reference.'
|
|
2689
|
+
);
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
return registerReference(value, context);
|
|
2693
|
+
}
|
|
2514
2694
|
const object = objectValue(value);
|
|
2515
2695
|
if (!object) return null;
|
|
2516
2696
|
if (typeof object.page === "string") return registerReference(object.page, context);
|
|
@@ -2553,8 +2733,9 @@ function convertNavigationValues(values, context) {
|
|
|
2553
2733
|
});
|
|
2554
2734
|
}
|
|
2555
2735
|
function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
|
|
2556
|
-
const
|
|
2557
|
-
if (!
|
|
2736
|
+
const rawContainer = objectValue(containerValue);
|
|
2737
|
+
if (!rawContainer) return [];
|
|
2738
|
+
const container = withFlattenedProductGroups(rawContainer);
|
|
2558
2739
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
|
|
2559
2740
|
for (const key of containerKeys) {
|
|
2560
2741
|
if (!Array.isArray(container[key])) continue;
|
|
@@ -2776,6 +2957,47 @@ function isRedirectPathSafe(rawSource, rawDestination) {
|
|
|
2776
2957
|
}
|
|
2777
2958
|
return true;
|
|
2778
2959
|
}
|
|
2960
|
+
function mintlifyDefaultVersionPrefixes(config) {
|
|
2961
|
+
const navigation = objectValue(config.navigation) ?? config;
|
|
2962
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
2963
|
+
function collect(container) {
|
|
2964
|
+
if (Array.isArray(container.versions)) {
|
|
2965
|
+
const entries = container.versions.map((value) => objectValue(value)).filter((value) => value !== null);
|
|
2966
|
+
const defaultEntry = entries.find((entry) => entry.default === true) ?? entries[0];
|
|
2967
|
+
if (defaultEntry && typeof defaultEntry.version === "string" && defaultEntry.version.trim()) {
|
|
2968
|
+
prefixes.add(defaultEntry.version.trim());
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (Array.isArray(container.languages)) {
|
|
2972
|
+
for (const value of container.languages) {
|
|
2973
|
+
const language = objectValue(value);
|
|
2974
|
+
if (language) collect(language);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
collect(navigation);
|
|
2979
|
+
return prefixes;
|
|
2980
|
+
}
|
|
2981
|
+
function mintlifyAllVersionPrefixes(config) {
|
|
2982
|
+
const navigation = objectValue(config.navigation) ?? config;
|
|
2983
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
2984
|
+
function collect(container) {
|
|
2985
|
+
if (Array.isArray(container.versions)) {
|
|
2986
|
+
for (const value of container.versions) {
|
|
2987
|
+
const entry = objectValue(value);
|
|
2988
|
+
if (entry && typeof entry.version === "string" && entry.version.trim()) prefixes.add(entry.version.trim());
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
if (Array.isArray(container.languages)) {
|
|
2992
|
+
for (const value of container.languages) {
|
|
2993
|
+
const language = objectValue(value);
|
|
2994
|
+
if (language) collect(language);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
collect(navigation);
|
|
2999
|
+
return prefixes;
|
|
3000
|
+
}
|
|
2779
3001
|
function projectMintlifyNavigation(config, options = {}) {
|
|
2780
3002
|
const warnings = [];
|
|
2781
3003
|
const references = [];
|
|
@@ -3021,7 +3243,7 @@ function pruneMissingNavigationPages(config, availableIds) {
|
|
|
3021
3243
|
}
|
|
3022
3244
|
|
|
3023
3245
|
// src/docusaurus.ts
|
|
3024
|
-
var MAX_CONFIG_BYTES =
|
|
3246
|
+
var MAX_CONFIG_BYTES = 2e7;
|
|
3025
3247
|
var CATEGORY_FILENAMES = ["_category_.json", "_category_.yml", "_category_.yaml"];
|
|
3026
3248
|
var SIDEBAR_FILENAMES = [
|
|
3027
3249
|
"sidebars.json",
|
|
@@ -3063,19 +3285,19 @@ function storageIdForRoute(routeId) {
|
|
|
3063
3285
|
function titleCase2(value) {
|
|
3064
3286
|
return stripNumberPrefix(value).replace(/sidebar$/i, "").split(/[-_]/).filter(Boolean).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3065
3287
|
}
|
|
3066
|
-
function routeFromSlug(
|
|
3067
|
-
if (
|
|
3068
|
-
if (
|
|
3069
|
-
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" :
|
|
3070
|
-
const resolved =
|
|
3288
|
+
function routeFromSlug(slug2, fallback, sourceDirectory) {
|
|
3289
|
+
if (slug2 === "/") return "introduction";
|
|
3290
|
+
if (slug2.startsWith("/")) return docusaurusRouteId(slug2);
|
|
3291
|
+
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" : posix2.dirname(fallback));
|
|
3292
|
+
const resolved = posix2.normalize(posix2.join(fallbackDirectory, slug2));
|
|
3071
3293
|
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
3072
3294
|
return docusaurusRouteId(resolved);
|
|
3073
3295
|
}
|
|
3074
3296
|
function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
3075
3297
|
const sourceDocId = normalizeDocId(sourcePath);
|
|
3076
|
-
const sourceDirectory =
|
|
3298
|
+
const sourceDirectory = posix2.dirname(sourceDocId);
|
|
3077
3299
|
const configuredId = typeof frontmatter.id === "string" && frontmatter.id.trim() ? stripNumberPrefix(frontmatter.id.trim()) : null;
|
|
3078
|
-
const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId :
|
|
3300
|
+
const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId : posix2.join(sourceDirectory, configuredId)) : sourceDocId;
|
|
3079
3301
|
const defaultNavigationId = defaultDocusaurusRouteId(docId) ?? fallback.navigationId;
|
|
3080
3302
|
const configuredSlug = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
3081
3303
|
const navigationId = configuredSlug ? routeFromSlug(
|
|
@@ -3097,6 +3319,105 @@ function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
|
3097
3319
|
function sourceReferenceKey(value) {
|
|
3098
3320
|
return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").replace(/\/$/, "");
|
|
3099
3321
|
}
|
|
3322
|
+
function sourceHeadings(body) {
|
|
3323
|
+
const headings = [];
|
|
3324
|
+
const lines = body.split("\n");
|
|
3325
|
+
let fence = null;
|
|
3326
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3327
|
+
const line = lines[index];
|
|
3328
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
3329
|
+
if (marker) {
|
|
3330
|
+
if (!fence) fence = marker[1];
|
|
3331
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && /^\s*$/.test(line.slice(marker[0].length))) fence = null;
|
|
3332
|
+
continue;
|
|
3333
|
+
}
|
|
3334
|
+
if (fence) continue;
|
|
3335
|
+
const heading = line.match(/^ {0,3}(#{2,6})\s+(.+?)\s*#*\s*$/);
|
|
3336
|
+
if (!heading) continue;
|
|
3337
|
+
const preceding = lines[index - 1]?.match(/^<a id="([\p{L}\p{N}_.:$-]{1,120})"><\/a>$/u);
|
|
3338
|
+
const plainText = !/[<>{}\[\]`*_]/.test(heading[2]);
|
|
3339
|
+
const generatedId = plainText ? heading[2].normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/(^-|-$)/g, "") : null;
|
|
3340
|
+
headings.push({ index, level: heading[1].length, id: preceding?.[1] ?? generatedId });
|
|
3341
|
+
}
|
|
3342
|
+
return headings;
|
|
3343
|
+
}
|
|
3344
|
+
function addDocusaurusTranslatedHeadingAliases(pages) {
|
|
3345
|
+
const byRoute = new Map(pages.map((page) => [page.navigationId, page]));
|
|
3346
|
+
for (const page of pages) {
|
|
3347
|
+
const locale = page.source.match(/(?:^|[/#])i18n\/([A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*)\/docusaurus-plugin-content-docs\//)?.[1];
|
|
3348
|
+
if (!locale) continue;
|
|
3349
|
+
const originalRoute = page.navigationId === locale ? "introduction" : page.navigationId.startsWith(`${locale}/`) ? page.navigationId.slice(locale.length + 1) : null;
|
|
3350
|
+
if (!originalRoute) continue;
|
|
3351
|
+
const original = byRoute.get(originalRoute);
|
|
3352
|
+
if (!original) continue;
|
|
3353
|
+
const originalHeadings = sourceHeadings(original.body);
|
|
3354
|
+
const translatedHeadings = sourceHeadings(page.body);
|
|
3355
|
+
if (!originalHeadings.length || originalHeadings.length !== translatedHeadings.length || originalHeadings.some((heading, index) => heading.level !== translatedHeadings[index].level)) continue;
|
|
3356
|
+
const occupied = new Set(translatedHeadings.map((heading) => heading.id).filter((id) => !!id));
|
|
3357
|
+
for (const match of page.body.matchAll(/<a\s+id="([^"]+)"/g)) occupied.add(match[1]);
|
|
3358
|
+
const originalIdCounts = /* @__PURE__ */ new Map();
|
|
3359
|
+
for (const heading of originalHeadings) {
|
|
3360
|
+
if (heading.id) originalIdCounts.set(heading.id, (originalIdCounts.get(heading.id) ?? 0) + 1);
|
|
3361
|
+
}
|
|
3362
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3363
|
+
for (let index = 0; index < originalHeadings.length; index++) {
|
|
3364
|
+
const id = originalHeadings[index].id;
|
|
3365
|
+
if (!id || occupied.has(id) || originalIdCounts.get(id) !== 1) continue;
|
|
3366
|
+
aliases.set(translatedHeadings[index].index, id);
|
|
3367
|
+
occupied.add(id);
|
|
3368
|
+
}
|
|
3369
|
+
if (aliases.size) page.body = page.body.split("\n").flatMap((line, index) => {
|
|
3370
|
+
const alias = aliases.get(index);
|
|
3371
|
+
return alias ? [`<a id="${alias}"></a>`, line] : [line];
|
|
3372
|
+
}).join("\n");
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
function expandDocusaurusDocCardLists(pages, config, sourceRoutes) {
|
|
3376
|
+
const pageByRoute = new Map(pages.map((page) => [page.navigationId, page]));
|
|
3377
|
+
const containsRoute = (items, route) => items.some((item) => typeof item === "string" ? item === route : containsRoute(item.pages, route));
|
|
3378
|
+
const findGroup = (items, route) => {
|
|
3379
|
+
for (const item of items) {
|
|
3380
|
+
if (typeof item === "string" || !containsRoute(item.pages, route)) continue;
|
|
3381
|
+
return findGroup(item.pages, route) ?? item;
|
|
3382
|
+
}
|
|
3383
|
+
return void 0;
|
|
3384
|
+
};
|
|
3385
|
+
const firstRoute = (items) => {
|
|
3386
|
+
for (const item of items) {
|
|
3387
|
+
if (typeof item === "string") return item;
|
|
3388
|
+
const candidate = firstRoute(item.pages);
|
|
3389
|
+
if (candidate) return candidate;
|
|
3390
|
+
}
|
|
3391
|
+
return void 0;
|
|
3392
|
+
};
|
|
3393
|
+
for (const page of pages) {
|
|
3394
|
+
if (!sourceRoutes.has(page.navigationId)) continue;
|
|
3395
|
+
const group = config.tabs.map((tab) => findGroup([
|
|
3396
|
+
...tab.pages ?? [],
|
|
3397
|
+
...tab.groups ?? []
|
|
3398
|
+
], page.navigationId)).find(Boolean);
|
|
3399
|
+
if (!group) {
|
|
3400
|
+
page.body = page.body.replace(/<div data-thally-doc-card-list=""\s*\/>/g, "");
|
|
3401
|
+
continue;
|
|
3402
|
+
}
|
|
3403
|
+
const cards = group.pages.flatMap((item) => {
|
|
3404
|
+
if (typeof item === "string") {
|
|
3405
|
+
if (item === page.navigationId) return [];
|
|
3406
|
+
const target = pageByRoute.get(item);
|
|
3407
|
+
return [{ title: target?.title ?? item.split("/").at(-1) ?? item, route: item }];
|
|
3408
|
+
}
|
|
3409
|
+
const slug2 = slugifySegment(item.group);
|
|
3410
|
+
const routes = [...item.pages].flatMap((child) => typeof child === "string" ? [child] : []);
|
|
3411
|
+
const route = routes.find((candidate) => candidate === slug2 || candidate.endsWith(`/${slug2}`)) ?? firstRoute(item.pages);
|
|
3412
|
+
return route ? [{ title: item.group, route }] : [];
|
|
3413
|
+
});
|
|
3414
|
+
const markup = cards.length ? `<CardGroup>
|
|
3415
|
+
${cards.map(({ title, route }) => ` <Card title={${JSON.stringify(title)}} href={${JSON.stringify(`/${route === "introduction" ? "" : route}`)}} />`).join("\n")}
|
|
3416
|
+
</CardGroup>` : "";
|
|
3417
|
+
page.body = page.body.replace(/<div data-thally-doc-card-list=""\s*\/>/g, markup);
|
|
3418
|
+
if (markup && !page.body.includes(markup)) page.body = [page.body.trim(), markup].filter(Boolean).join("\n\n");
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3100
3421
|
function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
3101
3422
|
const routes = /* @__PURE__ */ new Map();
|
|
3102
3423
|
for (const descriptor of descriptors) {
|
|
@@ -3107,18 +3428,31 @@ function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
|
3107
3428
|
const sourceRoute = pageIdFromReference(sourceKey);
|
|
3108
3429
|
if (sourceRoute) routes.set(sourceRoute, descriptor.navigationId);
|
|
3109
3430
|
}
|
|
3110
|
-
const currentDirectory =
|
|
3431
|
+
const currentDirectory = posix2.dirname(sourceReferenceKey(current.sourcePath));
|
|
3432
|
+
function copiedAssetTarget(target) {
|
|
3433
|
+
if (!target.startsWith("/") || target.startsWith("//") || target.includes("\\")) return null;
|
|
3434
|
+
const suffixIndex = target.search(/[?#]/);
|
|
3435
|
+
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
3436
|
+
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
3437
|
+
if (path.startsWith("/docs/") && options.assetPaths?.has(path.slice("/docs/".length))) {
|
|
3438
|
+
return `/${path.slice("/docs/".length)}${suffix}`;
|
|
3439
|
+
}
|
|
3440
|
+
return options.assetPaths?.has(path.slice(1)) ? target : null;
|
|
3441
|
+
}
|
|
3111
3442
|
function rewriteTarget(target) {
|
|
3112
|
-
if (!target ||
|
|
3443
|
+
if (!target || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
|
|
3444
|
+
if (target.startsWith("#")) return target;
|
|
3113
3445
|
const suffixIndex = target.search(/[?#]/);
|
|
3114
3446
|
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
3115
3447
|
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
3448
|
+
const copiedAsset = copiedAssetTarget(target);
|
|
3449
|
+
if (copiedAsset) return copiedAsset;
|
|
3116
3450
|
const candidates = path.startsWith("/") ? [
|
|
3117
3451
|
sourceReferenceKey(path),
|
|
3118
3452
|
sourceReferenceKey(path).replace(/^docs\//, ""),
|
|
3119
3453
|
sourceReferenceKey(path).replace(/^docs\/(?:next|current|latest)\//, "")
|
|
3120
3454
|
] : [
|
|
3121
|
-
sourceReferenceKey(
|
|
3455
|
+
sourceReferenceKey(posix2.normalize(posix2.join(currentDirectory, path))),
|
|
3122
3456
|
sourceReferenceKey(path)
|
|
3123
3457
|
];
|
|
3124
3458
|
const route = candidates.map((candidate) => routes.get(candidate)).find(Boolean);
|
|
@@ -3140,7 +3474,7 @@ function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
|
3140
3474
|
return line;
|
|
3141
3475
|
}
|
|
3142
3476
|
if (codeFence) return line;
|
|
3143
|
-
return line.replace(/(\]\()([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(target)}`).replace(/(\bhref=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(target)}${suffix}`).replace(/^(\s{0,3}\[[^\]]+\]:\s*)(<[^>]+>|\S+)/, (_match, prefix, destination) => {
|
|
3477
|
+
return line.replace(/(\]\()([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(target)}`).replace(/(\bhref=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(target)}${suffix}`).replace(/(\bsrc=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${copiedAssetTarget(target) ?? target}${suffix}`).replace(/^(\s{0,3}\[[^\]]+\]:\s*)(<[^>]+>|\S+)/, (_match, prefix, destination) => {
|
|
3144
3478
|
const bracketed = destination.startsWith("<") && destination.endsWith(">");
|
|
3145
3479
|
const target = bracketed ? destination.slice(1, -1) : destination;
|
|
3146
3480
|
const rewritten = rewriteTarget(target);
|
|
@@ -3161,7 +3495,7 @@ function readDocusaurusSiteOrigin(repositoryRoot) {
|
|
|
3161
3495
|
}
|
|
3162
3496
|
function readBoundedText(path) {
|
|
3163
3497
|
if (lstatSync3(path).size > MAX_CONFIG_BYTES) {
|
|
3164
|
-
throw new Error("Docusaurus sidebar config
|
|
3498
|
+
throw new Error("The Docusaurus sidebar config is larger than 20 MB and could not be imported.");
|
|
3165
3499
|
}
|
|
3166
3500
|
return readFileSync3(path, "utf8");
|
|
3167
3501
|
}
|
|
@@ -3418,7 +3752,7 @@ function readDocusaurusRedirects(repositoryRoot, warnings) {
|
|
|
3418
3752
|
}
|
|
3419
3753
|
function readCategoryMetadata(contentRoot, directory) {
|
|
3420
3754
|
for (const filename of CATEGORY_FILENAMES) {
|
|
3421
|
-
const path = resolveWithin(contentRoot,
|
|
3755
|
+
const path = resolveWithin(contentRoot, posix2.join(directory, filename));
|
|
3422
3756
|
if (!existsSync3(path) || !lstatSync3(path).isFile()) continue;
|
|
3423
3757
|
try {
|
|
3424
3758
|
const raw = readBoundedText(path);
|
|
@@ -3463,7 +3797,7 @@ function generatedIndexPage(label, linkValue, context) {
|
|
|
3463
3797
|
if (link.type !== "generated-index") return null;
|
|
3464
3798
|
const fallback = `category/${slugifySegment(label)}`;
|
|
3465
3799
|
const unprefixedNavigationId = typeof link.slug === "string" ? routeFromSlug(link.slug, fallback) ?? fallback : fallback;
|
|
3466
|
-
const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ?
|
|
3800
|
+
const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ? posix2.join(context.routePrefix, unprefixedNavigationId) : unprefixedNavigationId;
|
|
3467
3801
|
if (!context.generatedIds.has(navigationId) && !context.descriptors.some((page) => page.navigationId === navigationId)) {
|
|
3468
3802
|
const description = typeof link.description === "string" ? link.description : `Browse the ${label} documentation.`;
|
|
3469
3803
|
context.generatedPages.push({
|
|
@@ -3601,7 +3935,7 @@ function staticStringField(source, name, bindings) {
|
|
|
3601
3935
|
}
|
|
3602
3936
|
}
|
|
3603
3937
|
function readDocusaurusSiteSettings(repositoryRoot) {
|
|
3604
|
-
const empty = { docsRouteBasePath: "docs", navbarLinks: [], footerLinks: [] };
|
|
3938
|
+
const empty = { docsRouteBasePath: "docs", defaultColorMode: "light", navbarLinks: [], footerLinks: [] };
|
|
3605
3939
|
const configPath = findDocusaurusConfigPath(repositoryRoot);
|
|
3606
3940
|
if (!configPath) return empty;
|
|
3607
3941
|
const source = readBoundedText(configPath);
|
|
@@ -3611,6 +3945,10 @@ function readDocusaurusSiteSettings(repositoryRoot) {
|
|
|
3611
3945
|
const docsRouteBasePath = classicDocs && staticStringField(classicDocs, "routeBasePath") || "docs";
|
|
3612
3946
|
const themeStart = propertyStart(source, "themeConfig");
|
|
3613
3947
|
const theme = themeStart < 0 ? null : matchingObjectLiteral(source, themeStart);
|
|
3948
|
+
const colorModeStart = theme ? propertyStart(theme, "colorMode") : -1;
|
|
3949
|
+
const colorMode = colorModeStart < 0 ? null : matchingObjectLiteral(theme, colorModeStart);
|
|
3950
|
+
const configuredColorMode = colorMode ? staticStringField(colorMode, "defaultMode") : void 0;
|
|
3951
|
+
const defaultColorMode = configuredColorMode === "dark" ? "dark" : "light";
|
|
3614
3952
|
const navbarStart = theme ? propertyStart(theme, "navbar") : -1;
|
|
3615
3953
|
const navbar = navbarStart < 0 ? null : matchingObjectLiteral(theme, navbarStart);
|
|
3616
3954
|
const logoStart = navbar ? propertyStart(navbar, "logo") : -1;
|
|
@@ -3646,6 +3984,8 @@ function readDocusaurusSiteSettings(repositoryRoot) {
|
|
|
3646
3984
|
return {
|
|
3647
3985
|
name: staticStringField(source, "title"),
|
|
3648
3986
|
description: staticStringField(source, "tagline"),
|
|
3987
|
+
...navbar && staticStringField(navbar, "title") ? { navbarTitle: staticStringField(navbar, "title") } : {},
|
|
3988
|
+
defaultColorMode,
|
|
3649
3989
|
docsRouteBasePath,
|
|
3650
3990
|
...logo && staticStringField(logo, "src") ? {
|
|
3651
3991
|
logo: {
|
|
@@ -3670,32 +4010,47 @@ function autogeneratedItems(dirName, context) {
|
|
|
3670
4010
|
const childDirectories = /* @__PURE__ */ new Set();
|
|
3671
4011
|
for (const descriptor of inDirectory) {
|
|
3672
4012
|
const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
|
|
3673
|
-
const relativePath = normalizedDir ?
|
|
4013
|
+
const relativePath = normalizedDir ? posix2.relative(normalizedDir, source) : source;
|
|
3674
4014
|
const segments = relativePath.split("/").filter(Boolean);
|
|
3675
4015
|
if (segments.length <= 1) directPages.push(descriptor);
|
|
3676
4016
|
else childDirectories.add(segments[0]);
|
|
3677
4017
|
}
|
|
3678
|
-
const
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
4018
|
+
const pages = directPages.map((descriptor) => ({
|
|
4019
|
+
// An index document is the category landing page in Docusaurus. Thally
|
|
4020
|
+
// renders it as a child link, so put it before the category's other docs.
|
|
4021
|
+
position: descriptor.sidebarPosition ?? (/(?:^|\/)(?:index|readme)\.mdx?$/i.test(descriptor.sourcePath) ? -1 : Number.MAX_SAFE_INTEGER),
|
|
4022
|
+
sortKey: descriptor.sourcePath,
|
|
4023
|
+
item: descriptor.navigationId
|
|
4024
|
+
}));
|
|
3682
4025
|
const groups = [...childDirectories].map((segment) => {
|
|
3683
|
-
const directory = normalizedDir ?
|
|
4026
|
+
const directory = normalizedDir ? posix2.join(normalizedDir, segment) : segment;
|
|
3684
4027
|
const metadata = readCategoryMetadata(context.contentRoot, directory);
|
|
3685
|
-
const
|
|
4028
|
+
const pages2 = autogeneratedItems(directory, context);
|
|
4029
|
+
if (pages2.length === 1 && typeof pages2[0] === "string" && !metadata.label && metadata.position === void 0 && !metadata.link) {
|
|
4030
|
+
const onlyPage = context.descriptors.find((descriptor) => descriptor.navigationId === pages2[0]);
|
|
4031
|
+
if (onlyPage && /(?:^|\/)(?:index|readme)\.mdx?$/i.test(onlyPage.sourcePath)) {
|
|
4032
|
+
return {
|
|
4033
|
+
position: onlyPage.sidebarPosition ?? Number.MAX_SAFE_INTEGER,
|
|
4034
|
+
sortKey: segment,
|
|
4035
|
+
item: pages2[0]
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
3686
4039
|
const landing = generatedIndexPage(metadata.label ?? titleCase2(segment), metadata.link, context);
|
|
3687
|
-
if (landing && !
|
|
4040
|
+
if (landing && !pages2.includes(landing)) pages2.unshift(landing);
|
|
3688
4041
|
return {
|
|
3689
4042
|
position: metadata.position ?? Number.MAX_SAFE_INTEGER,
|
|
3690
|
-
segment,
|
|
3691
|
-
|
|
4043
|
+
sortKey: segment,
|
|
4044
|
+
item: {
|
|
3692
4045
|
group: metadata.label ?? titleCase2(segment),
|
|
3693
|
-
pages
|
|
4046
|
+
pages: pages2
|
|
3694
4047
|
}
|
|
3695
4048
|
};
|
|
3696
|
-
})
|
|
3697
|
-
|
|
3698
|
-
|
|
4049
|
+
});
|
|
4050
|
+
return [...pages, ...groups].sort((left, right) => left.position - right.position || left.sortKey.localeCompare(right.sortKey, void 0, { numeric: true })).map(({ item }) => {
|
|
4051
|
+
if (typeof item === "string") context.referencedNavigationIds.add(item);
|
|
4052
|
+
return item;
|
|
4053
|
+
});
|
|
3699
4054
|
}
|
|
3700
4055
|
function convertItems(value, context) {
|
|
3701
4056
|
if (typeof value === "string") {
|
|
@@ -3772,7 +4127,7 @@ import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as rea
|
|
|
3772
4127
|
import { basename as basename2, dirname as dirname3, extname as extname4, relative as relative4 } from "path";
|
|
3773
4128
|
import { load } from "cheerio";
|
|
3774
4129
|
import { parse as parseYaml3 } from "yaml";
|
|
3775
|
-
var MAX_CONFIG_BYTES2 =
|
|
4130
|
+
var MAX_CONFIG_BYTES2 = 2e7;
|
|
3776
4131
|
function announcementMarkdown(message) {
|
|
3777
4132
|
const document = load(message);
|
|
3778
4133
|
document("script, style, iframe, object, embed, svg, math").remove();
|
|
@@ -3807,7 +4162,7 @@ function titleCase3(value) {
|
|
|
3807
4162
|
}
|
|
3808
4163
|
function readBoundedYaml(path) {
|
|
3809
4164
|
if (lstatSync4(path).size > MAX_CONFIG_BYTES2) {
|
|
3810
|
-
throw new Error("Fern config
|
|
4165
|
+
throw new Error("The Fern config is larger than 20 MB and could not be imported.");
|
|
3811
4166
|
}
|
|
3812
4167
|
return parseYaml3(readFileSync4(path, "utf8"));
|
|
3813
4168
|
}
|
|
@@ -3869,7 +4224,7 @@ function registerPageAt(rawPath, base, context, navTitle, hidden = false) {
|
|
|
3869
4224
|
let sourcePath;
|
|
3870
4225
|
try {
|
|
3871
4226
|
const absolute = resolveWithinRoot(
|
|
3872
|
-
context.pathPrefix ?
|
|
4227
|
+
context.pathPrefix ? resolveWithinRoot(context.fernRoot, context.pathPrefix, context.repositoryRoot) : context.fernRoot,
|
|
3873
4228
|
rawPath.trim(),
|
|
3874
4229
|
context.repositoryRoot
|
|
3875
4230
|
);
|
|
@@ -3898,7 +4253,7 @@ function registerFolder(object, parentSegments, context) {
|
|
|
3898
4253
|
let folder;
|
|
3899
4254
|
try {
|
|
3900
4255
|
folder = resolveWithinRoot(
|
|
3901
|
-
context.pathPrefix ?
|
|
4256
|
+
context.pathPrefix ? resolveWithinRoot(context.fernRoot, context.pathPrefix, context.repositoryRoot) : context.fernRoot,
|
|
3902
4257
|
rawPath,
|
|
3903
4258
|
context.repositoryRoot
|
|
3904
4259
|
);
|
|
@@ -3984,18 +4339,31 @@ function convertNode(node, parentSegments, context) {
|
|
|
3984
4339
|
};
|
|
3985
4340
|
}
|
|
3986
4341
|
if (typeof object.link === "string") {
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4342
|
+
const label = object.link.trim();
|
|
4343
|
+
const rawHref = typeof object.href === "string" ? object.href.trim() : "";
|
|
4344
|
+
let href;
|
|
4345
|
+
if (/^https?:\/\//i.test(rawHref) && !/[\\\u0000-\u0020\u007f]/.test(rawHref)) {
|
|
4346
|
+
try {
|
|
4347
|
+
const url = new URL(rawHref);
|
|
4348
|
+
if (["http:", "https:"].includes(url.protocol) && !url.username && !url.password) href = url.toString();
|
|
4349
|
+
} catch {
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
if (label && href && object.hidden !== true && !context.hiddenInherited) {
|
|
4353
|
+
if (!context.externalLinks.some((link) => link.label === label && link.href === href)) {
|
|
4354
|
+
context.externalLinks.push({ label, href });
|
|
4355
|
+
}
|
|
4356
|
+
} else if (!href && object.hidden !== true && !context.hiddenInherited) {
|
|
4357
|
+
warnOnce2(context, `fern-link-${rawHref}`, `Fern navigation link "${label}" has an unsafe or invalid external URL and was skipped.`);
|
|
4358
|
+
}
|
|
3992
4359
|
return null;
|
|
3993
4360
|
}
|
|
3994
4361
|
if (typeof object.api === "string") {
|
|
4362
|
+
const ownSegment = segmentFor(object, object.api, context);
|
|
3995
4363
|
context.apiSections.push({
|
|
3996
4364
|
name: typeof object["api-name"] === "string" ? object["api-name"] : object.api,
|
|
3997
4365
|
nameExplicit: typeof object["api-name"] === "string",
|
|
3998
|
-
routeSegments: parentSegments
|
|
4366
|
+
routeSegments: ownSegment ? [...parentSegments, ownSegment] : parentSegments
|
|
3999
4367
|
});
|
|
4000
4368
|
return null;
|
|
4001
4369
|
}
|
|
@@ -4037,7 +4405,7 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
|
|
|
4037
4405
|
}
|
|
4038
4406
|
if (!chosen || typeof chosen.path !== "string") return config;
|
|
4039
4407
|
try {
|
|
4040
|
-
const versionPath = resolveWithinRoot(configDir, chosen.path,
|
|
4408
|
+
const versionPath = resolveWithinRoot(configDir, chosen.path, context.repositoryRoot);
|
|
4041
4409
|
if (!existsSync4(versionPath) || !lstatSync4(versionPath).isFile()) {
|
|
4042
4410
|
warnOnce2(context, "fern-version-not-file", `Fern version file "${chosen.path}" is not a regular file and was skipped.`);
|
|
4043
4411
|
return config;
|
|
@@ -4189,6 +4557,7 @@ function projectFernNavigation(input) {
|
|
|
4189
4557
|
changelogIndexes: [],
|
|
4190
4558
|
warnings: [],
|
|
4191
4559
|
warningKeys: /* @__PURE__ */ new Set(),
|
|
4560
|
+
externalLinks: [],
|
|
4192
4561
|
bareRouteRedirects: [],
|
|
4193
4562
|
segmentAliases: /* @__PURE__ */ new Map(),
|
|
4194
4563
|
pathPrefix: "",
|
|
@@ -4209,6 +4578,11 @@ function projectFernNavigation(input) {
|
|
|
4209
4578
|
const label = typeof link?.text === "string" ? link.text : typeof link?.label === "string" ? link.label : link?.type === "github" ? "GitHub" : void 0;
|
|
4210
4579
|
return href && label ? [{ label, href, ...link?.type === "github" ? { type: "github" } : {} }] : [];
|
|
4211
4580
|
}) : [];
|
|
4581
|
+
for (const link of context.externalLinks) {
|
|
4582
|
+
if (!navbarLinks.some((existing) => existing.label === link.label && existing.href === link.href)) {
|
|
4583
|
+
navbarLinks.push(link);
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4212
4586
|
const announcement = objectValue3(config.announcement);
|
|
4213
4587
|
const announcementMessage = typeof announcement?.message === "string" ? announcement.message.trim() : "";
|
|
4214
4588
|
const bannerContent = announcementMarkdown(announcementMessage);
|
|
@@ -4269,7 +4643,16 @@ function projectFernNavigation(input) {
|
|
|
4269
4643
|
return section;
|
|
4270
4644
|
}
|
|
4271
4645
|
const distinguishingSegment = section.routeSegments.at(-1);
|
|
4272
|
-
|
|
4646
|
+
let tabLabel = distinguishingSegment ? `${section.tabLabel}: ${titleCase3(distinguishingSegment)}` : section.tabLabel;
|
|
4647
|
+
if (!tabLabel || tabLabel === `${section.tabLabel}: ${section.tabLabel}` || claimedTabLabels.has(tabLabel)) {
|
|
4648
|
+
let ordinal = 2;
|
|
4649
|
+
let candidate = `${section.tabLabel} (${ordinal})`;
|
|
4650
|
+
while (claimedTabLabels.has(candidate)) {
|
|
4651
|
+
ordinal += 1;
|
|
4652
|
+
candidate = `${section.tabLabel} (${ordinal})`;
|
|
4653
|
+
}
|
|
4654
|
+
tabLabel = candidate;
|
|
4655
|
+
}
|
|
4273
4656
|
claimedTabLabels.add(tabLabel);
|
|
4274
4657
|
return { ...section, tabLabel };
|
|
4275
4658
|
});
|
|
@@ -4319,11 +4702,14 @@ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
4319
4702
|
".ico",
|
|
4320
4703
|
".jpeg",
|
|
4321
4704
|
".jpg",
|
|
4705
|
+
".m4a",
|
|
4322
4706
|
".mp3",
|
|
4323
4707
|
".mp4",
|
|
4708
|
+
".ogg",
|
|
4324
4709
|
".pdf",
|
|
4325
4710
|
".png",
|
|
4326
4711
|
".svg",
|
|
4712
|
+
".wav",
|
|
4327
4713
|
".webm",
|
|
4328
4714
|
".webp"
|
|
4329
4715
|
]);
|
|
@@ -4511,9 +4897,92 @@ function readDocusaurusConfigSource(projectRoot) {
|
|
|
4511
4897
|
if (!path || lstatSync5(path).size > 1e6) return "";
|
|
4512
4898
|
return readFileSync5(path, "utf8");
|
|
4513
4899
|
}
|
|
4900
|
+
function matchingBraceIndex(source, openBraceIndex) {
|
|
4901
|
+
let depth = 0;
|
|
4902
|
+
for (let i = openBraceIndex; i < source.length; i++) {
|
|
4903
|
+
const ch = source[i];
|
|
4904
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
4905
|
+
const quote = ch;
|
|
4906
|
+
i++;
|
|
4907
|
+
while (i < source.length && source[i] !== quote) {
|
|
4908
|
+
if (source[i] === "\\") i++;
|
|
4909
|
+
i++;
|
|
4910
|
+
}
|
|
4911
|
+
continue;
|
|
4912
|
+
}
|
|
4913
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
4914
|
+
const newline = source.indexOf("\n", i);
|
|
4915
|
+
i = newline === -1 ? source.length : newline;
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4918
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
4919
|
+
const end = source.indexOf("*/", i + 2);
|
|
4920
|
+
i = end === -1 ? source.length : end + 1;
|
|
4921
|
+
continue;
|
|
4922
|
+
}
|
|
4923
|
+
if (ch === "{") depth++;
|
|
4924
|
+
else if (ch === "}") {
|
|
4925
|
+
depth--;
|
|
4926
|
+
if (depth === 0) return i;
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
return -1;
|
|
4930
|
+
}
|
|
4931
|
+
function topLevelStringProperty(objectBody, key) {
|
|
4932
|
+
let depth = 0;
|
|
4933
|
+
const keyPattern = new RegExp(`^${key}\\s*:\\s*(['"])`);
|
|
4934
|
+
for (let i = 0; i < objectBody.length; i++) {
|
|
4935
|
+
const ch = objectBody[i];
|
|
4936
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
4937
|
+
const quote = ch;
|
|
4938
|
+
i++;
|
|
4939
|
+
while (i < objectBody.length && objectBody[i] !== quote) {
|
|
4940
|
+
if (objectBody[i] === "\\") i++;
|
|
4941
|
+
i++;
|
|
4942
|
+
}
|
|
4943
|
+
continue;
|
|
4944
|
+
}
|
|
4945
|
+
if (ch === "/" && objectBody[i + 1] === "/") {
|
|
4946
|
+
const newline = objectBody.indexOf("\n", i);
|
|
4947
|
+
i = newline === -1 ? objectBody.length : newline;
|
|
4948
|
+
continue;
|
|
4949
|
+
}
|
|
4950
|
+
if (ch === "/" && objectBody[i + 1] === "*") {
|
|
4951
|
+
const end = objectBody.indexOf("*/", i + 2);
|
|
4952
|
+
i = end === -1 ? objectBody.length : end + 1;
|
|
4953
|
+
continue;
|
|
4954
|
+
}
|
|
4955
|
+
if (ch === "{" || ch === "[" || ch === "(") {
|
|
4956
|
+
depth++;
|
|
4957
|
+
continue;
|
|
4958
|
+
}
|
|
4959
|
+
if (ch === "}" || ch === "]" || ch === ")") {
|
|
4960
|
+
depth--;
|
|
4961
|
+
continue;
|
|
4962
|
+
}
|
|
4963
|
+
if (depth === 0) {
|
|
4964
|
+
const match = keyPattern.exec(objectBody.slice(i));
|
|
4965
|
+
if (match) {
|
|
4966
|
+
const quote = match[1];
|
|
4967
|
+
const valueStart = i + match[0].length;
|
|
4968
|
+
const valueEnd = objectBody.indexOf(quote, valueStart);
|
|
4969
|
+
if (valueEnd !== -1) return objectBody.slice(valueStart, valueEnd);
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
return void 0;
|
|
4974
|
+
}
|
|
4514
4975
|
function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
4515
4976
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
4516
|
-
const
|
|
4977
|
+
const docsKeyMatch = source.match(/\bdocs\s*:\s*\{/);
|
|
4978
|
+
let configured;
|
|
4979
|
+
if (docsKeyMatch?.index !== void 0) {
|
|
4980
|
+
const openBrace = docsKeyMatch.index + docsKeyMatch[0].length - 1;
|
|
4981
|
+
const closeBrace = matchingBraceIndex(source, openBrace);
|
|
4982
|
+
if (closeBrace !== -1) {
|
|
4983
|
+
configured = topLevelStringProperty(source.slice(openBrace + 1, closeBrace), "path");
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4517
4986
|
return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
|
|
4518
4987
|
}
|
|
4519
4988
|
function docusaurusArchiveRoots(projectRoot, repositoryDir, warnings) {
|
|
@@ -4565,7 +5034,7 @@ function docusaurusArchiveRoots(projectRoot, repositoryDir, warnings) {
|
|
|
4565
5034
|
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot, warnings) {
|
|
4566
5035
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
4567
5036
|
const plugins = [];
|
|
4568
|
-
const matcher = /['"]
|
|
5037
|
+
const matcher = /['"](?:@docusaurus\/plugin-)?content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
|
|
4569
5038
|
for (const match of source.matchAll(matcher)) {
|
|
4570
5039
|
const localPath = trimTrailingSlashes(match[2].replace(/^\.\//, ""));
|
|
4571
5040
|
const routePrefix = trimEdgeSlashes(match[4]);
|
|
@@ -4803,8 +5272,10 @@ function detectRepositoryDocsDir(repositoryDir) {
|
|
|
4803
5272
|
}
|
|
4804
5273
|
return "";
|
|
4805
5274
|
}
|
|
4806
|
-
|
|
5275
|
+
var MAX_RANKED_WALK_FILES = MAX_SOURCE_FILES * 20;
|
|
5276
|
+
function scanFiles(root, confinementRoot = root, warnings, rank) {
|
|
4807
5277
|
const files = [];
|
|
5278
|
+
const walkCap = rank ? MAX_RANKED_WALK_FILES : MAX_SOURCE_FILES;
|
|
4808
5279
|
let confinementReal;
|
|
4809
5280
|
try {
|
|
4810
5281
|
confinementReal = realpathSync2(confinementRoot);
|
|
@@ -4813,7 +5284,7 @@ function scanFiles(root, confinementRoot = root, warnings) {
|
|
|
4813
5284
|
}
|
|
4814
5285
|
const visitedRealPaths = /* @__PURE__ */ new Set();
|
|
4815
5286
|
function visit(directory, logicalDirectory = directory) {
|
|
4816
|
-
if (files.length >=
|
|
5287
|
+
if (files.length >= walkCap) return;
|
|
4817
5288
|
let directoryReal;
|
|
4818
5289
|
try {
|
|
4819
5290
|
directoryReal = realpathSync2(directory);
|
|
@@ -4829,7 +5300,7 @@ function scanFiles(root, confinementRoot = root, warnings) {
|
|
|
4829
5300
|
return;
|
|
4830
5301
|
}
|
|
4831
5302
|
for (const entry of entries) {
|
|
4832
|
-
if (files.length >=
|
|
5303
|
+
if (files.length >= walkCap) return;
|
|
4833
5304
|
if (isIgnoredContentDirectory(entry.name)) {
|
|
4834
5305
|
if (warnings && entry.isDirectory() && !entry.isSymbolicLink() && containsMarkdown(resolveWithin(directory, entry.name))) {
|
|
4835
5306
|
warnings.push({
|
|
@@ -4866,8 +5337,44 @@ function scanFiles(root, confinementRoot = root, warnings) {
|
|
|
4866
5337
|
}
|
|
4867
5338
|
}
|
|
4868
5339
|
visit(root);
|
|
5340
|
+
if (rank && files.length >= walkCap) {
|
|
5341
|
+
warnings?.push({
|
|
5342
|
+
code: "limit-reached",
|
|
5343
|
+
message: `Stopped scanning after ${walkCap} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.`
|
|
5344
|
+
});
|
|
5345
|
+
}
|
|
4869
5346
|
return files;
|
|
4870
5347
|
}
|
|
5348
|
+
function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label) {
|
|
5349
|
+
if (scanned.length <= MAX_SOURCE_FILES) return scanned;
|
|
5350
|
+
const ranked = scanned.map((file) => ({ file, priority: rank(file.relativePath) })).sort((left, right) => left.priority - right.priority || (left.file.relativePath < right.file.relativePath ? -1 : left.file.relativePath > right.file.relativePath ? 1 : 0));
|
|
5351
|
+
const dropped = ranked.slice(MAX_SOURCE_FILES).map(({ file }) => file);
|
|
5352
|
+
if (warnings) {
|
|
5353
|
+
const droppedPages = label === "file" ? dropped.filter((file) => isDocumentationExtension(file.relativePath)) : dropped;
|
|
5354
|
+
const noun = label === "file" ? "page" : "image or media file";
|
|
5355
|
+
const droppedVersions = /* @__PURE__ */ new Set();
|
|
5356
|
+
for (const file of dropped) {
|
|
5357
|
+
const firstSegment = file.relativePath.split("/", 1)[0];
|
|
5358
|
+
if (allVersionPrefixes.has(firstSegment)) droppedVersions.add(firstSegment);
|
|
5359
|
+
}
|
|
5360
|
+
const examples = droppedPages.slice(0, 3).map((file) => file.relativePath);
|
|
5361
|
+
const rest = droppedPages.length - examples.length;
|
|
5362
|
+
warnings.push({
|
|
5363
|
+
code: "limit-reached",
|
|
5364
|
+
message: `This repository has more than ${MAX_SOURCE_FILES} ${label === "file" ? "files" : "assets"}, so only the first ${MAX_SOURCE_FILES} (in navigation order, default version first) were migrated. ${droppedPages.length} ${noun}(s) were left out` + (examples.length > 0 ? `: ${examples.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}` : "") + (droppedVersions.size > 0 ? ` (versions: ${[...droppedVersions].slice(0, 5).join(", ")}${droppedVersions.size > 5 ? `, and ${droppedVersions.size - 5} more` : ""})` : "") + (label === "file" ? ". To include them, run the migration on a smaller part of the repository with --docs-dir." : ". Copy them into public/ manually if your pages use them.")
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
return ranked.slice(0, MAX_SOURCE_FILES).map(({ file }) => file);
|
|
5368
|
+
}
|
|
5369
|
+
function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes) {
|
|
5370
|
+
const isAsset = (file) => ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase());
|
|
5371
|
+
const assetFiles = scanned.filter(isAsset);
|
|
5372
|
+
const otherFiles = scanned.filter((file) => !isAsset(file));
|
|
5373
|
+
return [
|
|
5374
|
+
...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file"),
|
|
5375
|
+
...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset")
|
|
5376
|
+
];
|
|
5377
|
+
}
|
|
4871
5378
|
function readMintignoreMatcher(mintlifyRoot) {
|
|
4872
5379
|
const path = resolveWithin(mintlifyRoot, MINTIGNORE_FILENAME);
|
|
4873
5380
|
if (!existsSync5(path) || !lstatSync5(path).isFile()) return null;
|
|
@@ -4942,7 +5449,7 @@ function mintlifyTopLevelApiReferences(config) {
|
|
|
4942
5449
|
}
|
|
4943
5450
|
return references;
|
|
4944
5451
|
}
|
|
4945
|
-
function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
5452
|
+
function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
4946
5453
|
if (!mintlifyConfig) return [];
|
|
4947
5454
|
const references = [
|
|
4948
5455
|
...mintlifyTopLevelApiReferences(mintlifyConfig),
|
|
@@ -4962,13 +5469,19 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
|
4962
5469
|
});
|
|
4963
5470
|
continue;
|
|
4964
5471
|
}
|
|
4965
|
-
if (/^https
|
|
5472
|
+
if (/^https:\/\//i.test(reference.value)) {
|
|
5473
|
+
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {} });
|
|
4966
5474
|
warnings.push({
|
|
4967
5475
|
code: "unsupported-config",
|
|
4968
|
-
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix}
|
|
5476
|
+
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
5477
|
+
source: reference.value
|
|
4969
5478
|
});
|
|
4970
5479
|
continue;
|
|
4971
5480
|
}
|
|
5481
|
+
if (/^http:\/\//i.test(reference.value)) {
|
|
5482
|
+
warnings.push({ code: "unsupported-config", message: `The OpenAPI spec "${reference.value}"${tabSuffix} uses insecure HTTP and was not imported.`, source: reference.value });
|
|
5483
|
+
continue;
|
|
5484
|
+
}
|
|
4972
5485
|
const key = reference.value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/");
|
|
4973
5486
|
const match = files.find((file) => file.relativePath === key);
|
|
4974
5487
|
if (!match) {
|
|
@@ -4978,10 +5491,159 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
|
4978
5491
|
});
|
|
4979
5492
|
continue;
|
|
4980
5493
|
}
|
|
4981
|
-
|
|
5494
|
+
if (reference.directory) {
|
|
5495
|
+
warnings.push({
|
|
5496
|
+
code: "unsupported-config",
|
|
5497
|
+
message: `The OpenAPI spec "${reference.value}"${tabSuffix} was limited to pages under "${reference.directory}" in the source, but Thally's API reference always covers a whole tab, so it was migrated as the tab's full API reference. Update any links to "${reference.directory}/..." pages manually.`
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
specs.push({
|
|
5501
|
+
filename: basename3(match.relativePath),
|
|
5502
|
+
content: readFileSync5(match.absolutePath),
|
|
5503
|
+
tabLabel: reference.tabLabel,
|
|
5504
|
+
sourcePath: match.relativePath,
|
|
5505
|
+
directory: reference.directory
|
|
5506
|
+
});
|
|
5507
|
+
}
|
|
5508
|
+
const filenameOwners = /* @__PURE__ */ new Map();
|
|
5509
|
+
for (const spec of specs) {
|
|
5510
|
+
const groupKey = spec.tabLabel ?? "\0default";
|
|
5511
|
+
const owner = filenameOwners.get(spec.filename);
|
|
5512
|
+
if (owner !== void 0 && owner !== groupKey) {
|
|
5513
|
+
let disambiguated = spec.sourcePath.replace(/\//g, "-");
|
|
5514
|
+
while (filenameOwners.has(disambiguated) && filenameOwners.get(disambiguated) !== groupKey) {
|
|
5515
|
+
disambiguated = `${groupKey.replace(/\W+/g, "-")}-${disambiguated}`;
|
|
5516
|
+
}
|
|
5517
|
+
spec.filename = disambiguated;
|
|
5518
|
+
}
|
|
5519
|
+
filenameOwners.set(spec.filename, groupKey);
|
|
4982
5520
|
}
|
|
4983
5521
|
return specs;
|
|
4984
5522
|
}
|
|
5523
|
+
var OPENAPI_HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head", "trace"];
|
|
5524
|
+
var FERN_CRUD_METHOD_NAMES = {
|
|
5525
|
+
findall: "list",
|
|
5526
|
+
findone: "get",
|
|
5527
|
+
remove: "delete"
|
|
5528
|
+
};
|
|
5529
|
+
function mintlifyOperationSlugSegment(value) {
|
|
5530
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
5531
|
+
}
|
|
5532
|
+
function thallyOperationSlugSegments(path, method) {
|
|
5533
|
+
const cleaned = path.split("/").filter(Boolean).map((segment) => segment.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").toLowerCase());
|
|
5534
|
+
if (!cleaned.length) cleaned.push("root");
|
|
5535
|
+
cleaned.push(method.toLowerCase());
|
|
5536
|
+
return cleaned;
|
|
5537
|
+
}
|
|
5538
|
+
function slugifyApiSpecId(value) {
|
|
5539
|
+
return value.toLowerCase().replace(/[^a-z0-9/]+/g, "-").replace(/(^-|-$)+/g, "").replace(/\//g, "-") || value.toLowerCase();
|
|
5540
|
+
}
|
|
5541
|
+
function parseOpenApiOperationsForLinking(content) {
|
|
5542
|
+
let document;
|
|
5543
|
+
try {
|
|
5544
|
+
document = parseYaml4(content.toString("utf8"));
|
|
5545
|
+
} catch {
|
|
5546
|
+
return [];
|
|
5547
|
+
}
|
|
5548
|
+
const paths = document && typeof document === "object" ? document.paths : null;
|
|
5549
|
+
if (!paths || typeof paths !== "object") return [];
|
|
5550
|
+
const operations = [];
|
|
5551
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
5552
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
5553
|
+
for (const method of OPENAPI_HTTP_METHODS) {
|
|
5554
|
+
const operation = pathItem[method];
|
|
5555
|
+
if (!operation || typeof operation !== "object") continue;
|
|
5556
|
+
const op = operation;
|
|
5557
|
+
const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
|
|
5558
|
+
operations.push({
|
|
5559
|
+
method,
|
|
5560
|
+
path,
|
|
5561
|
+
tag: tags[0],
|
|
5562
|
+
summary: typeof op.summary === "string" ? op.summary : void 0,
|
|
5563
|
+
operationId: typeof op.operationId === "string" ? op.operationId : void 0
|
|
5564
|
+
});
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
return operations;
|
|
5568
|
+
}
|
|
5569
|
+
function apiOperationLinkMap(sources, docsConfig) {
|
|
5570
|
+
const map = /* @__PURE__ */ new Map();
|
|
5571
|
+
const prefixLandings = /* @__PURE__ */ new Map();
|
|
5572
|
+
const apiTabs = docsConfig.tabs.filter((tab) => !tab.hidden && tab.api);
|
|
5573
|
+
apiTabs.forEach((tab, index) => {
|
|
5574
|
+
const source = sources.find((entry) => tab.api?.source === `/${entry.filename}`);
|
|
5575
|
+
if (!source || !source.prefix) return;
|
|
5576
|
+
const specId = index === 0 ? "default" : slugifyApiSpecId(tab.tab);
|
|
5577
|
+
const prefix = source.prefix.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
5578
|
+
if (prefix) prefixLandings.set(prefix, specId === "default" ? "/api" : `/api/${specId}`);
|
|
5579
|
+
for (const operation of parseOpenApiOperationsForLinking(source.content)) {
|
|
5580
|
+
const thallyHref = `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
|
|
5581
|
+
const leafCandidates = /* @__PURE__ */ new Set();
|
|
5582
|
+
if (operation.operationId) {
|
|
5583
|
+
const parts = operation.operationId.split(/[_.]/).filter(Boolean);
|
|
5584
|
+
const lastPart = parts.at(-1) ?? operation.operationId;
|
|
5585
|
+
const conventional = FERN_CRUD_METHOD_NAMES[lastPart.toLowerCase()];
|
|
5586
|
+
if (conventional) leafCandidates.add(conventional);
|
|
5587
|
+
leafCandidates.add(mintlifyOperationSlugSegment(lastPart));
|
|
5588
|
+
}
|
|
5589
|
+
if (operation.summary) leafCandidates.add(mintlifyOperationSlugSegment(operation.summary));
|
|
5590
|
+
if (operation.operationId) leafCandidates.add(mintlifyOperationSlugSegment(operation.operationId));
|
|
5591
|
+
leafCandidates.add(mintlifyOperationSlugSegment(`${operation.method} ${operation.path}`));
|
|
5592
|
+
const tagSegment = mintlifyOperationSlugSegment(operation.tag ?? "default");
|
|
5593
|
+
for (const leaf of leafCandidates) {
|
|
5594
|
+
if (!leaf) continue;
|
|
5595
|
+
const tagged = `${source.prefix}/${tagSegment}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
5596
|
+
const flat = `${source.prefix}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
5597
|
+
if (!map.has(tagged)) map.set(tagged, thallyHref);
|
|
5598
|
+
if (!map.has(flat)) map.set(flat, thallyHref);
|
|
5599
|
+
}
|
|
5600
|
+
}
|
|
5601
|
+
});
|
|
5602
|
+
return { operationLinks: map, prefixLandings };
|
|
5603
|
+
}
|
|
5604
|
+
function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings) {
|
|
5605
|
+
if (operationLinks.size === 0 && prefixLandings.size === 0) return;
|
|
5606
|
+
const unmatched = /* @__PURE__ */ new Set();
|
|
5607
|
+
for (const page of pages) {
|
|
5608
|
+
page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, unmatched);
|
|
5609
|
+
}
|
|
5610
|
+
if (unmatched.size === 0) return;
|
|
5611
|
+
const sorted = [...unmatched].sort();
|
|
5612
|
+
warnings.push({
|
|
5613
|
+
code: "unsupported-config",
|
|
5614
|
+
message: `${sorted.length} link(s) to API endpoints matched no endpoint in the OpenAPI spec, so they now point to the API section's overview page instead: ${sorted.slice(0, 10).join(", ")}${sorted.length > 10 ? `, and ${sorted.length - 10} more` : ""}. Update those links if you want them to reach a specific endpoint.`
|
|
5615
|
+
});
|
|
5616
|
+
}
|
|
5617
|
+
function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
|
|
5618
|
+
if (linkMap.size === 0 && prefixLandings.size === 0) return body;
|
|
5619
|
+
const orderedPrefixes = [...prefixLandings.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
5620
|
+
let codeFence = null;
|
|
5621
|
+
return body.split("\n").map((line) => {
|
|
5622
|
+
const fence = line.match(/^\s*(`{3,}|~{3,})/);
|
|
5623
|
+
if (fence) {
|
|
5624
|
+
if (!codeFence) codeFence = fence[1][0];
|
|
5625
|
+
else if (fence[1][0] === codeFence) codeFence = null;
|
|
5626
|
+
return line;
|
|
5627
|
+
}
|
|
5628
|
+
if (codeFence) return line;
|
|
5629
|
+
const rewriteTarget = (target) => {
|
|
5630
|
+
const suffixIndex = target.search(/[?#]/);
|
|
5631
|
+
const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "").toLowerCase();
|
|
5632
|
+
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
5633
|
+
const mapped = linkMap.get(path);
|
|
5634
|
+
if (mapped) return `${mapped}${suffix}`;
|
|
5635
|
+
for (const [prefix, landing] of orderedPrefixes) {
|
|
5636
|
+
if (path === prefix) return `${landing}${suffix}`;
|
|
5637
|
+
if (path.startsWith(`${prefix}/`)) {
|
|
5638
|
+
unmatched.add(target);
|
|
5639
|
+
return `${landing}${suffix}`;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
return target;
|
|
5643
|
+
};
|
|
5644
|
+
return line.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
|
|
5645
|
+
}).join("\n");
|
|
5646
|
+
}
|
|
4985
5647
|
var MAX_FERN_GENERATORS_BYTES = 2e6;
|
|
4986
5648
|
function fernGeneratorsOpenApiPaths(config) {
|
|
4987
5649
|
const api = config.api;
|
|
@@ -5364,9 +6026,9 @@ function addDatedHeadingAliases(body, date) {
|
|
|
5364
6026
|
if (open) return [line];
|
|
5365
6027
|
const heading = line.match(/^#{2,6}\s+(.+?)\s*#*\s*$/);
|
|
5366
6028
|
if (!heading) return [line];
|
|
5367
|
-
const
|
|
5368
|
-
const id = `${date}-${
|
|
5369
|
-
if (!
|
|
6029
|
+
const slug2 = heading[1].replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_]/g, "").normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").trim().replace(/\s+/g, "-");
|
|
6030
|
+
const id = `${date}-${slug2}`;
|
|
6031
|
+
if (!slug2 || existingIds.has(id)) return [line];
|
|
5370
6032
|
existingIds.add(id);
|
|
5371
6033
|
return [`<a id="${id}" />`, "", line];
|
|
5372
6034
|
}).join("\n");
|
|
@@ -5598,17 +6260,27 @@ ${result.slice(prefix.length)}`;
|
|
|
5598
6260
|
}
|
|
5599
6261
|
return depth === 0 ? hoistMdxImports(result) : result;
|
|
5600
6262
|
}
|
|
5601
|
-
function injectOpenApiSpecs(config, specs) {
|
|
6263
|
+
function injectOpenApiSpecs(config, specs, warnings) {
|
|
5602
6264
|
let tabs = config.tabs.map((tab) => ({ ...tab }));
|
|
5603
6265
|
for (const spec of specs) {
|
|
5604
6266
|
const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
|
|
5605
|
-
if (apiTab)
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
6267
|
+
if (apiTab) {
|
|
6268
|
+
if (apiTab.api && warnings) {
|
|
6269
|
+
warnings.push({
|
|
6270
|
+
code: "unsupported-config",
|
|
6271
|
+
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "/${spec.filename}" was not added. Put it in its own tab to include it.`
|
|
6272
|
+
});
|
|
6273
|
+
continue;
|
|
6274
|
+
}
|
|
6275
|
+
apiTab.api = {
|
|
6276
|
+
source: `/${spec.filename}`,
|
|
6277
|
+
// An API-only tab needs generated endpoint navigation; an authored
|
|
6278
|
+
// page tab keeps its own groups alongside the bound spec.
|
|
6279
|
+
...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
|
|
6280
|
+
};
|
|
6281
|
+
} else {
|
|
6282
|
+
tabs = [...tabs, { tab: spec.tabLabel ?? "API Reference", api: { source: `/${spec.filename}` } }];
|
|
6283
|
+
}
|
|
5612
6284
|
}
|
|
5613
6285
|
return { ...config, tabs };
|
|
5614
6286
|
}
|
|
@@ -5648,6 +6320,8 @@ function migrateRepository(options) {
|
|
|
5648
6320
|
const componentRoot = mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir;
|
|
5649
6321
|
const componentMigrator = platform === "mintlify" || platform === "docusaurus" || platform === "fern" ? createComponentMigrator(componentRoot, repositoryDir, warnings, componentSourceIdentity(options.sourceUrl, repositoryDir, componentRoot)) : void 0;
|
|
5650
6322
|
let docsConfig = { tabs: [] };
|
|
6323
|
+
let defaultVersionPrefixes = /* @__PURE__ */ new Set();
|
|
6324
|
+
let allVersionPrefixes = /* @__PURE__ */ new Set();
|
|
5651
6325
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
5652
6326
|
const exactReferenceMap = /* @__PURE__ */ new Map();
|
|
5653
6327
|
const referenceOrder = /* @__PURE__ */ new Map();
|
|
@@ -5657,6 +6331,7 @@ function migrateRepository(options) {
|
|
|
5657
6331
|
let fernBasePath = "";
|
|
5658
6332
|
let fernVersionPath = "";
|
|
5659
6333
|
let fernApiSections = [];
|
|
6334
|
+
const fernExternalSourcePaths = /* @__PURE__ */ new Set();
|
|
5660
6335
|
let fernChangelogIndexes = [];
|
|
5661
6336
|
const fernReferencedPaths = /* @__PURE__ */ new Set();
|
|
5662
6337
|
const fernSourceLinkAliases = /* @__PURE__ */ new Map();
|
|
@@ -5667,6 +6342,8 @@ function migrateRepository(options) {
|
|
|
5667
6342
|
const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
|
|
5668
6343
|
if (config) {
|
|
5669
6344
|
mintlifyConfig = config;
|
|
6345
|
+
defaultVersionPrefixes = mintlifyDefaultVersionPrefixes(config);
|
|
6346
|
+
allVersionPrefixes = mintlifyAllVersionPrefixes(config);
|
|
5670
6347
|
const projected = projectMintlifyNavigation(config);
|
|
5671
6348
|
docsConfig = projected.docsConfig;
|
|
5672
6349
|
warnings.push(...projected.warnings);
|
|
@@ -5717,6 +6394,7 @@ function migrateRepository(options) {
|
|
|
5717
6394
|
const exactKey = exactReferenceKey(descriptor.sourcePath);
|
|
5718
6395
|
if (!exactReferenceMap.has(exactKey)) exactReferenceMap.set(exactKey, { navigationId: descriptor.navigationId });
|
|
5719
6396
|
if (!referenceOrder.has(key)) referenceOrder.set(key, index);
|
|
6397
|
+
if (descriptor.sourcePath.startsWith("../")) fernExternalSourcePaths.add(descriptor.sourcePath);
|
|
5720
6398
|
}
|
|
5721
6399
|
}
|
|
5722
6400
|
} catch (error) {
|
|
@@ -5741,15 +6419,35 @@ function migrateRepository(options) {
|
|
|
5741
6419
|
throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
|
|
5742
6420
|
}
|
|
5743
6421
|
const mintignoreMatcher = platform === "mintlify" && mintlifyProjectRoot ? readMintignoreMatcher(mintlifyProjectRoot) : null;
|
|
5744
|
-
const
|
|
6422
|
+
const lastReferencedIndexByVersionLocale = /* @__PURE__ */ new Map();
|
|
6423
|
+
for (const [key, index] of referenceOrder) {
|
|
6424
|
+
const blockKey = key.split("/").slice(0, 2).join("/");
|
|
6425
|
+
const current = lastReferencedIndexByVersionLocale.get(blockKey);
|
|
6426
|
+
if (current === void 0 || index > current) lastReferencedIndexByVersionLocale.set(blockKey, index);
|
|
6427
|
+
}
|
|
6428
|
+
const discoveryRank = referenceOrder.size > 0 ? (relativePath) => {
|
|
6429
|
+
const referencedIndex = referenceOrder.get(normalizedReferenceKey(relativePath));
|
|
6430
|
+
if (referencedIndex !== void 0) return referencedIndex;
|
|
6431
|
+
const blockKey = relativePath.split("/").slice(0, 2).join("/");
|
|
6432
|
+
const versionCeiling = lastReferencedIndexByVersionLocale.get(blockKey);
|
|
6433
|
+
return versionCeiling !== void 0 ? versionCeiling + 1 : Number.MAX_SAFE_INTEGER;
|
|
6434
|
+
} : void 0;
|
|
6435
|
+
const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank);
|
|
6436
|
+
const mintignoreFilteredFiles = mintignoreMatcher ? scannedFiles.filter((file) => !mintignoreMatcher.ignores(file.relativePath)) : scannedFiles;
|
|
6437
|
+
const discoveryBudgetApplied = discoveryRank !== void 0 && mintignoreFilteredFiles.length > MAX_SOURCE_FILES;
|
|
6438
|
+
const files = discoveryRank ? selectFilesWithinBudget(mintignoreFilteredFiles, discoveryRank, warnings, allVersionPrefixes) : mintignoreFilteredFiles;
|
|
5745
6439
|
if (platform === "fern" && fernProjectRoot) {
|
|
5746
|
-
const
|
|
5747
|
-
for (const sourcePath of
|
|
6440
|
+
const discovered2 = new Set(files.map((file) => file.relativePath));
|
|
6441
|
+
for (const sourcePath of fernExternalSourcePaths) {
|
|
6442
|
+
if (discovered2.has(sourcePath)) continue;
|
|
5748
6443
|
try {
|
|
5749
6444
|
const absolutePath = resolveWithinRoot(fernProjectRoot, sourcePath, repositoryDir);
|
|
5750
|
-
if (
|
|
5751
|
-
|
|
5752
|
-
|
|
6445
|
+
if (existsSync5(absolutePath) && lstatSync5(absolutePath).isFile()) {
|
|
6446
|
+
files.push({ absolutePath, relativePath: sourcePath });
|
|
6447
|
+
discovered2.add(sourcePath);
|
|
6448
|
+
} else {
|
|
6449
|
+
warnings.push({ code: "missing-page", message: "A Fern navigation path could not be read within this repository.", source: sourcePath });
|
|
6450
|
+
}
|
|
5753
6451
|
} catch {
|
|
5754
6452
|
warnings.push({ code: "missing-page", message: "A Fern navigation path could not be read within this repository.", source: sourcePath });
|
|
5755
6453
|
}
|
|
@@ -5757,6 +6455,7 @@ function migrateRepository(options) {
|
|
|
5757
6455
|
}
|
|
5758
6456
|
const pages = [];
|
|
5759
6457
|
const assets = [];
|
|
6458
|
+
const remoteApiSpecs = [];
|
|
5760
6459
|
const referencedAssetPaths = /* @__PURE__ */ new Map();
|
|
5761
6460
|
const addAssetReference = (assetPath, referencingPage) => {
|
|
5762
6461
|
const referrers = referencedAssetPaths.get(assetPath);
|
|
@@ -5764,6 +6463,7 @@ function migrateRepository(options) {
|
|
|
5764
6463
|
else referencedAssetPaths.set(assetPath, /* @__PURE__ */ new Set([referencingPage]));
|
|
5765
6464
|
};
|
|
5766
6465
|
const docusaurusDescriptors = [];
|
|
6466
|
+
const docusaurusDocCardRoutes = /* @__PURE__ */ new Set();
|
|
5767
6467
|
const seenPageIds = /* @__PURE__ */ new Set();
|
|
5768
6468
|
const fernIdRenames = /* @__PURE__ */ new Map();
|
|
5769
6469
|
let skipped = 0;
|
|
@@ -5833,6 +6533,9 @@ function migrateRepository(options) {
|
|
|
5833
6533
|
mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir,
|
|
5834
6534
|
snippetAliases
|
|
5835
6535
|
);
|
|
6536
|
+
if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
|
|
6537
|
+
raw = normalizeExplicitHeadingIds(raw);
|
|
6538
|
+
}
|
|
5836
6539
|
if (componentMigrator) {
|
|
5837
6540
|
const warningsBeforeTransform = warnings.length;
|
|
5838
6541
|
raw = componentMigrator.transform(raw, file.absolutePath);
|
|
@@ -5858,6 +6561,10 @@ function migrateRepository(options) {
|
|
|
5858
6561
|
}
|
|
5859
6562
|
raw = escapeFernLiteralBraces(protectedMath.body);
|
|
5860
6563
|
}
|
|
6564
|
+
const hasDocusaurusDocCardList = platform === "docusaurus" && /<DocCardList\b[^>]*\/>/.test(raw);
|
|
6565
|
+
if (hasDocusaurusDocCardList) {
|
|
6566
|
+
raw = raw.replace(/<DocCardList\b[^>]*\/>/g, '<div data-thally-doc-card-list="" />');
|
|
6567
|
+
}
|
|
5861
6568
|
let docusaurusDescriptor;
|
|
5862
6569
|
const page = parseMarkdownPage({
|
|
5863
6570
|
id,
|
|
@@ -5891,9 +6598,9 @@ function migrateRepository(options) {
|
|
|
5891
6598
|
// hierarchy (never just its own segment) and wins over a docs.yml slug.
|
|
5892
6599
|
...platform === "fern" ? {
|
|
5893
6600
|
resolveIdentity: (frontmatter, fallback) => {
|
|
5894
|
-
const
|
|
5895
|
-
if (!
|
|
5896
|
-
const overridden = pageIdFromReference(
|
|
6601
|
+
const slug2 = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
6602
|
+
if (!slug2) return fallback;
|
|
6603
|
+
const overridden = pageIdFromReference(slug2, true);
|
|
5897
6604
|
if (!overridden) return fallback;
|
|
5898
6605
|
fernIdRenames.set(fallback.navigationId, overridden);
|
|
5899
6606
|
return { ...fallback, id: overridden, navigationId: overridden };
|
|
@@ -5904,6 +6611,13 @@ function migrateRepository(options) {
|
|
|
5904
6611
|
skipped++;
|
|
5905
6612
|
continue;
|
|
5906
6613
|
}
|
|
6614
|
+
if (page.frontmatterError) {
|
|
6615
|
+
warnings.push({
|
|
6616
|
+
code: "unsupported-config",
|
|
6617
|
+
message: `The page's frontmatter is not valid YAML (${page.frontmatterError}); the page was kept and as much of its metadata as could be read was used.`,
|
|
6618
|
+
source: relative5(repositoryDir, file.absolutePath).replace(/\\/g, "/")
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
5907
6621
|
if (platform === "fern" && !page.navTitle) {
|
|
5908
6622
|
const navTitle = fernNavTitles.get(navigationId);
|
|
5909
6623
|
if (navTitle && navTitle !== page.title) page.navTitle = navTitle;
|
|
@@ -5918,6 +6632,9 @@ function migrateRepository(options) {
|
|
|
5918
6632
|
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, fernProjectRoot, (assetPath) => {
|
|
5919
6633
|
addAssetReference(assetPath, file.relativePath);
|
|
5920
6634
|
});
|
|
6635
|
+
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, repositoryDir, (assetPath) => {
|
|
6636
|
+
addAssetReference(assetPath, file.relativePath);
|
|
6637
|
+
});
|
|
5921
6638
|
page.body = stripFernBasePathFromLinks(page.body, fernBasePath);
|
|
5922
6639
|
page.body = stripFernBasePathFromLinks(page.body, fernVersionPath);
|
|
5923
6640
|
}
|
|
@@ -5972,11 +6689,21 @@ function migrateRepository(options) {
|
|
|
5972
6689
|
}
|
|
5973
6690
|
seenPageIds.add(page.id);
|
|
5974
6691
|
pages.push(page);
|
|
6692
|
+
if (hasDocusaurusDocCardList) {
|
|
6693
|
+
docusaurusDocCardRoutes.add(page.navigationId);
|
|
6694
|
+
}
|
|
5975
6695
|
if (platform === "mintlify") {
|
|
5976
6696
|
const sourcePath = exactReferenceKey(file.relativePath);
|
|
5977
6697
|
if (sourcePath !== page.id && /^[A-Za-z0-9_./-]+$/.test(sourcePath)) {
|
|
5978
6698
|
routeAliases.push({ source: `/${sourcePath}`, destination: `/${page.id}`, permanent: false });
|
|
5979
6699
|
}
|
|
6700
|
+
const versionPrefix = [...defaultVersionPrefixes].find((prefix) => sourcePath === prefix || sourcePath.startsWith(`${prefix}/`));
|
|
6701
|
+
if (versionPrefix) {
|
|
6702
|
+
const unversioned = sourcePath.slice(versionPrefix.length).replace(/^\/+/, "");
|
|
6703
|
+
if (unversioned && unversioned !== sourcePath && unversioned !== page.id && /^[A-Za-z0-9_./-]+$/.test(unversioned) && !routeAliases.some((redirect) => redirect.source === `/${unversioned}`)) {
|
|
6704
|
+
routeAliases.push({ source: `/${unversioned}`, destination: `/${page.id}`, permanent: false });
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
5980
6707
|
}
|
|
5981
6708
|
if (docusaurusDescriptor) {
|
|
5982
6709
|
docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
|
|
@@ -5989,6 +6716,43 @@ function migrateRepository(options) {
|
|
|
5989
6716
|
}
|
|
5990
6717
|
}
|
|
5991
6718
|
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
6719
|
+
const fernBrandAssetPaths = /* @__PURE__ */ new Map();
|
|
6720
|
+
const fernReferencedAssets = [];
|
|
6721
|
+
if (platform === "fern" && fernProjectRoot) {
|
|
6722
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6723
|
+
const addFernAsset = (candidate, source) => {
|
|
6724
|
+
try {
|
|
6725
|
+
const absolutePath = source ? resolveWithinRoot(fernProjectRoot, source.replace(/^\/+/, ""), repositoryDir) : resolveWithin(repositoryDir, candidate);
|
|
6726
|
+
if (!existsSync5(absolutePath) || !lstatSync5(absolutePath).isFile()) return;
|
|
6727
|
+
const realRoot = realpathSync2(repositoryDir);
|
|
6728
|
+
const realPath = realpathSync2(absolutePath);
|
|
6729
|
+
const confined = relative5(realRoot, realPath);
|
|
6730
|
+
if (confined === ".." || confined.startsWith(`..${sep2}`) || isAbsolute2(confined)) return;
|
|
6731
|
+
const withinFern = relative5(fernProjectRoot, absolutePath);
|
|
6732
|
+
const assetRelative = withinFern !== ".." && !withinFern.startsWith(`..${sep2}`) && !isAbsolute2(withinFern) ? withinFern : relative5(repositoryDir, absolutePath);
|
|
6733
|
+
const assetPath = normalizeAssetPath(assetRelative.replace(/\\/g, "/"));
|
|
6734
|
+
if (!assetPath || !ASSET_EXTENSIONS.has(extname5(assetPath).toLowerCase())) return;
|
|
6735
|
+
if (source) {
|
|
6736
|
+
fernBrandAssetPaths.set(source, assetPath);
|
|
6737
|
+
addAssetReference(assetPath, "fern/docs.yml");
|
|
6738
|
+
}
|
|
6739
|
+
if (seen.has(absolutePath)) return;
|
|
6740
|
+
seen.add(absolutePath);
|
|
6741
|
+
fernReferencedAssets.push({ absolutePath, relativePath: assetPath });
|
|
6742
|
+
} catch {
|
|
6743
|
+
}
|
|
6744
|
+
};
|
|
6745
|
+
for (const path of referencedAssetPaths.keys()) addFernAsset(path);
|
|
6746
|
+
for (const value of [fernRawConfig?.logo, fernRawConfig?.favicon]) {
|
|
6747
|
+
if (typeof value === "string") addFernAsset("", value);
|
|
6748
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6749
|
+
const variants = value;
|
|
6750
|
+
for (const candidate of [variants.light, variants.dark, variants.src, variants.srcDark]) {
|
|
6751
|
+
if (typeof candidate === "string") addFernAsset("", candidate);
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
5992
6756
|
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
5993
6757
|
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
5994
6758
|
if (!existsSync5(root) || !lstatSync5(root).isDirectory()) return [];
|
|
@@ -5998,10 +6762,10 @@ function migrateRepository(options) {
|
|
|
5998
6762
|
}));
|
|
5999
6763
|
}) : [];
|
|
6000
6764
|
const assetCandidates = [];
|
|
6001
|
-
for (const file of [...files, ...repositoryAssets]) {
|
|
6765
|
+
for (const file of [...files, ...repositoryAssets, ...fernReferencedAssets]) {
|
|
6002
6766
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
6003
6767
|
if (!ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase())) continue;
|
|
6004
|
-
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
|
|
6768
|
+
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment) && !(platform === "fern" && referencedAssetPaths.has(file.relativePath))) continue;
|
|
6005
6769
|
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
6006
6770
|
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
6007
6771
|
if (!assetPath) continue;
|
|
@@ -6018,7 +6782,7 @@ function migrateRepository(options) {
|
|
|
6018
6782
|
const referencingPages = referencedAssetPaths.get(assetPath);
|
|
6019
6783
|
warnings.push({
|
|
6020
6784
|
code: "limit-reached",
|
|
6021
|
-
message:
|
|
6785
|
+
message: "This file was not copied because it is larger than 25 MB or the copied files would exceed 500 MB in total. Add it to public/ manually" + (referencingPages ? `; it is used by ${[...referencingPages].slice(0, 3).join(", ")}${referencingPages.size > 3 ? `, and ${referencingPages.size - 3} more` : ""}.` : "."),
|
|
6022
6786
|
source: file.relativePath
|
|
6023
6787
|
});
|
|
6024
6788
|
continue;
|
|
@@ -6035,8 +6799,8 @@ function migrateRepository(options) {
|
|
|
6035
6799
|
assets.push({ path: assetPath, content });
|
|
6036
6800
|
totalAssetBytes += size;
|
|
6037
6801
|
}
|
|
6038
|
-
if (files.length >= MAX_SOURCE_FILES) {
|
|
6039
|
-
warnings.push({ code: "limit-reached", message: `
|
|
6802
|
+
if (!discoveryBudgetApplied && files.length >= MAX_SOURCE_FILES) {
|
|
6803
|
+
warnings.push({ code: "limit-reached", message: `Stopped scanning after ${MAX_SOURCE_FILES} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.` });
|
|
6040
6804
|
}
|
|
6041
6805
|
if (platform === "docusaurus") {
|
|
6042
6806
|
const projected = projectDocusaurusNavigation({
|
|
@@ -6047,6 +6811,7 @@ function migrateRepository(options) {
|
|
|
6047
6811
|
routePrefix: options.docusaurusRoutePrefix
|
|
6048
6812
|
});
|
|
6049
6813
|
docsConfig = projected.docsConfig;
|
|
6814
|
+
expandDocusaurusDocCardLists(pages, docsConfig, docusaurusDocCardRoutes);
|
|
6050
6815
|
warnings.push(...projected.warnings);
|
|
6051
6816
|
for (const page of projected.generatedPages) {
|
|
6052
6817
|
if (seenPageIds.has(page.id)) continue;
|
|
@@ -6062,7 +6827,8 @@ function migrateRepository(options) {
|
|
|
6062
6827
|
const descriptor = descriptorByNavigationId.get(page.navigationId);
|
|
6063
6828
|
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors, {
|
|
6064
6829
|
sourceOrigin,
|
|
6065
|
-
onExternalLink: (target) => externalizedLinks.add(target)
|
|
6830
|
+
onExternalLink: (target) => externalizedLinks.add(target),
|
|
6831
|
+
assetPaths: new Set(assets.map((asset) => asset.path))
|
|
6066
6832
|
});
|
|
6067
6833
|
}
|
|
6068
6834
|
if (externalizedLinks.size > 0) warnings.push({
|
|
@@ -6120,10 +6886,11 @@ function migrateRepository(options) {
|
|
|
6120
6886
|
filename = `${prefix}${filename}`;
|
|
6121
6887
|
}
|
|
6122
6888
|
specFilenameSources.set(filename, spec.absolutePath);
|
|
6889
|
+
const specContent = readFileSync5(spec.absolutePath);
|
|
6123
6890
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
6124
|
-
assets.push({ path: filename, content:
|
|
6891
|
+
assets.push({ path: filename, content: specContent });
|
|
6125
6892
|
}
|
|
6126
|
-
resolvedSpecs.push({ filename, tabLabel: section.tabLabel });
|
|
6893
|
+
resolvedSpecs.push({ filename, tabLabel: section.tabLabel, content: specContent, routeSegments: section.routeSegments ?? [] });
|
|
6127
6894
|
continue;
|
|
6128
6895
|
}
|
|
6129
6896
|
if (resolution.unsupported.length > 0) {
|
|
@@ -6151,16 +6918,28 @@ function migrateRepository(options) {
|
|
|
6151
6918
|
message: checked.length ? `No OpenAPI/AsyncAPI spec could be found for the "${section.name}" API. Checked: ${checked.join(", ")}. Point generators.yml at a spec file that exists.` : `No OpenAPI/AsyncAPI spec could be found for the "${section.name}" API, because its api-name is not a valid folder name. Add the spec manually.`
|
|
6152
6919
|
});
|
|
6153
6920
|
}
|
|
6154
|
-
if (resolvedSpecs.length > 0)
|
|
6921
|
+
if (resolvedSpecs.length > 0) {
|
|
6922
|
+
docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
|
|
6923
|
+
const { operationLinks, prefixLandings } = apiOperationLinkMap(
|
|
6924
|
+
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.routeSegments.join("/") })),
|
|
6925
|
+
docsConfig
|
|
6926
|
+
);
|
|
6927
|
+
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
|
|
6928
|
+
}
|
|
6155
6929
|
} else if (platform === "mintlify") {
|
|
6156
|
-
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings);
|
|
6930
|
+
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
|
|
6157
6931
|
for (const spec of resolvedSpecs) {
|
|
6158
6932
|
if (!assets.some((asset) => asset.path === spec.filename)) {
|
|
6159
6933
|
assets.push({ path: spec.filename, content: spec.content });
|
|
6160
6934
|
}
|
|
6161
6935
|
}
|
|
6162
6936
|
if (resolvedSpecs.length > 0) {
|
|
6163
|
-
docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs);
|
|
6937
|
+
docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
|
|
6938
|
+
const { operationLinks, prefixLandings } = apiOperationLinkMap(
|
|
6939
|
+
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.directory })),
|
|
6940
|
+
docsConfig
|
|
6941
|
+
);
|
|
6942
|
+
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
|
|
6164
6943
|
} else {
|
|
6165
6944
|
const fallback = findOpenApi(files);
|
|
6166
6945
|
if (fallback) {
|
|
@@ -6193,7 +6972,7 @@ function migrateRepository(options) {
|
|
|
6193
6972
|
];
|
|
6194
6973
|
for (const plugin of additionalRoots) {
|
|
6195
6974
|
if (discovered >= MAX_SOURCE_FILES) {
|
|
6196
|
-
warnings.push({ code: "limit-reached", message: `Docusaurus docs
|
|
6975
|
+
warnings.push({ code: "limit-reached", message: `Stopped importing Docusaurus docs plugins after ${MAX_SOURCE_FILES} files; the remaining plugin docs were not migrated.` });
|
|
6197
6976
|
break;
|
|
6198
6977
|
}
|
|
6199
6978
|
if (plugin.docsDir === configuredDocsDir) continue;
|
|
@@ -6234,6 +7013,15 @@ function migrateRepository(options) {
|
|
|
6234
7013
|
}
|
|
6235
7014
|
}
|
|
6236
7015
|
if (platform === "docusaurus") {
|
|
7016
|
+
const copiedAssets = new Set(assets.map((asset) => asset.path));
|
|
7017
|
+
for (const page of pages) {
|
|
7018
|
+
page.body = rewriteDocusaurusLinks(page.body, {
|
|
7019
|
+
sourcePath: page.source ?? page.navigationId,
|
|
7020
|
+
docId: page.navigationId,
|
|
7021
|
+
navigationId: page.navigationId,
|
|
7022
|
+
title: page.title
|
|
7023
|
+
}, [], { assetPaths: copiedAssets });
|
|
7024
|
+
}
|
|
6237
7025
|
const importedRoutes = new Set(pages.map((page) => page.navigationId));
|
|
6238
7026
|
docsConfig.tabs = docsConfig.tabs.map((tab) => {
|
|
6239
7027
|
if (tab.href) return tab;
|
|
@@ -6293,6 +7081,27 @@ ${body}`;
|
|
|
6293
7081
|
}
|
|
6294
7082
|
}
|
|
6295
7083
|
docsConfig = pruneMissingNavigationPages(docsConfig, new Set(pages.map((page) => page.navigationId)));
|
|
7084
|
+
if (platform === "docusaurus") addDocusaurusTranslatedHeadingAliases(pages);
|
|
7085
|
+
if (platform === "fern" && fernProjectRoot) {
|
|
7086
|
+
const sourcePath = (page) => {
|
|
7087
|
+
const fragment = page.source.split("#", 2)[1];
|
|
7088
|
+
if (!fragment) return null;
|
|
7089
|
+
try {
|
|
7090
|
+
return relative5(fernProjectRoot, resolveWithin(repositoryDir, decodeURIComponent(fragment))).replace(/\\/g, "/");
|
|
7091
|
+
} catch {
|
|
7092
|
+
return null;
|
|
7093
|
+
}
|
|
7094
|
+
};
|
|
7095
|
+
const routes = /* @__PURE__ */ new Map();
|
|
7096
|
+
for (const page of pages) {
|
|
7097
|
+
const source = sourcePath(page);
|
|
7098
|
+
if (source) routes.set(source, page.id);
|
|
7099
|
+
}
|
|
7100
|
+
for (const page of pages) {
|
|
7101
|
+
const source = sourcePath(page);
|
|
7102
|
+
if (source) page.body = rewriteFernRelativePageLinks(page.body, source, routes);
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
6296
7105
|
preserveLinkedAnchors(pages);
|
|
6297
7106
|
if (platform === "fern" && fernRawConfig) {
|
|
6298
7107
|
const sourceSiteUrl = fernSourceSiteUrl(fernRawConfig);
|
|
@@ -6410,7 +7219,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6410
7219
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
6411
7220
|
const publicBrandPath = (value) => {
|
|
6412
7221
|
if (typeof value !== "string" || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(value)) return void 0;
|
|
6413
|
-
const normalized = normalizeAssetPath(value);
|
|
7222
|
+
const normalized = fernBrandAssetPaths.get(value) ?? normalizeAssetPath(value);
|
|
6414
7223
|
return normalized && assetPaths.has(normalized) ? `/${normalized}` : void 0;
|
|
6415
7224
|
};
|
|
6416
7225
|
const brandVariants = (value) => {
|
|
@@ -6445,6 +7254,34 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6445
7254
|
}
|
|
6446
7255
|
}
|
|
6447
7256
|
};
|
|
7257
|
+
if (platform === "docusaurus" && docusaurusSettings?.navbarTitle && !logoLight) {
|
|
7258
|
+
docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
|
|
7259
|
+
}
|
|
7260
|
+
if (mintlifyConfig) {
|
|
7261
|
+
const appearance = mintlifyConfig.appearance && typeof mintlifyConfig.appearance === "object" ? mintlifyConfig.appearance : null;
|
|
7262
|
+
const defaultMode = appearance?.default;
|
|
7263
|
+
if (defaultMode === "light" || defaultMode === "dark" || defaultMode === "system") {
|
|
7264
|
+
docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, default: defaultMode } };
|
|
7265
|
+
}
|
|
7266
|
+
const background = mintlifyConfig.background && typeof mintlifyConfig.background === "object" ? mintlifyConfig.background : null;
|
|
7267
|
+
if (background) {
|
|
7268
|
+
const decoration = background.decoration;
|
|
7269
|
+
const image = publicBrandPath(background.image);
|
|
7270
|
+
const imageDark = publicBrandPath(background.imageDark);
|
|
7271
|
+
docsConfig = {
|
|
7272
|
+
...docsConfig,
|
|
7273
|
+
background: {
|
|
7274
|
+
...docsConfig.background,
|
|
7275
|
+
...decoration === "none" || decoration === "grid" || decoration === "gradient" ? { decoration } : {},
|
|
7276
|
+
...image ? { image } : {},
|
|
7277
|
+
...imageDark ? { imageDark } : {}
|
|
7278
|
+
}
|
|
7279
|
+
};
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
if (platform === "docusaurus") {
|
|
7283
|
+
docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, default: docusaurusSettings?.defaultColorMode ?? "light" } };
|
|
7284
|
+
}
|
|
6448
7285
|
if (faviconLight) docsConfig = {
|
|
6449
7286
|
...docsConfig,
|
|
6450
7287
|
favicon: { light: faviconLight, ...faviconDark ? { dark: faviconDark } : {} }
|
|
@@ -6467,6 +7304,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6467
7304
|
platform,
|
|
6468
7305
|
pages,
|
|
6469
7306
|
assets,
|
|
7307
|
+
...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
|
|
6470
7308
|
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
6471
7309
|
docsConfig,
|
|
6472
7310
|
...mintlifyConfig ? {
|
|
@@ -6482,7 +7320,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6482
7320
|
}
|
|
6483
7321
|
} : docusaurusSettings && (docusaurusSettings.name || docusaurusSettings.description || themeColors) ? {
|
|
6484
7322
|
site: {
|
|
6485
|
-
...docusaurusSettings.name ? { name: docusaurusSettings.name } : {},
|
|
7323
|
+
...docusaurusSettings.navbarTitle || docusaurusSettings.name ? { name: docusaurusSettings.navbarTitle ?? docusaurusSettings.name } : {},
|
|
6486
7324
|
...docusaurusSettings.description ? { description: docusaurusSettings.description } : {},
|
|
6487
7325
|
...themeColors ? { colors: themeColors } : {}
|
|
6488
7326
|
}
|
|
@@ -6754,10 +7592,10 @@ async function readResponseBody(response) {
|
|
|
6754
7592
|
}
|
|
6755
7593
|
return new TextDecoder().decode(body);
|
|
6756
7594
|
}
|
|
6757
|
-
var defaultMigrationFetcher = async (url,
|
|
7595
|
+
var defaultMigrationFetcher = async (url, request2) => {
|
|
6758
7596
|
const response = await fetch(url, {
|
|
6759
7597
|
headers: {
|
|
6760
|
-
Accept:
|
|
7598
|
+
Accept: request2.accept,
|
|
6761
7599
|
"User-Agent": "Thally-Migrate/1.0 (+https://thally.io)"
|
|
6762
7600
|
},
|
|
6763
7601
|
redirect: "follow",
|
|
@@ -8172,6 +9010,202 @@ async function migrateUrl(options) {
|
|
|
8172
9010
|
}
|
|
8173
9011
|
};
|
|
8174
9012
|
}
|
|
9013
|
+
|
|
9014
|
+
// src/remote-api.ts
|
|
9015
|
+
import { createHash as createHash2 } from "crypto";
|
|
9016
|
+
import { lookup } from "dns/promises";
|
|
9017
|
+
import { request } from "https";
|
|
9018
|
+
import { isIP } from "net";
|
|
9019
|
+
import { parse as parseYaml6 } from "yaml";
|
|
9020
|
+
var MAX_SPEC_BYTES = 25e6;
|
|
9021
|
+
var MAX_REDIRECTS = 3;
|
|
9022
|
+
function isPublicAddress(address, family) {
|
|
9023
|
+
if (family === 4) {
|
|
9024
|
+
const parts = address.split(".").map(Number);
|
|
9025
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
9026
|
+
const [a, b] = parts;
|
|
9027
|
+
return !(a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && (b === 0 || b === 168) || a === 198 && (b === 18 || b === 19) || a === 203 && b === 0 || a === 198 && b === 51);
|
|
9028
|
+
}
|
|
9029
|
+
return family === 6 && /^[23][0-9a-f]{0,3}:/i.test(address) && !/^2001:db8:/i.test(address);
|
|
9030
|
+
}
|
|
9031
|
+
function validatedSpecUrl(value) {
|
|
9032
|
+
const url = new URL(value);
|
|
9033
|
+
if (url.protocol !== "https:" || url.username || url.password || isIP(url.hostname)) {
|
|
9034
|
+
throw new Error("Remote OpenAPI references must use an HTTPS domain without credentials or an IP literal.");
|
|
9035
|
+
}
|
|
9036
|
+
url.hash = "";
|
|
9037
|
+
return url;
|
|
9038
|
+
}
|
|
9039
|
+
async function getPinnedAddress(url) {
|
|
9040
|
+
const addresses = await lookup(url.hostname, { all: true });
|
|
9041
|
+
if (addresses.length === 0 || addresses.some((candidate) => !isPublicAddress(candidate.address, candidate.family))) {
|
|
9042
|
+
throw new Error("The OpenAPI host did not resolve exclusively to public addresses.");
|
|
9043
|
+
}
|
|
9044
|
+
return addresses[0];
|
|
9045
|
+
}
|
|
9046
|
+
async function requestPinnedSpec(url) {
|
|
9047
|
+
const pinned = await getPinnedAddress(url);
|
|
9048
|
+
return new Promise((resolve3, reject) => {
|
|
9049
|
+
const req = request(url, {
|
|
9050
|
+
headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
|
|
9051
|
+
signal: AbortSignal.timeout(3e4),
|
|
9052
|
+
// Node's dual-stack connector asks lookup for `all: true`; that
|
|
9053
|
+
// callback must receive address records, not a single address string.
|
|
9054
|
+
lookup: (_hostname, options, callback) => {
|
|
9055
|
+
if (typeof options === "object" && options.all) callback(null, [pinned]);
|
|
9056
|
+
else callback(null, pinned.address, pinned.family);
|
|
9057
|
+
}
|
|
9058
|
+
}, (response) => {
|
|
9059
|
+
const status = response.statusCode ?? 0;
|
|
9060
|
+
if (status >= 300 && status < 400) {
|
|
9061
|
+
response.resume();
|
|
9062
|
+
resolve3({ status, location: response.headers.location, body: Buffer.alloc(0) });
|
|
9063
|
+
return;
|
|
9064
|
+
}
|
|
9065
|
+
if (status < 200 || status >= 300) {
|
|
9066
|
+
response.resume();
|
|
9067
|
+
reject(new Error(`OpenAPI server returned ${status}.`));
|
|
9068
|
+
return;
|
|
9069
|
+
}
|
|
9070
|
+
const statedBytes = Number(response.headers["content-length"] ?? 0);
|
|
9071
|
+
if (statedBytes > MAX_SPEC_BYTES) {
|
|
9072
|
+
response.destroy();
|
|
9073
|
+
reject(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
9074
|
+
return;
|
|
9075
|
+
}
|
|
9076
|
+
const chunks = [];
|
|
9077
|
+
let bytes = 0;
|
|
9078
|
+
response.on("data", (chunk) => {
|
|
9079
|
+
bytes += chunk.length;
|
|
9080
|
+
if (bytes > MAX_SPEC_BYTES) {
|
|
9081
|
+
response.destroy(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
9082
|
+
return;
|
|
9083
|
+
}
|
|
9084
|
+
chunks.push(chunk);
|
|
9085
|
+
});
|
|
9086
|
+
response.on("end", () => resolve3({ status, body: Buffer.concat(chunks) }));
|
|
9087
|
+
response.on("error", reject);
|
|
9088
|
+
});
|
|
9089
|
+
req.on("error", reject);
|
|
9090
|
+
req.end();
|
|
9091
|
+
});
|
|
9092
|
+
}
|
|
9093
|
+
async function downloadSpec(value, fetcher) {
|
|
9094
|
+
let url = validatedSpecUrl(value);
|
|
9095
|
+
if (fetcher) {
|
|
9096
|
+
const response = await fetcher(url, { accept: "application/json, application/yaml, text/yaml" });
|
|
9097
|
+
validatedSpecUrl(response.finalUrl.toString());
|
|
9098
|
+
const body = Buffer.from(response.body);
|
|
9099
|
+
if (body.length > MAX_SPEC_BYTES) throw new Error("Remote OpenAPI spec exceeds the 25 MB import limit.");
|
|
9100
|
+
return body;
|
|
9101
|
+
}
|
|
9102
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
9103
|
+
const response = await requestPinnedSpec(url);
|
|
9104
|
+
if (response.status >= 300 && response.status < 400) {
|
|
9105
|
+
if (!response.location || hop === MAX_REDIRECTS) throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
9106
|
+
url = validatedSpecUrl(new URL(response.location, url).toString());
|
|
9107
|
+
continue;
|
|
9108
|
+
}
|
|
9109
|
+
return response.body;
|
|
9110
|
+
}
|
|
9111
|
+
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
9112
|
+
}
|
|
9113
|
+
function parseOpenApi(content, extension) {
|
|
9114
|
+
const text = content.toString("utf8");
|
|
9115
|
+
const parsed = extension === "yaml" || !text.trimStart().startsWith("{") ? parseYaml6(text) : JSON.parse(text);
|
|
9116
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
9117
|
+
const document = parsed;
|
|
9118
|
+
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
9119
|
+
throw new Error("Remote specification is not OpenAPI 3.x.");
|
|
9120
|
+
}
|
|
9121
|
+
if (!document.paths || typeof document.paths !== "object" || Array.isArray(document.paths)) {
|
|
9122
|
+
throw new Error("Remote OpenAPI spec has no paths object.");
|
|
9123
|
+
}
|
|
9124
|
+
const paths = Object.entries(document.paths);
|
|
9125
|
+
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
9126
|
+
const operations = [];
|
|
9127
|
+
for (const [path, value] of paths) {
|
|
9128
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
9129
|
+
for (const [method, raw] of Object.entries(value)) {
|
|
9130
|
+
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
9131
|
+
const operation = raw;
|
|
9132
|
+
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
9133
|
+
operations.push({
|
|
9134
|
+
method: method.toLowerCase(),
|
|
9135
|
+
path,
|
|
9136
|
+
tag: typeof tags[0] === "string" ? tags[0] : "API Reference",
|
|
9137
|
+
summary: typeof operation.summary === "string" ? operation.summary : String(operation.operationId ?? "")
|
|
9138
|
+
});
|
|
9139
|
+
}
|
|
9140
|
+
}
|
|
9141
|
+
return operations;
|
|
9142
|
+
}
|
|
9143
|
+
function slug(value) {
|
|
9144
|
+
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
9145
|
+
}
|
|
9146
|
+
function operationTarget(operation) {
|
|
9147
|
+
const path = operation.path.split("/").filter(Boolean).map((part) => part.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").toLowerCase());
|
|
9148
|
+
return `/api/default/${[...path, operation.method].join("/")}`;
|
|
9149
|
+
}
|
|
9150
|
+
async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
9151
|
+
if (!bundle.remoteApiSpecs?.length) return bundle;
|
|
9152
|
+
const assets = [...bundle.assets];
|
|
9153
|
+
const tabs = bundle.docsConfig.tabs.map((tab) => ({ ...tab }));
|
|
9154
|
+
const redirects = [...bundle.docsConfig.redirects ?? []];
|
|
9155
|
+
const warnings = [...bundle.warnings];
|
|
9156
|
+
const authoredLinks = /* @__PURE__ */ new Set();
|
|
9157
|
+
for (const page of bundle.pages) {
|
|
9158
|
+
for (const match of page.body.matchAll(/\/[a-z0-9_/-]+/gi)) {
|
|
9159
|
+
const segments = match[0].split("/").filter(Boolean);
|
|
9160
|
+
if (segments.length < 3) continue;
|
|
9161
|
+
const section = segments[0].toLowerCase();
|
|
9162
|
+
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0]);
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
9165
|
+
for (const reference of bundle.remoteApiSpecs) {
|
|
9166
|
+
for (let index = warnings.length - 1; index >= 0; index--) {
|
|
9167
|
+
if (warnings[index].source === reference.url && warnings[index].message.includes("requires a network download")) warnings.splice(index, 1);
|
|
9168
|
+
}
|
|
9169
|
+
try {
|
|
9170
|
+
const url = validatedSpecUrl(reference.url);
|
|
9171
|
+
const extension = /\.ya?ml$/i.test(url.pathname) ? "yaml" : "json";
|
|
9172
|
+
const body = await downloadSpec(reference.url, fetcher);
|
|
9173
|
+
const operations = parseOpenApi(body, extension);
|
|
9174
|
+
const filename = `openapi-${createHash2("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
9175
|
+
assets.push({ path: filename, content: body });
|
|
9176
|
+
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
9177
|
+
if (tab) tab.api = { source: `/${filename}` };
|
|
9178
|
+
else tabs.push({ tab: reference.tabLabel ?? "API Reference", api: { source: `/${filename}` } });
|
|
9179
|
+
const routes = /* @__PURE__ */ new Map();
|
|
9180
|
+
for (const operation of operations) {
|
|
9181
|
+
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
9182
|
+
if (routes.has(key)) routes.set(key, null);
|
|
9183
|
+
else routes.set(key, operationTarget(operation));
|
|
9184
|
+
}
|
|
9185
|
+
const existingSources = new Set(redirects.map((redirect) => redirect.source));
|
|
9186
|
+
for (const source of authoredLinks) {
|
|
9187
|
+
const parts = source.split("/").filter(Boolean);
|
|
9188
|
+
const key = parts.slice(-2).join("/");
|
|
9189
|
+
const destination = routes.get(key);
|
|
9190
|
+
if (!destination || existingSources.has(source)) continue;
|
|
9191
|
+
redirects.push({ source, destination, permanent: false });
|
|
9192
|
+
existingSources.add(source);
|
|
9193
|
+
}
|
|
9194
|
+
} catch (error) {
|
|
9195
|
+
warnings.push({
|
|
9196
|
+
code: "fetch-failed",
|
|
9197
|
+
source: reference.url,
|
|
9198
|
+
message: `Remote OpenAPI spec could not be imported: ${error instanceof Error ? error.message : String(error)}`
|
|
9199
|
+
});
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
return {
|
|
9203
|
+
...bundle,
|
|
9204
|
+
assets,
|
|
9205
|
+
docsConfig: { ...bundle.docsConfig, tabs, ...redirects.length > 0 ? { redirects } : {} },
|
|
9206
|
+
warnings
|
|
9207
|
+
};
|
|
9208
|
+
}
|
|
8175
9209
|
export {
|
|
8176
9210
|
addMintlifyDirectoryRedirects,
|
|
8177
9211
|
buildNavigationFromPages,
|
|
@@ -8180,6 +9214,7 @@ export {
|
|
|
8180
9214
|
detectRepositoryDocsDir,
|
|
8181
9215
|
detectRepositoryPlatform,
|
|
8182
9216
|
gitmodulePaths,
|
|
9217
|
+
hydrateRemoteApiSpecs,
|
|
8183
9218
|
mergeMigrationConfig,
|
|
8184
9219
|
migrateRepository,
|
|
8185
9220
|
migrateUrl,
|