@thallylabs/migrate 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +24 -2
- package/dist/index.js +580 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ function parseFrontmatter(raw) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// src/mdx.ts
|
|
56
|
+
import { posix } from "path";
|
|
56
57
|
import { compileSync } from "@mdx-js/mdx";
|
|
57
58
|
import { nameToEmoji } from "gemoji";
|
|
58
59
|
import remarkGfm from "remark-gfm";
|
|
@@ -644,6 +645,66 @@ function replaceOutsideCode(body, transform) {
|
|
|
644
645
|
const { masked, unmask } = maskCode(body);
|
|
645
646
|
return unmask(transform(masked));
|
|
646
647
|
}
|
|
648
|
+
function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePath) {
|
|
649
|
+
const rewrite = (target) => {
|
|
650
|
+
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;
|
|
651
|
+
const suffixAt = target.search(/[?#]/);
|
|
652
|
+
const pathname = suffixAt < 0 ? target : target.slice(0, suffixAt);
|
|
653
|
+
const suffix = suffixAt < 0 ? "" : target.slice(suffixAt);
|
|
654
|
+
if (!pathname || pathname.startsWith("//") || pathname.split("/").some((part) => /%2f|%5c/i.test(part))) return target;
|
|
655
|
+
const source = posix.normalize(posix.join(posix.dirname(currentSourcePath.replace(/\\/g, "/")), pathname));
|
|
656
|
+
const candidates = /\.mdx?$/i.test(source) ? [source] : [source, `${source}.mdx`, `${source}.md`, `${source}/index.mdx`, `${source}/index.md`];
|
|
657
|
+
const route = candidates.map((candidate) => routesBySourcePath.get(candidate)).find(Boolean);
|
|
658
|
+
return route ? `/${route.replace(/^\/+/, "")}${suffix}` : target;
|
|
659
|
+
};
|
|
660
|
+
return replaceOutsideCode(body, (text) => text.replace(
|
|
661
|
+
/(\]\()(<[^>\n]+>|[^\s)]+)(?=\s|\))/g,
|
|
662
|
+
(_match, opening, target) => `${opening}${target.startsWith("<") && target.endsWith(">") ? `<${rewrite(target.slice(1, -1))}>` : rewrite(target)}`
|
|
663
|
+
).replace(
|
|
664
|
+
/(\b(?:href|to)\s*=\s*)(["'])([^"'\n]+)\2/g,
|
|
665
|
+
(_match, opening, quote, target) => `${opening}${quote}${rewrite(target)}${quote}`
|
|
666
|
+
).replace(
|
|
667
|
+
/(\b(?:href|to)\s*=\s*\{\s*)(["'])([^"'\n]+)\2(?=\s*\})/g,
|
|
668
|
+
(_match, opening, quote, target) => `${opening}${quote}${rewrite(target)}${quote}`
|
|
669
|
+
));
|
|
670
|
+
}
|
|
671
|
+
function normalizeFernFileTrees(body) {
|
|
672
|
+
return replaceOutsideCode(body, (whole) => whole.replace(
|
|
673
|
+
/<FileTree\s*>\s*\n([\s\S]*?)\n\s*<\/FileTree>/g,
|
|
674
|
+
(original, contents) => {
|
|
675
|
+
const lines = contents.split(/\r?\n/).filter((line) => line.trim());
|
|
676
|
+
if (!lines.length) return original;
|
|
677
|
+
const nodes = [];
|
|
678
|
+
for (const line of lines) {
|
|
679
|
+
const match = /^( *)(?:-|\*|\+)\s+(.+?)\s*$/.exec(line);
|
|
680
|
+
if (!match || match[1].length % 2 !== 0 || match[2].includes("\0")) return original;
|
|
681
|
+
nodes.push({ indent: match[1].length / 2, name: match[2], isFolder: match[2].endsWith("/") });
|
|
682
|
+
}
|
|
683
|
+
const output = ["<Tree>"];
|
|
684
|
+
const folders = [];
|
|
685
|
+
for (const node of nodes) {
|
|
686
|
+
while (folders.length > node.indent) {
|
|
687
|
+
output.push("</Folder>");
|
|
688
|
+
folders.pop();
|
|
689
|
+
}
|
|
690
|
+
if (node.indent > folders.length || node.indent > 0 && folders.length === 0) return original;
|
|
691
|
+
const name = `{${JSON.stringify(node.name)}}`;
|
|
692
|
+
if (node.isFolder) {
|
|
693
|
+
output.push(`<Folder name=${name} defaultOpen>`);
|
|
694
|
+
folders.push(node.indent);
|
|
695
|
+
} else {
|
|
696
|
+
output.push(`<File name=${name} />`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
while (folders.length) {
|
|
700
|
+
output.push("</Folder>");
|
|
701
|
+
folders.pop();
|
|
702
|
+
}
|
|
703
|
+
output.push("</Tree>");
|
|
704
|
+
return output.join("\n");
|
|
705
|
+
}
|
|
706
|
+
));
|
|
707
|
+
}
|
|
647
708
|
function normalizeHtmlComments(body) {
|
|
648
709
|
return replaceOutsideCode(body, (whole) => whole.replace(/<!--([\s\S]*?)-->/g, (_match, comment) => `{/*${comment}*/}`));
|
|
649
710
|
}
|
|
@@ -1013,7 +1074,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
|
1013
1074
|
const runMintlify = platform === "mintlify";
|
|
1014
1075
|
const runDocusaurus = platform === "docusaurus";
|
|
1015
1076
|
const sourceBody = runDocusaurus && unwrapMdxCodeBlocks ? unwrapDocusaurusMdxCodeBlocks(body) : body;
|
|
1016
|
-
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernCallouts(sourceBody) : sourceBody));
|
|
1077
|
+
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(sourceBody)) : sourceBody));
|
|
1017
1078
|
if (runDocusaurus) {
|
|
1018
1079
|
rewritten = replaceOutsideCode(rewritten, (segment) => segment.replace(
|
|
1019
1080
|
/:([+\w-]+):/g,
|
|
@@ -1493,9 +1554,14 @@ function implicitReactImports(source) {
|
|
|
1493
1554
|
visit(source);
|
|
1494
1555
|
const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
|
|
1495
1556
|
const usesMintlifyComponents = references.has("MintlifyComponents") && !bindings.has("MintlifyComponents");
|
|
1557
|
+
const implicitComponents = MINTLIFY_COMPONENT_MODULES.map(({ module, names }) => {
|
|
1558
|
+
const needed = names.filter((name) => references.has(name) && !bindings.has(name));
|
|
1559
|
+
return needed.length ? `import { ${needed.join(", ")} } from '${module}';` : "";
|
|
1560
|
+
}).filter(Boolean);
|
|
1496
1561
|
return [
|
|
1497
1562
|
references.has("React") && !bindings.has("React") ? "import * as React from 'react';" : "",
|
|
1498
1563
|
hooks.length ? `import { ${hooks.join(", ")} } from 'react';` : "",
|
|
1564
|
+
...implicitComponents,
|
|
1499
1565
|
usesMintlifyComponents ? mintlifyComponentsShim() : ""
|
|
1500
1566
|
].filter(Boolean).join("\n");
|
|
1501
1567
|
}
|
|
@@ -2242,7 +2308,7 @@ import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
|
|
|
2242
2308
|
|
|
2243
2309
|
// src/docusaurus.ts
|
|
2244
2310
|
import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3 } from "fs";
|
|
2245
|
-
import { extname as extname3, posix } from "path";
|
|
2311
|
+
import { extname as extname3, posix as posix2 } from "path";
|
|
2246
2312
|
import JSON5 from "json5";
|
|
2247
2313
|
import { parse as parseYaml2 } from "yaml";
|
|
2248
2314
|
|
|
@@ -3063,19 +3129,19 @@ function storageIdForRoute(routeId) {
|
|
|
3063
3129
|
function titleCase2(value) {
|
|
3064
3130
|
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
3131
|
}
|
|
3066
|
-
function routeFromSlug(
|
|
3067
|
-
if (
|
|
3068
|
-
if (
|
|
3069
|
-
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" :
|
|
3070
|
-
const resolved =
|
|
3132
|
+
function routeFromSlug(slug2, fallback, sourceDirectory) {
|
|
3133
|
+
if (slug2 === "/") return "introduction";
|
|
3134
|
+
if (slug2.startsWith("/")) return docusaurusRouteId(slug2);
|
|
3135
|
+
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" : posix2.dirname(fallback));
|
|
3136
|
+
const resolved = posix2.normalize(posix2.join(fallbackDirectory, slug2));
|
|
3071
3137
|
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
3072
3138
|
return docusaurusRouteId(resolved);
|
|
3073
3139
|
}
|
|
3074
3140
|
function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
3075
3141
|
const sourceDocId = normalizeDocId(sourcePath);
|
|
3076
|
-
const sourceDirectory =
|
|
3142
|
+
const sourceDirectory = posix2.dirname(sourceDocId);
|
|
3077
3143
|
const configuredId = typeof frontmatter.id === "string" && frontmatter.id.trim() ? stripNumberPrefix(frontmatter.id.trim()) : null;
|
|
3078
|
-
const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId :
|
|
3144
|
+
const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId : posix2.join(sourceDirectory, configuredId)) : sourceDocId;
|
|
3079
3145
|
const defaultNavigationId = defaultDocusaurusRouteId(docId) ?? fallback.navigationId;
|
|
3080
3146
|
const configuredSlug = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
3081
3147
|
const navigationId = configuredSlug ? routeFromSlug(
|
|
@@ -3097,6 +3163,105 @@ function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
|
3097
3163
|
function sourceReferenceKey(value) {
|
|
3098
3164
|
return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").replace(/\/$/, "");
|
|
3099
3165
|
}
|
|
3166
|
+
function sourceHeadings(body) {
|
|
3167
|
+
const headings = [];
|
|
3168
|
+
const lines = body.split("\n");
|
|
3169
|
+
let fence = null;
|
|
3170
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3171
|
+
const line = lines[index];
|
|
3172
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
3173
|
+
if (marker) {
|
|
3174
|
+
if (!fence) fence = marker[1];
|
|
3175
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && /^\s*$/.test(line.slice(marker[0].length))) fence = null;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
if (fence) continue;
|
|
3179
|
+
const heading = line.match(/^ {0,3}(#{2,6})\s+(.+?)\s*#*\s*$/);
|
|
3180
|
+
if (!heading) continue;
|
|
3181
|
+
const preceding = lines[index - 1]?.match(/^<a id="([\p{L}\p{N}_.:$-]{1,120})"><\/a>$/u);
|
|
3182
|
+
const plainText = !/[<>{}\[\]`*_]/.test(heading[2]);
|
|
3183
|
+
const generatedId = plainText ? heading[2].normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/(^-|-$)/g, "") : null;
|
|
3184
|
+
headings.push({ index, level: heading[1].length, id: preceding?.[1] ?? generatedId });
|
|
3185
|
+
}
|
|
3186
|
+
return headings;
|
|
3187
|
+
}
|
|
3188
|
+
function addDocusaurusTranslatedHeadingAliases(pages) {
|
|
3189
|
+
const byRoute = new Map(pages.map((page) => [page.navigationId, page]));
|
|
3190
|
+
for (const page of pages) {
|
|
3191
|
+
const locale = page.source.match(/(?:^|[/#])i18n\/([A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*)\/docusaurus-plugin-content-docs\//)?.[1];
|
|
3192
|
+
if (!locale) continue;
|
|
3193
|
+
const originalRoute = page.navigationId === locale ? "introduction" : page.navigationId.startsWith(`${locale}/`) ? page.navigationId.slice(locale.length + 1) : null;
|
|
3194
|
+
if (!originalRoute) continue;
|
|
3195
|
+
const original = byRoute.get(originalRoute);
|
|
3196
|
+
if (!original) continue;
|
|
3197
|
+
const originalHeadings = sourceHeadings(original.body);
|
|
3198
|
+
const translatedHeadings = sourceHeadings(page.body);
|
|
3199
|
+
if (!originalHeadings.length || originalHeadings.length !== translatedHeadings.length || originalHeadings.some((heading, index) => heading.level !== translatedHeadings[index].level)) continue;
|
|
3200
|
+
const occupied = new Set(translatedHeadings.map((heading) => heading.id).filter((id) => !!id));
|
|
3201
|
+
for (const match of page.body.matchAll(/<a\s+id="([^"]+)"/g)) occupied.add(match[1]);
|
|
3202
|
+
const originalIdCounts = /* @__PURE__ */ new Map();
|
|
3203
|
+
for (const heading of originalHeadings) {
|
|
3204
|
+
if (heading.id) originalIdCounts.set(heading.id, (originalIdCounts.get(heading.id) ?? 0) + 1);
|
|
3205
|
+
}
|
|
3206
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3207
|
+
for (let index = 0; index < originalHeadings.length; index++) {
|
|
3208
|
+
const id = originalHeadings[index].id;
|
|
3209
|
+
if (!id || occupied.has(id) || originalIdCounts.get(id) !== 1) continue;
|
|
3210
|
+
aliases.set(translatedHeadings[index].index, id);
|
|
3211
|
+
occupied.add(id);
|
|
3212
|
+
}
|
|
3213
|
+
if (aliases.size) page.body = page.body.split("\n").flatMap((line, index) => {
|
|
3214
|
+
const alias = aliases.get(index);
|
|
3215
|
+
return alias ? [`<a id="${alias}"></a>`, line] : [line];
|
|
3216
|
+
}).join("\n");
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function expandDocusaurusDocCardLists(pages, config, sourceRoutes) {
|
|
3220
|
+
const pageByRoute = new Map(pages.map((page) => [page.navigationId, page]));
|
|
3221
|
+
const containsRoute = (items, route) => items.some((item) => typeof item === "string" ? item === route : containsRoute(item.pages, route));
|
|
3222
|
+
const findGroup = (items, route) => {
|
|
3223
|
+
for (const item of items) {
|
|
3224
|
+
if (typeof item === "string" || !containsRoute(item.pages, route)) continue;
|
|
3225
|
+
return findGroup(item.pages, route) ?? item;
|
|
3226
|
+
}
|
|
3227
|
+
return void 0;
|
|
3228
|
+
};
|
|
3229
|
+
const firstRoute = (items) => {
|
|
3230
|
+
for (const item of items) {
|
|
3231
|
+
if (typeof item === "string") return item;
|
|
3232
|
+
const candidate = firstRoute(item.pages);
|
|
3233
|
+
if (candidate) return candidate;
|
|
3234
|
+
}
|
|
3235
|
+
return void 0;
|
|
3236
|
+
};
|
|
3237
|
+
for (const page of pages) {
|
|
3238
|
+
if (!sourceRoutes.has(page.navigationId)) continue;
|
|
3239
|
+
const group = config.tabs.map((tab) => findGroup([
|
|
3240
|
+
...tab.pages ?? [],
|
|
3241
|
+
...tab.groups ?? []
|
|
3242
|
+
], page.navigationId)).find(Boolean);
|
|
3243
|
+
if (!group) {
|
|
3244
|
+
page.body = page.body.replace(/<div data-thally-doc-card-list=""\s*\/>/g, "");
|
|
3245
|
+
continue;
|
|
3246
|
+
}
|
|
3247
|
+
const cards = group.pages.flatMap((item) => {
|
|
3248
|
+
if (typeof item === "string") {
|
|
3249
|
+
if (item === page.navigationId) return [];
|
|
3250
|
+
const target = pageByRoute.get(item);
|
|
3251
|
+
return [{ title: target?.title ?? item.split("/").at(-1) ?? item, route: item }];
|
|
3252
|
+
}
|
|
3253
|
+
const slug2 = slugifySegment(item.group);
|
|
3254
|
+
const routes = [...item.pages].flatMap((child) => typeof child === "string" ? [child] : []);
|
|
3255
|
+
const route = routes.find((candidate) => candidate === slug2 || candidate.endsWith(`/${slug2}`)) ?? firstRoute(item.pages);
|
|
3256
|
+
return route ? [{ title: item.group, route }] : [];
|
|
3257
|
+
});
|
|
3258
|
+
const markup = cards.length ? `<CardGroup>
|
|
3259
|
+
${cards.map(({ title, route }) => ` <Card title={${JSON.stringify(title)}} href={${JSON.stringify(`/${route === "introduction" ? "" : route}`)}} />`).join("\n")}
|
|
3260
|
+
</CardGroup>` : "";
|
|
3261
|
+
page.body = page.body.replace(/<div data-thally-doc-card-list=""\s*\/>/g, markup);
|
|
3262
|
+
if (markup && !page.body.includes(markup)) page.body = [page.body.trim(), markup].filter(Boolean).join("\n\n");
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3100
3265
|
function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
3101
3266
|
const routes = /* @__PURE__ */ new Map();
|
|
3102
3267
|
for (const descriptor of descriptors) {
|
|
@@ -3107,18 +3272,30 @@ function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
|
3107
3272
|
const sourceRoute = pageIdFromReference(sourceKey);
|
|
3108
3273
|
if (sourceRoute) routes.set(sourceRoute, descriptor.navigationId);
|
|
3109
3274
|
}
|
|
3110
|
-
const currentDirectory =
|
|
3275
|
+
const currentDirectory = posix2.dirname(sourceReferenceKey(current.sourcePath));
|
|
3276
|
+
function copiedAssetTarget(target) {
|
|
3277
|
+
if (!target.startsWith("/") || target.startsWith("//") || target.includes("\\")) return null;
|
|
3278
|
+
const suffixIndex = target.search(/[?#]/);
|
|
3279
|
+
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
3280
|
+
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
3281
|
+
if (path.startsWith("/docs/") && options.assetPaths?.has(path.slice("/docs/".length))) {
|
|
3282
|
+
return `/${path.slice("/docs/".length)}${suffix}`;
|
|
3283
|
+
}
|
|
3284
|
+
return options.assetPaths?.has(path.slice(1)) ? target : null;
|
|
3285
|
+
}
|
|
3111
3286
|
function rewriteTarget(target) {
|
|
3112
3287
|
if (!target || target.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
|
|
3113
3288
|
const suffixIndex = target.search(/[?#]/);
|
|
3114
3289
|
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
3115
3290
|
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
3291
|
+
const copiedAsset = copiedAssetTarget(target);
|
|
3292
|
+
if (copiedAsset) return copiedAsset;
|
|
3116
3293
|
const candidates = path.startsWith("/") ? [
|
|
3117
3294
|
sourceReferenceKey(path),
|
|
3118
3295
|
sourceReferenceKey(path).replace(/^docs\//, ""),
|
|
3119
3296
|
sourceReferenceKey(path).replace(/^docs\/(?:next|current|latest)\//, "")
|
|
3120
3297
|
] : [
|
|
3121
|
-
sourceReferenceKey(
|
|
3298
|
+
sourceReferenceKey(posix2.normalize(posix2.join(currentDirectory, path))),
|
|
3122
3299
|
sourceReferenceKey(path)
|
|
3123
3300
|
];
|
|
3124
3301
|
const route = candidates.map((candidate) => routes.get(candidate)).find(Boolean);
|
|
@@ -3140,7 +3317,7 @@ function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
|
|
|
3140
3317
|
return line;
|
|
3141
3318
|
}
|
|
3142
3319
|
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) => {
|
|
3320
|
+
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
3321
|
const bracketed = destination.startsWith("<") && destination.endsWith(">");
|
|
3145
3322
|
const target = bracketed ? destination.slice(1, -1) : destination;
|
|
3146
3323
|
const rewritten = rewriteTarget(target);
|
|
@@ -3418,7 +3595,7 @@ function readDocusaurusRedirects(repositoryRoot, warnings) {
|
|
|
3418
3595
|
}
|
|
3419
3596
|
function readCategoryMetadata(contentRoot, directory) {
|
|
3420
3597
|
for (const filename of CATEGORY_FILENAMES) {
|
|
3421
|
-
const path = resolveWithin(contentRoot,
|
|
3598
|
+
const path = resolveWithin(contentRoot, posix2.join(directory, filename));
|
|
3422
3599
|
if (!existsSync3(path) || !lstatSync3(path).isFile()) continue;
|
|
3423
3600
|
try {
|
|
3424
3601
|
const raw = readBoundedText(path);
|
|
@@ -3463,7 +3640,7 @@ function generatedIndexPage(label, linkValue, context) {
|
|
|
3463
3640
|
if (link.type !== "generated-index") return null;
|
|
3464
3641
|
const fallback = `category/${slugifySegment(label)}`;
|
|
3465
3642
|
const unprefixedNavigationId = typeof link.slug === "string" ? routeFromSlug(link.slug, fallback) ?? fallback : fallback;
|
|
3466
|
-
const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ?
|
|
3643
|
+
const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ? posix2.join(context.routePrefix, unprefixedNavigationId) : unprefixedNavigationId;
|
|
3467
3644
|
if (!context.generatedIds.has(navigationId) && !context.descriptors.some((page) => page.navigationId === navigationId)) {
|
|
3468
3645
|
const description = typeof link.description === "string" ? link.description : `Browse the ${label} documentation.`;
|
|
3469
3646
|
context.generatedPages.push({
|
|
@@ -3601,7 +3778,7 @@ function staticStringField(source, name, bindings) {
|
|
|
3601
3778
|
}
|
|
3602
3779
|
}
|
|
3603
3780
|
function readDocusaurusSiteSettings(repositoryRoot) {
|
|
3604
|
-
const empty = { docsRouteBasePath: "docs", navbarLinks: [], footerLinks: [] };
|
|
3781
|
+
const empty = { docsRouteBasePath: "docs", defaultColorMode: "light", navbarLinks: [], footerLinks: [] };
|
|
3605
3782
|
const configPath = findDocusaurusConfigPath(repositoryRoot);
|
|
3606
3783
|
if (!configPath) return empty;
|
|
3607
3784
|
const source = readBoundedText(configPath);
|
|
@@ -3611,6 +3788,10 @@ function readDocusaurusSiteSettings(repositoryRoot) {
|
|
|
3611
3788
|
const docsRouteBasePath = classicDocs && staticStringField(classicDocs, "routeBasePath") || "docs";
|
|
3612
3789
|
const themeStart = propertyStart(source, "themeConfig");
|
|
3613
3790
|
const theme = themeStart < 0 ? null : matchingObjectLiteral(source, themeStart);
|
|
3791
|
+
const colorModeStart = theme ? propertyStart(theme, "colorMode") : -1;
|
|
3792
|
+
const colorMode = colorModeStart < 0 ? null : matchingObjectLiteral(theme, colorModeStart);
|
|
3793
|
+
const configuredColorMode = colorMode ? staticStringField(colorMode, "defaultMode") : void 0;
|
|
3794
|
+
const defaultColorMode = configuredColorMode === "dark" ? "dark" : "light";
|
|
3614
3795
|
const navbarStart = theme ? propertyStart(theme, "navbar") : -1;
|
|
3615
3796
|
const navbar = navbarStart < 0 ? null : matchingObjectLiteral(theme, navbarStart);
|
|
3616
3797
|
const logoStart = navbar ? propertyStart(navbar, "logo") : -1;
|
|
@@ -3646,6 +3827,8 @@ function readDocusaurusSiteSettings(repositoryRoot) {
|
|
|
3646
3827
|
return {
|
|
3647
3828
|
name: staticStringField(source, "title"),
|
|
3648
3829
|
description: staticStringField(source, "tagline"),
|
|
3830
|
+
...navbar && staticStringField(navbar, "title") ? { navbarTitle: staticStringField(navbar, "title") } : {},
|
|
3831
|
+
defaultColorMode,
|
|
3649
3832
|
docsRouteBasePath,
|
|
3650
3833
|
...logo && staticStringField(logo, "src") ? {
|
|
3651
3834
|
logo: {
|
|
@@ -3670,32 +3853,47 @@ function autogeneratedItems(dirName, context) {
|
|
|
3670
3853
|
const childDirectories = /* @__PURE__ */ new Set();
|
|
3671
3854
|
for (const descriptor of inDirectory) {
|
|
3672
3855
|
const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
|
|
3673
|
-
const relativePath = normalizedDir ?
|
|
3856
|
+
const relativePath = normalizedDir ? posix2.relative(normalizedDir, source) : source;
|
|
3674
3857
|
const segments = relativePath.split("/").filter(Boolean);
|
|
3675
3858
|
if (segments.length <= 1) directPages.push(descriptor);
|
|
3676
3859
|
else childDirectories.add(segments[0]);
|
|
3677
3860
|
}
|
|
3678
|
-
const
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3861
|
+
const pages = directPages.map((descriptor) => ({
|
|
3862
|
+
// An index document is the category landing page in Docusaurus. Thally
|
|
3863
|
+
// renders it as a child link, so put it before the category's other docs.
|
|
3864
|
+
position: descriptor.sidebarPosition ?? (/(?:^|\/)(?:index|readme)\.mdx?$/i.test(descriptor.sourcePath) ? -1 : Number.MAX_SAFE_INTEGER),
|
|
3865
|
+
sortKey: descriptor.sourcePath,
|
|
3866
|
+
item: descriptor.navigationId
|
|
3867
|
+
}));
|
|
3682
3868
|
const groups = [...childDirectories].map((segment) => {
|
|
3683
|
-
const directory = normalizedDir ?
|
|
3869
|
+
const directory = normalizedDir ? posix2.join(normalizedDir, segment) : segment;
|
|
3684
3870
|
const metadata = readCategoryMetadata(context.contentRoot, directory);
|
|
3685
|
-
const
|
|
3871
|
+
const pages2 = autogeneratedItems(directory, context);
|
|
3872
|
+
if (pages2.length === 1 && typeof pages2[0] === "string" && !metadata.label && metadata.position === void 0 && !metadata.link) {
|
|
3873
|
+
const onlyPage = context.descriptors.find((descriptor) => descriptor.navigationId === pages2[0]);
|
|
3874
|
+
if (onlyPage && /(?:^|\/)(?:index|readme)\.mdx?$/i.test(onlyPage.sourcePath)) {
|
|
3875
|
+
return {
|
|
3876
|
+
position: onlyPage.sidebarPosition ?? Number.MAX_SAFE_INTEGER,
|
|
3877
|
+
sortKey: segment,
|
|
3878
|
+
item: pages2[0]
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3686
3882
|
const landing = generatedIndexPage(metadata.label ?? titleCase2(segment), metadata.link, context);
|
|
3687
|
-
if (landing && !
|
|
3883
|
+
if (landing && !pages2.includes(landing)) pages2.unshift(landing);
|
|
3688
3884
|
return {
|
|
3689
3885
|
position: metadata.position ?? Number.MAX_SAFE_INTEGER,
|
|
3690
|
-
segment,
|
|
3691
|
-
|
|
3886
|
+
sortKey: segment,
|
|
3887
|
+
item: {
|
|
3692
3888
|
group: metadata.label ?? titleCase2(segment),
|
|
3693
|
-
pages
|
|
3889
|
+
pages: pages2
|
|
3694
3890
|
}
|
|
3695
3891
|
};
|
|
3696
|
-
})
|
|
3697
|
-
|
|
3698
|
-
|
|
3892
|
+
});
|
|
3893
|
+
return [...pages, ...groups].sort((left, right) => left.position - right.position || left.sortKey.localeCompare(right.sortKey, void 0, { numeric: true })).map(({ item }) => {
|
|
3894
|
+
if (typeof item === "string") context.referencedNavigationIds.add(item);
|
|
3895
|
+
return item;
|
|
3896
|
+
});
|
|
3699
3897
|
}
|
|
3700
3898
|
function convertItems(value, context) {
|
|
3701
3899
|
if (typeof value === "string") {
|
|
@@ -3984,11 +4182,23 @@ function convertNode(node, parentSegments, context) {
|
|
|
3984
4182
|
};
|
|
3985
4183
|
}
|
|
3986
4184
|
if (typeof object.link === "string") {
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4185
|
+
const label = object.link.trim();
|
|
4186
|
+
const rawHref = typeof object.href === "string" ? object.href.trim() : "";
|
|
4187
|
+
let href;
|
|
4188
|
+
if (/^https?:\/\//i.test(rawHref) && !/[\\\u0000-\u0020\u007f]/.test(rawHref)) {
|
|
4189
|
+
try {
|
|
4190
|
+
const url = new URL(rawHref);
|
|
4191
|
+
if (["http:", "https:"].includes(url.protocol) && !url.username && !url.password) href = url.toString();
|
|
4192
|
+
} catch {
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
if (label && href && object.hidden !== true && !context.hiddenInherited) {
|
|
4196
|
+
if (!context.externalLinks.some((link) => link.label === label && link.href === href)) {
|
|
4197
|
+
context.externalLinks.push({ label, href });
|
|
4198
|
+
}
|
|
4199
|
+
} else if (!href && object.hidden !== true && !context.hiddenInherited) {
|
|
4200
|
+
warnOnce2(context, `fern-link-${rawHref}`, `Fern navigation link "${label}" has an unsafe or invalid external URL and was skipped.`);
|
|
4201
|
+
}
|
|
3992
4202
|
return null;
|
|
3993
4203
|
}
|
|
3994
4204
|
if (typeof object.api === "string") {
|
|
@@ -4189,6 +4399,7 @@ function projectFernNavigation(input) {
|
|
|
4189
4399
|
changelogIndexes: [],
|
|
4190
4400
|
warnings: [],
|
|
4191
4401
|
warningKeys: /* @__PURE__ */ new Set(),
|
|
4402
|
+
externalLinks: [],
|
|
4192
4403
|
bareRouteRedirects: [],
|
|
4193
4404
|
segmentAliases: /* @__PURE__ */ new Map(),
|
|
4194
4405
|
pathPrefix: "",
|
|
@@ -4209,6 +4420,11 @@ function projectFernNavigation(input) {
|
|
|
4209
4420
|
const label = typeof link?.text === "string" ? link.text : typeof link?.label === "string" ? link.label : link?.type === "github" ? "GitHub" : void 0;
|
|
4210
4421
|
return href && label ? [{ label, href, ...link?.type === "github" ? { type: "github" } : {} }] : [];
|
|
4211
4422
|
}) : [];
|
|
4423
|
+
for (const link of context.externalLinks) {
|
|
4424
|
+
if (!navbarLinks.some((existing) => existing.label === link.label && existing.href === link.href)) {
|
|
4425
|
+
navbarLinks.push(link);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4212
4428
|
const announcement = objectValue3(config.announcement);
|
|
4213
4429
|
const announcementMessage = typeof announcement?.message === "string" ? announcement.message.trim() : "";
|
|
4214
4430
|
const bannerContent = announcementMarkdown(announcementMessage);
|
|
@@ -4942,7 +5158,7 @@ function mintlifyTopLevelApiReferences(config) {
|
|
|
4942
5158
|
}
|
|
4943
5159
|
return references;
|
|
4944
5160
|
}
|
|
4945
|
-
function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
5161
|
+
function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
4946
5162
|
if (!mintlifyConfig) return [];
|
|
4947
5163
|
const references = [
|
|
4948
5164
|
...mintlifyTopLevelApiReferences(mintlifyConfig),
|
|
@@ -4962,13 +5178,19 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings) {
|
|
|
4962
5178
|
});
|
|
4963
5179
|
continue;
|
|
4964
5180
|
}
|
|
4965
|
-
if (/^https
|
|
5181
|
+
if (/^https:\/\//i.test(reference.value)) {
|
|
5182
|
+
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {} });
|
|
4966
5183
|
warnings.push({
|
|
4967
5184
|
code: "unsupported-config",
|
|
4968
|
-
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix}
|
|
5185
|
+
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
5186
|
+
source: reference.value
|
|
4969
5187
|
});
|
|
4970
5188
|
continue;
|
|
4971
5189
|
}
|
|
5190
|
+
if (/^http:\/\//i.test(reference.value)) {
|
|
5191
|
+
warnings.push({ code: "unsupported-config", message: `The OpenAPI spec "${reference.value}"${tabSuffix} uses insecure HTTP and was not imported.`, source: reference.value });
|
|
5192
|
+
continue;
|
|
5193
|
+
}
|
|
4972
5194
|
const key = reference.value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/");
|
|
4973
5195
|
const match = files.find((file) => file.relativePath === key);
|
|
4974
5196
|
if (!match) {
|
|
@@ -5364,9 +5586,9 @@ function addDatedHeadingAliases(body, date) {
|
|
|
5364
5586
|
if (open) return [line];
|
|
5365
5587
|
const heading = line.match(/^#{2,6}\s+(.+?)\s*#*\s*$/);
|
|
5366
5588
|
if (!heading) return [line];
|
|
5367
|
-
const
|
|
5368
|
-
const id = `${date}-${
|
|
5369
|
-
if (!
|
|
5589
|
+
const slug2 = heading[1].replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_]/g, "").normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").trim().replace(/\s+/g, "-");
|
|
5590
|
+
const id = `${date}-${slug2}`;
|
|
5591
|
+
if (!slug2 || existingIds.has(id)) return [line];
|
|
5370
5592
|
existingIds.add(id);
|
|
5371
5593
|
return [`<a id="${id}" />`, "", line];
|
|
5372
5594
|
}).join("\n");
|
|
@@ -5757,6 +5979,7 @@ function migrateRepository(options) {
|
|
|
5757
5979
|
}
|
|
5758
5980
|
const pages = [];
|
|
5759
5981
|
const assets = [];
|
|
5982
|
+
const remoteApiSpecs = [];
|
|
5760
5983
|
const referencedAssetPaths = /* @__PURE__ */ new Map();
|
|
5761
5984
|
const addAssetReference = (assetPath, referencingPage) => {
|
|
5762
5985
|
const referrers = referencedAssetPaths.get(assetPath);
|
|
@@ -5764,6 +5987,7 @@ function migrateRepository(options) {
|
|
|
5764
5987
|
else referencedAssetPaths.set(assetPath, /* @__PURE__ */ new Set([referencingPage]));
|
|
5765
5988
|
};
|
|
5766
5989
|
const docusaurusDescriptors = [];
|
|
5990
|
+
const docusaurusDocCardRoutes = /* @__PURE__ */ new Set();
|
|
5767
5991
|
const seenPageIds = /* @__PURE__ */ new Set();
|
|
5768
5992
|
const fernIdRenames = /* @__PURE__ */ new Map();
|
|
5769
5993
|
let skipped = 0;
|
|
@@ -5858,6 +6082,10 @@ function migrateRepository(options) {
|
|
|
5858
6082
|
}
|
|
5859
6083
|
raw = escapeFernLiteralBraces(protectedMath.body);
|
|
5860
6084
|
}
|
|
6085
|
+
const hasDocusaurusDocCardList = platform === "docusaurus" && /<DocCardList\b[^>]*\/>/.test(raw);
|
|
6086
|
+
if (hasDocusaurusDocCardList) {
|
|
6087
|
+
raw = raw.replace(/<DocCardList\b[^>]*\/>/g, '<div data-thally-doc-card-list="" />');
|
|
6088
|
+
}
|
|
5861
6089
|
let docusaurusDescriptor;
|
|
5862
6090
|
const page = parseMarkdownPage({
|
|
5863
6091
|
id,
|
|
@@ -5891,9 +6119,9 @@ function migrateRepository(options) {
|
|
|
5891
6119
|
// hierarchy (never just its own segment) and wins over a docs.yml slug.
|
|
5892
6120
|
...platform === "fern" ? {
|
|
5893
6121
|
resolveIdentity: (frontmatter, fallback) => {
|
|
5894
|
-
const
|
|
5895
|
-
if (!
|
|
5896
|
-
const overridden = pageIdFromReference(
|
|
6122
|
+
const slug2 = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
6123
|
+
if (!slug2) return fallback;
|
|
6124
|
+
const overridden = pageIdFromReference(slug2, true);
|
|
5897
6125
|
if (!overridden) return fallback;
|
|
5898
6126
|
fernIdRenames.set(fallback.navigationId, overridden);
|
|
5899
6127
|
return { ...fallback, id: overridden, navigationId: overridden };
|
|
@@ -5918,6 +6146,9 @@ function migrateRepository(options) {
|
|
|
5918
6146
|
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, fernProjectRoot, (assetPath) => {
|
|
5919
6147
|
addAssetReference(assetPath, file.relativePath);
|
|
5920
6148
|
});
|
|
6149
|
+
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, repositoryDir, (assetPath) => {
|
|
6150
|
+
addAssetReference(assetPath, file.relativePath);
|
|
6151
|
+
});
|
|
5921
6152
|
page.body = stripFernBasePathFromLinks(page.body, fernBasePath);
|
|
5922
6153
|
page.body = stripFernBasePathFromLinks(page.body, fernVersionPath);
|
|
5923
6154
|
}
|
|
@@ -5972,6 +6203,9 @@ function migrateRepository(options) {
|
|
|
5972
6203
|
}
|
|
5973
6204
|
seenPageIds.add(page.id);
|
|
5974
6205
|
pages.push(page);
|
|
6206
|
+
if (hasDocusaurusDocCardList) {
|
|
6207
|
+
docusaurusDocCardRoutes.add(page.navigationId);
|
|
6208
|
+
}
|
|
5975
6209
|
if (platform === "mintlify") {
|
|
5976
6210
|
const sourcePath = exactReferenceKey(file.relativePath);
|
|
5977
6211
|
if (sourcePath !== page.id && /^[A-Za-z0-9_./-]+$/.test(sourcePath)) {
|
|
@@ -5989,6 +6223,43 @@ function migrateRepository(options) {
|
|
|
5989
6223
|
}
|
|
5990
6224
|
}
|
|
5991
6225
|
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
6226
|
+
const fernBrandAssetPaths = /* @__PURE__ */ new Map();
|
|
6227
|
+
const fernReferencedAssets = [];
|
|
6228
|
+
if (platform === "fern" && fernProjectRoot) {
|
|
6229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6230
|
+
const addFernAsset = (candidate, source) => {
|
|
6231
|
+
try {
|
|
6232
|
+
const absolutePath = source ? resolveWithinRoot(fernProjectRoot, source.replace(/^\/+/, ""), repositoryDir) : resolveWithin(repositoryDir, candidate);
|
|
6233
|
+
if (!existsSync5(absolutePath) || !lstatSync5(absolutePath).isFile()) return;
|
|
6234
|
+
const realRoot = realpathSync2(repositoryDir);
|
|
6235
|
+
const realPath = realpathSync2(absolutePath);
|
|
6236
|
+
const confined = relative5(realRoot, realPath);
|
|
6237
|
+
if (confined === ".." || confined.startsWith(`..${sep2}`) || isAbsolute2(confined)) return;
|
|
6238
|
+
const withinFern = relative5(fernProjectRoot, absolutePath);
|
|
6239
|
+
const assetRelative = withinFern !== ".." && !withinFern.startsWith(`..${sep2}`) && !isAbsolute2(withinFern) ? withinFern : relative5(repositoryDir, absolutePath);
|
|
6240
|
+
const assetPath = normalizeAssetPath(assetRelative.replace(/\\/g, "/"));
|
|
6241
|
+
if (!assetPath || !ASSET_EXTENSIONS.has(extname5(assetPath).toLowerCase())) return;
|
|
6242
|
+
if (source) {
|
|
6243
|
+
fernBrandAssetPaths.set(source, assetPath);
|
|
6244
|
+
addAssetReference(assetPath, "fern/docs.yml");
|
|
6245
|
+
}
|
|
6246
|
+
if (seen.has(absolutePath)) return;
|
|
6247
|
+
seen.add(absolutePath);
|
|
6248
|
+
fernReferencedAssets.push({ absolutePath, relativePath: assetPath });
|
|
6249
|
+
} catch {
|
|
6250
|
+
}
|
|
6251
|
+
};
|
|
6252
|
+
for (const path of referencedAssetPaths.keys()) addFernAsset(path);
|
|
6253
|
+
for (const value of [fernRawConfig?.logo, fernRawConfig?.favicon]) {
|
|
6254
|
+
if (typeof value === "string") addFernAsset("", value);
|
|
6255
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6256
|
+
const variants = value;
|
|
6257
|
+
for (const candidate of [variants.light, variants.dark, variants.src, variants.srcDark]) {
|
|
6258
|
+
if (typeof candidate === "string") addFernAsset("", candidate);
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
5992
6263
|
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
5993
6264
|
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
5994
6265
|
if (!existsSync5(root) || !lstatSync5(root).isDirectory()) return [];
|
|
@@ -5998,10 +6269,10 @@ function migrateRepository(options) {
|
|
|
5998
6269
|
}));
|
|
5999
6270
|
}) : [];
|
|
6000
6271
|
const assetCandidates = [];
|
|
6001
|
-
for (const file of [...files, ...repositoryAssets]) {
|
|
6272
|
+
for (const file of [...files, ...repositoryAssets, ...fernReferencedAssets]) {
|
|
6002
6273
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
6003
6274
|
if (!ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase())) continue;
|
|
6004
|
-
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
|
|
6275
|
+
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment) && !(platform === "fern" && referencedAssetPaths.has(file.relativePath))) continue;
|
|
6005
6276
|
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
6006
6277
|
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
6007
6278
|
if (!assetPath) continue;
|
|
@@ -6047,6 +6318,7 @@ function migrateRepository(options) {
|
|
|
6047
6318
|
routePrefix: options.docusaurusRoutePrefix
|
|
6048
6319
|
});
|
|
6049
6320
|
docsConfig = projected.docsConfig;
|
|
6321
|
+
expandDocusaurusDocCardLists(pages, docsConfig, docusaurusDocCardRoutes);
|
|
6050
6322
|
warnings.push(...projected.warnings);
|
|
6051
6323
|
for (const page of projected.generatedPages) {
|
|
6052
6324
|
if (seenPageIds.has(page.id)) continue;
|
|
@@ -6062,7 +6334,8 @@ function migrateRepository(options) {
|
|
|
6062
6334
|
const descriptor = descriptorByNavigationId.get(page.navigationId);
|
|
6063
6335
|
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors, {
|
|
6064
6336
|
sourceOrigin,
|
|
6065
|
-
onExternalLink: (target) => externalizedLinks.add(target)
|
|
6337
|
+
onExternalLink: (target) => externalizedLinks.add(target),
|
|
6338
|
+
assetPaths: new Set(assets.map((asset) => asset.path))
|
|
6066
6339
|
});
|
|
6067
6340
|
}
|
|
6068
6341
|
if (externalizedLinks.size > 0) warnings.push({
|
|
@@ -6153,7 +6426,7 @@ function migrateRepository(options) {
|
|
|
6153
6426
|
}
|
|
6154
6427
|
if (resolvedSpecs.length > 0) docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs);
|
|
6155
6428
|
} else if (platform === "mintlify") {
|
|
6156
|
-
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings);
|
|
6429
|
+
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
|
|
6157
6430
|
for (const spec of resolvedSpecs) {
|
|
6158
6431
|
if (!assets.some((asset) => asset.path === spec.filename)) {
|
|
6159
6432
|
assets.push({ path: spec.filename, content: spec.content });
|
|
@@ -6234,6 +6507,15 @@ function migrateRepository(options) {
|
|
|
6234
6507
|
}
|
|
6235
6508
|
}
|
|
6236
6509
|
if (platform === "docusaurus") {
|
|
6510
|
+
const copiedAssets = new Set(assets.map((asset) => asset.path));
|
|
6511
|
+
for (const page of pages) {
|
|
6512
|
+
page.body = rewriteDocusaurusLinks(page.body, {
|
|
6513
|
+
sourcePath: page.source ?? page.navigationId,
|
|
6514
|
+
docId: page.navigationId,
|
|
6515
|
+
navigationId: page.navigationId,
|
|
6516
|
+
title: page.title
|
|
6517
|
+
}, [], { assetPaths: copiedAssets });
|
|
6518
|
+
}
|
|
6237
6519
|
const importedRoutes = new Set(pages.map((page) => page.navigationId));
|
|
6238
6520
|
docsConfig.tabs = docsConfig.tabs.map((tab) => {
|
|
6239
6521
|
if (tab.href) return tab;
|
|
@@ -6293,6 +6575,27 @@ ${body}`;
|
|
|
6293
6575
|
}
|
|
6294
6576
|
}
|
|
6295
6577
|
docsConfig = pruneMissingNavigationPages(docsConfig, new Set(pages.map((page) => page.navigationId)));
|
|
6578
|
+
if (platform === "docusaurus") addDocusaurusTranslatedHeadingAliases(pages);
|
|
6579
|
+
if (platform === "fern" && fernProjectRoot) {
|
|
6580
|
+
const sourcePath = (page) => {
|
|
6581
|
+
const fragment = page.source.split("#", 2)[1];
|
|
6582
|
+
if (!fragment) return null;
|
|
6583
|
+
try {
|
|
6584
|
+
return relative5(fernProjectRoot, resolveWithin(repositoryDir, decodeURIComponent(fragment))).replace(/\\/g, "/");
|
|
6585
|
+
} catch {
|
|
6586
|
+
return null;
|
|
6587
|
+
}
|
|
6588
|
+
};
|
|
6589
|
+
const routes = /* @__PURE__ */ new Map();
|
|
6590
|
+
for (const page of pages) {
|
|
6591
|
+
const source = sourcePath(page);
|
|
6592
|
+
if (source) routes.set(source, page.id);
|
|
6593
|
+
}
|
|
6594
|
+
for (const page of pages) {
|
|
6595
|
+
const source = sourcePath(page);
|
|
6596
|
+
if (source) page.body = rewriteFernRelativePageLinks(page.body, source, routes);
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6296
6599
|
preserveLinkedAnchors(pages);
|
|
6297
6600
|
if (platform === "fern" && fernRawConfig) {
|
|
6298
6601
|
const sourceSiteUrl = fernSourceSiteUrl(fernRawConfig);
|
|
@@ -6410,7 +6713,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6410
6713
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
6411
6714
|
const publicBrandPath = (value) => {
|
|
6412
6715
|
if (typeof value !== "string" || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(value)) return void 0;
|
|
6413
|
-
const normalized = normalizeAssetPath(value);
|
|
6716
|
+
const normalized = fernBrandAssetPaths.get(value) ?? normalizeAssetPath(value);
|
|
6414
6717
|
return normalized && assetPaths.has(normalized) ? `/${normalized}` : void 0;
|
|
6415
6718
|
};
|
|
6416
6719
|
const brandVariants = (value) => {
|
|
@@ -6445,6 +6748,34 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6445
6748
|
}
|
|
6446
6749
|
}
|
|
6447
6750
|
};
|
|
6751
|
+
if (platform === "docusaurus" && docusaurusSettings?.navbarTitle && !logoLight) {
|
|
6752
|
+
docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
|
|
6753
|
+
}
|
|
6754
|
+
if (mintlifyConfig) {
|
|
6755
|
+
const appearance = mintlifyConfig.appearance && typeof mintlifyConfig.appearance === "object" ? mintlifyConfig.appearance : null;
|
|
6756
|
+
const defaultMode = appearance?.default;
|
|
6757
|
+
if (defaultMode === "light" || defaultMode === "dark" || defaultMode === "system") {
|
|
6758
|
+
docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, default: defaultMode } };
|
|
6759
|
+
}
|
|
6760
|
+
const background = mintlifyConfig.background && typeof mintlifyConfig.background === "object" ? mintlifyConfig.background : null;
|
|
6761
|
+
if (background) {
|
|
6762
|
+
const decoration = background.decoration;
|
|
6763
|
+
const image = publicBrandPath(background.image);
|
|
6764
|
+
const imageDark = publicBrandPath(background.imageDark);
|
|
6765
|
+
docsConfig = {
|
|
6766
|
+
...docsConfig,
|
|
6767
|
+
background: {
|
|
6768
|
+
...docsConfig.background,
|
|
6769
|
+
...decoration === "none" || decoration === "grid" || decoration === "gradient" ? { decoration } : {},
|
|
6770
|
+
...image ? { image } : {},
|
|
6771
|
+
...imageDark ? { imageDark } : {}
|
|
6772
|
+
}
|
|
6773
|
+
};
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
if (platform === "docusaurus") {
|
|
6777
|
+
docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, default: docusaurusSettings?.defaultColorMode ?? "light" } };
|
|
6778
|
+
}
|
|
6448
6779
|
if (faviconLight) docsConfig = {
|
|
6449
6780
|
...docsConfig,
|
|
6450
6781
|
favicon: { light: faviconLight, ...faviconDark ? { dark: faviconDark } : {} }
|
|
@@ -6467,6 +6798,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6467
6798
|
platform,
|
|
6468
6799
|
pages,
|
|
6469
6800
|
assets,
|
|
6801
|
+
...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
|
|
6470
6802
|
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
6471
6803
|
docsConfig,
|
|
6472
6804
|
...mintlifyConfig ? {
|
|
@@ -6482,7 +6814,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
6482
6814
|
}
|
|
6483
6815
|
} : docusaurusSettings && (docusaurusSettings.name || docusaurusSettings.description || themeColors) ? {
|
|
6484
6816
|
site: {
|
|
6485
|
-
...docusaurusSettings.name ? { name: docusaurusSettings.name } : {},
|
|
6817
|
+
...docusaurusSettings.navbarTitle || docusaurusSettings.name ? { name: docusaurusSettings.navbarTitle ?? docusaurusSettings.name } : {},
|
|
6486
6818
|
...docusaurusSettings.description ? { description: docusaurusSettings.description } : {},
|
|
6487
6819
|
...themeColors ? { colors: themeColors } : {}
|
|
6488
6820
|
}
|
|
@@ -6754,10 +7086,10 @@ async function readResponseBody(response) {
|
|
|
6754
7086
|
}
|
|
6755
7087
|
return new TextDecoder().decode(body);
|
|
6756
7088
|
}
|
|
6757
|
-
var defaultMigrationFetcher = async (url,
|
|
7089
|
+
var defaultMigrationFetcher = async (url, request2) => {
|
|
6758
7090
|
const response = await fetch(url, {
|
|
6759
7091
|
headers: {
|
|
6760
|
-
Accept:
|
|
7092
|
+
Accept: request2.accept,
|
|
6761
7093
|
"User-Agent": "Thally-Migrate/1.0 (+https://thally.io)"
|
|
6762
7094
|
},
|
|
6763
7095
|
redirect: "follow",
|
|
@@ -8172,6 +8504,202 @@ async function migrateUrl(options) {
|
|
|
8172
8504
|
}
|
|
8173
8505
|
};
|
|
8174
8506
|
}
|
|
8507
|
+
|
|
8508
|
+
// src/remote-api.ts
|
|
8509
|
+
import { createHash as createHash2 } from "crypto";
|
|
8510
|
+
import { lookup } from "dns/promises";
|
|
8511
|
+
import { request } from "https";
|
|
8512
|
+
import { isIP } from "net";
|
|
8513
|
+
import { parse as parseYaml6 } from "yaml";
|
|
8514
|
+
var MAX_SPEC_BYTES = 25e6;
|
|
8515
|
+
var MAX_REDIRECTS = 3;
|
|
8516
|
+
function isPublicAddress(address, family) {
|
|
8517
|
+
if (family === 4) {
|
|
8518
|
+
const parts = address.split(".").map(Number);
|
|
8519
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
8520
|
+
const [a, b] = parts;
|
|
8521
|
+
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);
|
|
8522
|
+
}
|
|
8523
|
+
return family === 6 && /^[23][0-9a-f]{0,3}:/i.test(address) && !/^2001:db8:/i.test(address);
|
|
8524
|
+
}
|
|
8525
|
+
function validatedSpecUrl(value) {
|
|
8526
|
+
const url = new URL(value);
|
|
8527
|
+
if (url.protocol !== "https:" || url.username || url.password || isIP(url.hostname)) {
|
|
8528
|
+
throw new Error("Remote OpenAPI references must use an HTTPS domain without credentials or an IP literal.");
|
|
8529
|
+
}
|
|
8530
|
+
url.hash = "";
|
|
8531
|
+
return url;
|
|
8532
|
+
}
|
|
8533
|
+
async function getPinnedAddress(url) {
|
|
8534
|
+
const addresses = await lookup(url.hostname, { all: true });
|
|
8535
|
+
if (addresses.length === 0 || addresses.some((candidate) => !isPublicAddress(candidate.address, candidate.family))) {
|
|
8536
|
+
throw new Error("The OpenAPI host did not resolve exclusively to public addresses.");
|
|
8537
|
+
}
|
|
8538
|
+
return addresses[0];
|
|
8539
|
+
}
|
|
8540
|
+
async function requestPinnedSpec(url) {
|
|
8541
|
+
const pinned = await getPinnedAddress(url);
|
|
8542
|
+
return new Promise((resolve3, reject) => {
|
|
8543
|
+
const req = request(url, {
|
|
8544
|
+
headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
|
|
8545
|
+
signal: AbortSignal.timeout(3e4),
|
|
8546
|
+
// Node's dual-stack connector asks lookup for `all: true`; that
|
|
8547
|
+
// callback must receive address records, not a single address string.
|
|
8548
|
+
lookup: (_hostname, options, callback) => {
|
|
8549
|
+
if (typeof options === "object" && options.all) callback(null, [pinned]);
|
|
8550
|
+
else callback(null, pinned.address, pinned.family);
|
|
8551
|
+
}
|
|
8552
|
+
}, (response) => {
|
|
8553
|
+
const status = response.statusCode ?? 0;
|
|
8554
|
+
if (status >= 300 && status < 400) {
|
|
8555
|
+
response.resume();
|
|
8556
|
+
resolve3({ status, location: response.headers.location, body: Buffer.alloc(0) });
|
|
8557
|
+
return;
|
|
8558
|
+
}
|
|
8559
|
+
if (status < 200 || status >= 300) {
|
|
8560
|
+
response.resume();
|
|
8561
|
+
reject(new Error(`OpenAPI server returned ${status}.`));
|
|
8562
|
+
return;
|
|
8563
|
+
}
|
|
8564
|
+
const statedBytes = Number(response.headers["content-length"] ?? 0);
|
|
8565
|
+
if (statedBytes > MAX_SPEC_BYTES) {
|
|
8566
|
+
response.destroy();
|
|
8567
|
+
reject(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
8568
|
+
return;
|
|
8569
|
+
}
|
|
8570
|
+
const chunks = [];
|
|
8571
|
+
let bytes = 0;
|
|
8572
|
+
response.on("data", (chunk) => {
|
|
8573
|
+
bytes += chunk.length;
|
|
8574
|
+
if (bytes > MAX_SPEC_BYTES) {
|
|
8575
|
+
response.destroy(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
8576
|
+
return;
|
|
8577
|
+
}
|
|
8578
|
+
chunks.push(chunk);
|
|
8579
|
+
});
|
|
8580
|
+
response.on("end", () => resolve3({ status, body: Buffer.concat(chunks) }));
|
|
8581
|
+
response.on("error", reject);
|
|
8582
|
+
});
|
|
8583
|
+
req.on("error", reject);
|
|
8584
|
+
req.end();
|
|
8585
|
+
});
|
|
8586
|
+
}
|
|
8587
|
+
async function downloadSpec(value, fetcher) {
|
|
8588
|
+
let url = validatedSpecUrl(value);
|
|
8589
|
+
if (fetcher) {
|
|
8590
|
+
const response = await fetcher(url, { accept: "application/json, application/yaml, text/yaml" });
|
|
8591
|
+
validatedSpecUrl(response.finalUrl.toString());
|
|
8592
|
+
const body = Buffer.from(response.body);
|
|
8593
|
+
if (body.length > MAX_SPEC_BYTES) throw new Error("Remote OpenAPI spec exceeds the 25 MB import limit.");
|
|
8594
|
+
return body;
|
|
8595
|
+
}
|
|
8596
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
8597
|
+
const response = await requestPinnedSpec(url);
|
|
8598
|
+
if (response.status >= 300 && response.status < 400) {
|
|
8599
|
+
if (!response.location || hop === MAX_REDIRECTS) throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
8600
|
+
url = validatedSpecUrl(new URL(response.location, url).toString());
|
|
8601
|
+
continue;
|
|
8602
|
+
}
|
|
8603
|
+
return response.body;
|
|
8604
|
+
}
|
|
8605
|
+
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
8606
|
+
}
|
|
8607
|
+
function parseOpenApi(content, extension) {
|
|
8608
|
+
const text = content.toString("utf8");
|
|
8609
|
+
const parsed = extension === "yaml" || !text.trimStart().startsWith("{") ? parseYaml6(text) : JSON.parse(text);
|
|
8610
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
8611
|
+
const document = parsed;
|
|
8612
|
+
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
8613
|
+
throw new Error("Remote specification is not OpenAPI 3.x.");
|
|
8614
|
+
}
|
|
8615
|
+
if (!document.paths || typeof document.paths !== "object" || Array.isArray(document.paths)) {
|
|
8616
|
+
throw new Error("Remote OpenAPI spec has no paths object.");
|
|
8617
|
+
}
|
|
8618
|
+
const paths = Object.entries(document.paths);
|
|
8619
|
+
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
8620
|
+
const operations = [];
|
|
8621
|
+
for (const [path, value] of paths) {
|
|
8622
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
8623
|
+
for (const [method, raw] of Object.entries(value)) {
|
|
8624
|
+
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
8625
|
+
const operation = raw;
|
|
8626
|
+
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
8627
|
+
operations.push({
|
|
8628
|
+
method: method.toLowerCase(),
|
|
8629
|
+
path,
|
|
8630
|
+
tag: typeof tags[0] === "string" ? tags[0] : "API Reference",
|
|
8631
|
+
summary: typeof operation.summary === "string" ? operation.summary : String(operation.operationId ?? "")
|
|
8632
|
+
});
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
return operations;
|
|
8636
|
+
}
|
|
8637
|
+
function slug(value) {
|
|
8638
|
+
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
8639
|
+
}
|
|
8640
|
+
function operationTarget(operation) {
|
|
8641
|
+
const path = operation.path.split("/").filter(Boolean).map((part) => part.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").toLowerCase());
|
|
8642
|
+
return `/api/default/${[...path, operation.method].join("/")}`;
|
|
8643
|
+
}
|
|
8644
|
+
async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
8645
|
+
if (!bundle.remoteApiSpecs?.length) return bundle;
|
|
8646
|
+
const assets = [...bundle.assets];
|
|
8647
|
+
const tabs = bundle.docsConfig.tabs.map((tab) => ({ ...tab }));
|
|
8648
|
+
const redirects = [...bundle.docsConfig.redirects ?? []];
|
|
8649
|
+
const warnings = [...bundle.warnings];
|
|
8650
|
+
const authoredLinks = /* @__PURE__ */ new Set();
|
|
8651
|
+
for (const page of bundle.pages) {
|
|
8652
|
+
for (const match of page.body.matchAll(/\/[a-z0-9_/-]+/gi)) {
|
|
8653
|
+
const segments = match[0].split("/").filter(Boolean);
|
|
8654
|
+
if (segments.length < 3) continue;
|
|
8655
|
+
const section = segments[0].toLowerCase();
|
|
8656
|
+
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0]);
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
8659
|
+
for (const reference of bundle.remoteApiSpecs) {
|
|
8660
|
+
for (let index = warnings.length - 1; index >= 0; index--) {
|
|
8661
|
+
if (warnings[index].source === reference.url && warnings[index].message.includes("requires a network download")) warnings.splice(index, 1);
|
|
8662
|
+
}
|
|
8663
|
+
try {
|
|
8664
|
+
const url = validatedSpecUrl(reference.url);
|
|
8665
|
+
const extension = /\.ya?ml$/i.test(url.pathname) ? "yaml" : "json";
|
|
8666
|
+
const body = await downloadSpec(reference.url, fetcher);
|
|
8667
|
+
const operations = parseOpenApi(body, extension);
|
|
8668
|
+
const filename = `openapi-${createHash2("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
8669
|
+
assets.push({ path: filename, content: body });
|
|
8670
|
+
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
8671
|
+
if (tab) tab.api = { source: `/${filename}` };
|
|
8672
|
+
else tabs.push({ tab: reference.tabLabel ?? "API Reference", api: { source: `/${filename}` } });
|
|
8673
|
+
const routes = /* @__PURE__ */ new Map();
|
|
8674
|
+
for (const operation of operations) {
|
|
8675
|
+
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
8676
|
+
if (routes.has(key)) routes.set(key, null);
|
|
8677
|
+
else routes.set(key, operationTarget(operation));
|
|
8678
|
+
}
|
|
8679
|
+
const existingSources = new Set(redirects.map((redirect) => redirect.source));
|
|
8680
|
+
for (const source of authoredLinks) {
|
|
8681
|
+
const parts = source.split("/").filter(Boolean);
|
|
8682
|
+
const key = parts.slice(-2).join("/");
|
|
8683
|
+
const destination = routes.get(key);
|
|
8684
|
+
if (!destination || existingSources.has(source)) continue;
|
|
8685
|
+
redirects.push({ source, destination, permanent: false });
|
|
8686
|
+
existingSources.add(source);
|
|
8687
|
+
}
|
|
8688
|
+
} catch (error) {
|
|
8689
|
+
warnings.push({
|
|
8690
|
+
code: "fetch-failed",
|
|
8691
|
+
source: reference.url,
|
|
8692
|
+
message: `Remote OpenAPI spec could not be imported: ${error instanceof Error ? error.message : String(error)}`
|
|
8693
|
+
});
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
return {
|
|
8697
|
+
...bundle,
|
|
8698
|
+
assets,
|
|
8699
|
+
docsConfig: { ...bundle.docsConfig, tabs, ...redirects.length > 0 ? { redirects } : {} },
|
|
8700
|
+
warnings
|
|
8701
|
+
};
|
|
8702
|
+
}
|
|
8175
8703
|
export {
|
|
8176
8704
|
addMintlifyDirectoryRedirects,
|
|
8177
8705
|
buildNavigationFromPages,
|
|
@@ -8180,6 +8708,7 @@ export {
|
|
|
8180
8708
|
detectRepositoryDocsDir,
|
|
8181
8709
|
detectRepositoryPlatform,
|
|
8182
8710
|
gitmodulePaths,
|
|
8711
|
+
hydrateRemoteApiSpecs,
|
|
8183
8712
|
mergeMigrationConfig,
|
|
8184
8713
|
migrateRepository,
|
|
8185
8714
|
migrateUrl,
|