@thallylabs/migrate 0.1.0 → 0.1.2
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 +14 -1
- package/dist/index.js +471 -31
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -29,7 +29,6 @@ function normalizeMdx(body) {
|
|
|
29
29
|
function parseMarkdownPage(input) {
|
|
30
30
|
const parsed = matter(input.raw);
|
|
31
31
|
const body = normalizeMdx(parsed.content).trim();
|
|
32
|
-
if (parsed.data.openapi && !body) return null;
|
|
33
32
|
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
34
33
|
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(input.navigationId ?? input.id);
|
|
35
34
|
const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
|
|
@@ -40,6 +39,7 @@ function parseMarkdownPage(input) {
|
|
|
40
39
|
title,
|
|
41
40
|
description,
|
|
42
41
|
keywords,
|
|
42
|
+
openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
|
|
43
43
|
body,
|
|
44
44
|
source: input.source
|
|
45
45
|
};
|
|
@@ -308,9 +308,91 @@ function projectMintlifyNavigation(config) {
|
|
|
308
308
|
function titleCase(value) {
|
|
309
309
|
return value.split(/[-_]/).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
310
310
|
}
|
|
311
|
-
|
|
311
|
+
var TOP_LEVEL_LABELS = {
|
|
312
|
+
"api-reference": "API Reference",
|
|
313
|
+
cli: "CLI",
|
|
314
|
+
faqs: "FAQs",
|
|
315
|
+
introduction: "Overview",
|
|
316
|
+
"mcp-server": "MCP Server",
|
|
317
|
+
sdk: "SDK"
|
|
318
|
+
};
|
|
319
|
+
function topLevelLabel(segment) {
|
|
320
|
+
return TOP_LEVEL_LABELS[segment] ?? titleCase(segment);
|
|
321
|
+
}
|
|
322
|
+
function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(section)) {
|
|
323
|
+
const groups = /* @__PURE__ */ new Map();
|
|
324
|
+
for (const id of pageIds) {
|
|
325
|
+
const relative4 = id === "introduction" ? "" : id.startsWith(`${section}/`) ? id.slice(section.length + 1) : id;
|
|
326
|
+
const nestedSegment = relative4.includes("/") ? relative4.split("/", 1)[0] : "overview";
|
|
327
|
+
const group = groups.get(nestedSegment) ?? [];
|
|
328
|
+
group.push(id);
|
|
329
|
+
groups.set(nestedSegment, group);
|
|
330
|
+
}
|
|
331
|
+
return [...groups].map(([segment, pages]) => ({
|
|
332
|
+
group: segment === "overview" ? sectionLabel : titleCase(segment),
|
|
333
|
+
pages
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
function preferredLandingPage(section, pageIds) {
|
|
337
|
+
const candidates = [
|
|
338
|
+
section === "introduction" ? "introduction" : void 0,
|
|
339
|
+
`${section}/overview`,
|
|
340
|
+
`${section}/introduction`,
|
|
341
|
+
section
|
|
342
|
+
];
|
|
343
|
+
return candidates.find((candidate) => Boolean(candidate && pageIds.includes(candidate)));
|
|
344
|
+
}
|
|
345
|
+
function buildNavigationFromPages(pages, options = {}) {
|
|
312
346
|
const ids = pages.filter((page) => !page.locale || page.locale === "en").map((page) => page.navigationId);
|
|
313
347
|
const ordered = [...new Set(ids)];
|
|
348
|
+
if (options.topLevelTabs) {
|
|
349
|
+
const sourceNavigation = (options.topLevelNavigation ?? []).flatMap((entry) => {
|
|
350
|
+
const sectionPages = ordered.filter((id) => id === entry.section || id.startsWith(`${entry.section}/`));
|
|
351
|
+
const pageId = ordered.includes(entry.pageId) ? entry.pageId : preferredLandingPage(entry.section, sectionPages) ?? sectionPages[0];
|
|
352
|
+
return pageId ? [{ ...entry, pageId }] : [];
|
|
353
|
+
});
|
|
354
|
+
if (sourceNavigation.length > 1) {
|
|
355
|
+
const claimedIds = /* @__PURE__ */ new Set();
|
|
356
|
+
const tabs = sourceNavigation.map((entry) => {
|
|
357
|
+
const pageIds = ordered.filter((id) => {
|
|
358
|
+
const matches = id === entry.pageId || id === entry.section || id.startsWith(`${entry.section}/`);
|
|
359
|
+
if (matches) claimedIds.add(id);
|
|
360
|
+
return matches;
|
|
361
|
+
});
|
|
362
|
+
return { entry, pageIds };
|
|
363
|
+
});
|
|
364
|
+
tabs[0].pageIds.push(...ordered.filter((id) => !claimedIds.has(id)));
|
|
365
|
+
return {
|
|
366
|
+
tabs: tabs.map(({ entry, pageIds }) => ({
|
|
367
|
+
tab: entry.label,
|
|
368
|
+
href: entry.pageId === "introduction" ? "/" : `/${entry.pageId}`,
|
|
369
|
+
groups: groupsWithinSection(entry.section, [...new Set(pageIds)], entry.label)
|
|
370
|
+
}))
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const sectionNames = [...new Set(ordered.filter((id) => id.includes("/")).map((id) => id.split("/", 1)[0]))];
|
|
374
|
+
if (sectionNames.length > 1) {
|
|
375
|
+
const defaultSection = sectionNames.includes("introduction") ? "introduction" : sectionNames[0];
|
|
376
|
+
const sections = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const id of ordered) {
|
|
378
|
+
const section = id.includes("/") ? id.split("/", 1)[0] : defaultSection;
|
|
379
|
+
const bucket = sections.get(section) ?? [];
|
|
380
|
+
bucket.push(id);
|
|
381
|
+
sections.set(section, bucket);
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
tabs: [...sections].map(([section, pageIds]) => {
|
|
385
|
+
const label = topLevelLabel(section);
|
|
386
|
+
const landingPage = preferredLandingPage(section, pageIds);
|
|
387
|
+
return {
|
|
388
|
+
tab: label,
|
|
389
|
+
...landingPage ? { href: landingPage === "introduction" ? "/" : `/${landingPage}` } : {},
|
|
390
|
+
groups: groupsWithinSection(section, pageIds)
|
|
391
|
+
};
|
|
392
|
+
})
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
314
396
|
const buckets = /* @__PURE__ */ new Map();
|
|
315
397
|
for (const id of ordered) {
|
|
316
398
|
const segment = id.includes("/") ? id.split("/", 1)[0] : "overview";
|
|
@@ -700,6 +782,7 @@ function renderPage(bundle, page) {
|
|
|
700
782
|
`title: ${yamlString(page.title)}`,
|
|
701
783
|
`description: ${yamlString(page.description)}`,
|
|
702
784
|
page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
|
|
785
|
+
page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
|
|
703
786
|
bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
|
|
704
787
|
"---",
|
|
705
788
|
"",
|
|
@@ -778,6 +861,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
778
861
|
// src/url.ts
|
|
779
862
|
import { load } from "cheerio";
|
|
780
863
|
import TurndownService from "turndown";
|
|
864
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
781
865
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
782
866
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
783
867
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -857,8 +941,12 @@ var LOCALE_CODES = /* @__PURE__ */ new Set([
|
|
|
857
941
|
"tr",
|
|
858
942
|
"uk",
|
|
859
943
|
"vi",
|
|
860
|
-
"zh"
|
|
944
|
+
"zh",
|
|
945
|
+
"pt-br",
|
|
946
|
+
"zh-hans",
|
|
947
|
+
"zh-hant"
|
|
861
948
|
]);
|
|
949
|
+
var PORTABLE_URL_PROPS = "href|src|img|primaryHref|secondaryHref";
|
|
862
950
|
function validateMigrationUrl(value) {
|
|
863
951
|
let url;
|
|
864
952
|
try {
|
|
@@ -921,6 +1009,27 @@ function docsScopePath(source) {
|
|
|
921
1009
|
const segments = path.split("/").filter(Boolean);
|
|
922
1010
|
return `/${segments.length > 1 ? segments[0] : segments.join("/")}`;
|
|
923
1011
|
}
|
|
1012
|
+
function machineIndexScopePath(source, documents) {
|
|
1013
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1014
|
+
for (const document of documents) {
|
|
1015
|
+
if (!document) continue;
|
|
1016
|
+
const references = [...headerLinks(document)];
|
|
1017
|
+
if (/html/i.test(document.contentType)) {
|
|
1018
|
+
const $ = load(document.body);
|
|
1019
|
+
references.push(...$("link[href], a[href]").map((_index, element) => $(element).attr("href") ?? "").get().filter((href) => /\/(?:llms(?:-full)?\.txt|sitemap\.xml)(?:[?#]|$)/i.test(href)));
|
|
1020
|
+
}
|
|
1021
|
+
for (const reference of references) {
|
|
1022
|
+
try {
|
|
1023
|
+
const indexUrl = new URL(reference, document.finalUrl);
|
|
1024
|
+
if (indexUrl.origin !== source.origin) continue;
|
|
1025
|
+
const scope = indexUrl.pathname.replace(/\/(?:llms(?:-full)?\.txt|sitemap\.xml)$/i, "") || "/";
|
|
1026
|
+
if (isInScope(source, source, scope)) candidates.add(scope);
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return [...candidates].sort((left, right) => right.length - left.length)[0] ?? null;
|
|
1032
|
+
}
|
|
924
1033
|
function isInScope(url, source, scopePath) {
|
|
925
1034
|
if (url.origin !== source.origin || !["http:", "https:"].includes(url.protocol)) return false;
|
|
926
1035
|
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
@@ -932,7 +1041,9 @@ function normalizeCandidate(value, base, source, scopePath) {
|
|
|
932
1041
|
const url = new URL(value, base);
|
|
933
1042
|
url.hash = "";
|
|
934
1043
|
if (!isInScope(url, source, scopePath)) return null;
|
|
935
|
-
if (
|
|
1044
|
+
if (url.pathname.startsWith("/cdn-cgi/")) return null;
|
|
1045
|
+
if (/\/(?:llms(?:-full)?|robots)\.txt$/i.test(url.pathname) || /\/sitemap\.xml$/i.test(url.pathname)) return null;
|
|
1046
|
+
if (/\.(?:avif|bmp|gif|ico|jpe?g|mp[34]|pdf|png|svg|ya?ml|webm|webp|zip)$/i.test(url.pathname) || /\/(?:openapi|swagger|asyncapi)[^/]*\.json$/i.test(url.pathname)) return null;
|
|
936
1047
|
return url;
|
|
937
1048
|
} catch {
|
|
938
1049
|
return null;
|
|
@@ -951,9 +1062,19 @@ function pageIdForUrl(url, scopePath) {
|
|
|
951
1062
|
}
|
|
952
1063
|
return pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
|
|
953
1064
|
}
|
|
1065
|
+
function canonicalLocalizedPageId(id) {
|
|
1066
|
+
const [segment, ...rest] = id.split("/");
|
|
1067
|
+
if (LOCALE_CODES.has(segment)) return rest.length > 0 ? id : `${segment}/introduction`;
|
|
1068
|
+
for (const locale of LOCALE_CODES) {
|
|
1069
|
+
if (segment !== `${locale}-api-reference`) continue;
|
|
1070
|
+
return [locale, "api-reference", ...rest].join("/");
|
|
1071
|
+
}
|
|
1072
|
+
return id;
|
|
1073
|
+
}
|
|
954
1074
|
function detectUrlPlatform(document) {
|
|
955
|
-
const
|
|
956
|
-
|
|
1075
|
+
const headers = Object.entries(document.headers ?? {}).map(([key, value2]) => `${key}:${value2 ?? ""}`).join(" ");
|
|
1076
|
+
const value = `${document.body.slice(0, 2e5)} ${headers}`.toLowerCase();
|
|
1077
|
+
if (value.includes("__mintlify") || value.includes("/mintlify-assets/") || value.includes("x-mintlify-") || value.includes("/_mintlify/")) return "mintlify";
|
|
957
1078
|
if (value.includes("docusaurus") || value.includes("__docusaurus")) return "docusaurus";
|
|
958
1079
|
if (value.includes("gitbook") || value.includes("gitbook.io")) return "gitbook";
|
|
959
1080
|
if (value.includes("nextra")) return "nextra";
|
|
@@ -982,10 +1103,122 @@ function withoutFencedCode(body) {
|
|
|
982
1103
|
return fenceCharacter ? "" : line;
|
|
983
1104
|
}).join("\n");
|
|
984
1105
|
}
|
|
1106
|
+
function eventHandlerPropEnd(value, start) {
|
|
1107
|
+
const opening = value[start];
|
|
1108
|
+
if (opening === '"' || opening === "'") {
|
|
1109
|
+
for (let index = start + 1; index < value.length; index += 1) {
|
|
1110
|
+
if (value[index] === opening && value[index - 1] !== "\\") return index + 1;
|
|
1111
|
+
}
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
if (opening !== "{") {
|
|
1115
|
+
const match = value.slice(start).match(/^[^\s>]+/);
|
|
1116
|
+
return match ? start + match[0].length : null;
|
|
1117
|
+
}
|
|
1118
|
+
let depth = 0;
|
|
1119
|
+
let quote = "";
|
|
1120
|
+
for (let index = start; index < value.length; index += 1) {
|
|
1121
|
+
const character = value[index];
|
|
1122
|
+
if (quote) {
|
|
1123
|
+
if (character === quote && value[index - 1] !== "\\") quote = "";
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
1127
|
+
quote = character;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (character === "{") depth += 1;
|
|
1131
|
+
if (character === "}") {
|
|
1132
|
+
depth -= 1;
|
|
1133
|
+
if (depth === 0) return index + 1;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
function stripEventHandlerProps(line) {
|
|
1139
|
+
const matcher = /\s+on[A-Z][A-Za-z0-9]*\s*=\s*/g;
|
|
1140
|
+
let cursor = 0;
|
|
1141
|
+
let result = "";
|
|
1142
|
+
for (const match of line.matchAll(matcher)) {
|
|
1143
|
+
const start = match.index;
|
|
1144
|
+
const valueStart = start + match[0].length;
|
|
1145
|
+
const end = eventHandlerPropEnd(line, valueStart);
|
|
1146
|
+
if (end === null) continue;
|
|
1147
|
+
result += line.slice(cursor, start);
|
|
1148
|
+
cursor = end;
|
|
1149
|
+
}
|
|
1150
|
+
return cursor === 0 ? line : `${result}${line.slice(cursor)}`;
|
|
1151
|
+
}
|
|
1152
|
+
function hasUnsafeUrlScheme(value) {
|
|
1153
|
+
const normalized = value.trim().replace(/[\u0000-\u0020]/g, "").replace(/&(?:#x0*3a|#0*58|colon);/gi, ":");
|
|
1154
|
+
return /^(?:javascript|data|vbscript):/i.test(normalized);
|
|
1155
|
+
}
|
|
1156
|
+
function hasUnsafePortableUrlProp(value) {
|
|
1157
|
+
const quotedProps = new RegExp(`\\b(?:${PORTABLE_URL_PROPS})\\s*=\\s*(['"])([^'"\\r\\n]*)\\1`, "gi");
|
|
1158
|
+
for (const match of value.matchAll(quotedProps)) {
|
|
1159
|
+
if (hasUnsafeUrlScheme(match[2])) return true;
|
|
1160
|
+
}
|
|
1161
|
+
const expressionProps = new RegExp(`\\b(?:${PORTABLE_URL_PROPS})\\s*=\\s*\\{\\s*("(?:\\\\.|[^"\\\\])*")\\s*\\}`, "gi");
|
|
1162
|
+
for (const match of value.matchAll(expressionProps)) {
|
|
1163
|
+
try {
|
|
1164
|
+
if (hasUnsafeUrlScheme(JSON.parse(match[1]))) return true;
|
|
1165
|
+
} catch {
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
function portableHtmlToMarkdown(value) {
|
|
1172
|
+
return value.replace(/<span\b[^>]*>|<\/span>/gi, "").replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_original, attributes, label) => {
|
|
1173
|
+
const href = attributes.match(/\bhref\s*=\s*(['"])(.*?)\1/i)?.[2];
|
|
1174
|
+
const plainLabel = label.replace(/<[^>]+>/g, "").trim();
|
|
1175
|
+
if (!href || hasUnsafeUrlScheme(href)) return plainLabel;
|
|
1176
|
+
return `[${plainLabel.replace(/]/g, "\\]")}](${href})`;
|
|
1177
|
+
}).replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_original, level, content) => {
|
|
1178
|
+
return `
|
|
1179
|
+
${"#".repeat(Number(level))} ${content.trim()}
|
|
1180
|
+
`;
|
|
1181
|
+
}).replace(/<p\b[^>]*>([\s\S]*?)<\/p>/gi, (_original, content) => `
|
|
1182
|
+
${content.trim()}
|
|
1183
|
+
`).replace(/<br\s*\/?\s*>/gi, "\n").replace(/<\/?(?:article|div|main|section)\b[^>]*>/gi, "");
|
|
1184
|
+
}
|
|
1185
|
+
function transformMarkdownProse(body, transform) {
|
|
1186
|
+
const output = [];
|
|
1187
|
+
let prose = [];
|
|
1188
|
+
let fenceCharacter = "";
|
|
1189
|
+
let fenceLength = 0;
|
|
1190
|
+
const flushProse = () => {
|
|
1191
|
+
if (prose.length === 0) return;
|
|
1192
|
+
output.push(transform(prose.join("\n")));
|
|
1193
|
+
prose = [];
|
|
1194
|
+
};
|
|
1195
|
+
for (const line of body.split("\n")) {
|
|
1196
|
+
const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
1197
|
+
if (fence && !fenceCharacter) {
|
|
1198
|
+
flushProse();
|
|
1199
|
+
fenceCharacter = fence[1][0];
|
|
1200
|
+
fenceLength = fence[1].length;
|
|
1201
|
+
output.push(line);
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (fenceCharacter) {
|
|
1205
|
+
output.push(line);
|
|
1206
|
+
if (fence && fence[1][0] === fenceCharacter && fence[1].length >= fenceLength && fence[2].trim() === "") {
|
|
1207
|
+
fenceCharacter = "";
|
|
1208
|
+
fenceLength = 0;
|
|
1209
|
+
}
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
prose.push(line);
|
|
1213
|
+
}
|
|
1214
|
+
flushProse();
|
|
1215
|
+
return output.join("\n");
|
|
1216
|
+
}
|
|
985
1217
|
function sanitizeRemoteMarkdown(body) {
|
|
986
1218
|
let fenceCharacter = "";
|
|
987
1219
|
let fenceLength = 0;
|
|
988
|
-
const
|
|
1220
|
+
const portableBody = transformMarkdownProse(body, portableHtmlToMarkdown);
|
|
1221
|
+
const sanitized = portableBody.split("\n").map((line) => {
|
|
989
1222
|
const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
990
1223
|
if (fence && !fenceCharacter) {
|
|
991
1224
|
fenceCharacter = fence[1][0];
|
|
@@ -998,12 +1231,14 @@ function sanitizeRemoteMarkdown(body) {
|
|
|
998
1231
|
return line;
|
|
999
1232
|
}
|
|
1000
1233
|
if (fenceCharacter) return line;
|
|
1001
|
-
return line.replace(
|
|
1234
|
+
return portableHtmlToMarkdown(stripEventHandlerProps(line.replace(/!\[([^\]]*)\]\(\s*([^)]+)\)/g, (original, alternative, destination) => {
|
|
1235
|
+
return hasUnsafeUrlScheme(destination.replace(/^<|>$/g, "")) ? alternative : original;
|
|
1236
|
+
}).replace(/<img\s+([^>]*?)\/?\s*>/gi, (original, attributes) => {
|
|
1002
1237
|
const source = attributes.match(/\bsrc\s*=\s*(['"])(.*?)\1/i)?.[2];
|
|
1003
1238
|
const alternative = attributes.match(/\balt\s*=\s*(['"])(.*?)\1/i)?.[2] ?? "";
|
|
1004
|
-
if (!source ||
|
|
1239
|
+
if (!source || hasUnsafeUrlScheme(source)) return "";
|
|
1005
1240
|
return `![${alternative.replace(/]/g, "\\]")}](${source})`;
|
|
1006
|
-
});
|
|
1241
|
+
})));
|
|
1007
1242
|
}).join("\n");
|
|
1008
1243
|
const executable = withoutFencedCode(sanitized).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<(?:https?:\/\/|mailto:)[^>]+>/gi, "").replace(/(`+)[\s\S]*?\1/g, "");
|
|
1009
1244
|
if (/^\s*(?:import|export)\s/m.test(executable)) return null;
|
|
@@ -1017,27 +1252,35 @@ function sanitizeRemoteMarkdown(body) {
|
|
|
1017
1252
|
return original;
|
|
1018
1253
|
}
|
|
1019
1254
|
}).replace(/\\[{}]/g, "");
|
|
1020
|
-
if (hasUnsafeExpression || /[{}]/.test(withoutStaticProps) || /\bon[A-Z][A-Za-z]*\s*=/g.test(withoutStaticProps) ||
|
|
1255
|
+
if (hasUnsafeExpression || /[{}]/.test(withoutStaticProps) || /\bon[A-Z][A-Za-z]*\s*=/g.test(withoutStaticProps) || hasUnsafePortableUrlProp(executable)) return null;
|
|
1021
1256
|
for (const match of withoutStaticProps.matchAll(/<\/?([A-Za-z][A-Za-z0-9.]*)(?:\s|>|\/)/g)) {
|
|
1022
1257
|
const component = match[1].split(".", 1)[0];
|
|
1023
1258
|
if (!PORTABLE_MDX_COMPONENTS.has(component)) return null;
|
|
1024
1259
|
}
|
|
1025
1260
|
return sanitized;
|
|
1026
1261
|
}
|
|
1027
|
-
function migratedHref(href, currentUrl, source, scopePath, importedIds) {
|
|
1262
|
+
function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHomePageId) {
|
|
1028
1263
|
if (/^(?:javascript|data|vbscript):/i.test(href)) return "#";
|
|
1029
1264
|
if (href.startsWith("#") || /^(?:mailto|tel):/i.test(href)) return href;
|
|
1030
1265
|
try {
|
|
1031
1266
|
const target = new URL(href, currentUrl);
|
|
1032
1267
|
if (target.origin !== source.origin) return href;
|
|
1268
|
+
if (scopePath === "/" && candidateIdentity(target) === candidateIdentity(source)) {
|
|
1269
|
+
return `/${target.search}${target.hash}`;
|
|
1270
|
+
}
|
|
1033
1271
|
let path = target.pathname;
|
|
1034
1272
|
if (scopePath !== "/" && (path === scopePath || path.startsWith(`${scopePath}/`))) {
|
|
1035
1273
|
path = path.slice(scopePath.length);
|
|
1036
1274
|
}
|
|
1037
1275
|
const id = pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
|
|
1038
|
-
const localizedId = id
|
|
1039
|
-
|
|
1040
|
-
|
|
1276
|
+
const localizedId = id ? canonicalLocalizedPageId(id) : id;
|
|
1277
|
+
const destinationId = localizedId === sourceHomePageId ? "introduction" : localizedId;
|
|
1278
|
+
let importedDestinationId = destinationId && importedIds.has(destinationId) ? destinationId : void 0;
|
|
1279
|
+
if (destinationId && !importedDestinationId) {
|
|
1280
|
+
importedDestinationId = [`${destinationId}/overview`, `${destinationId}/introduction`].find((candidate) => importedIds.has(candidate)) ?? [...importedIds].find((candidate) => candidate.startsWith(`${destinationId}/`));
|
|
1281
|
+
}
|
|
1282
|
+
if (importedDestinationId) {
|
|
1283
|
+
const destination = importedDestinationId === "introduction" ? "/" : `/${importedDestinationId}`;
|
|
1041
1284
|
return `${destination}${target.search}${target.hash}`;
|
|
1042
1285
|
}
|
|
1043
1286
|
return target.toString();
|
|
@@ -1045,13 +1288,13 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds) {
|
|
|
1045
1288
|
return href;
|
|
1046
1289
|
}
|
|
1047
1290
|
}
|
|
1048
|
-
function rewriteInternalLinks(body, currentUrl, source, scopePath, importedIds) {
|
|
1291
|
+
function rewriteInternalLinks(body, currentUrl, source, scopePath, importedIds, sourceHomePageId) {
|
|
1049
1292
|
const markdown = body.replace(/(?<!!)\[([^\]]+)\]\(([^)\s]+)(\s+['"][^'"]*['"])?\)/g, (original, label, href, title = "") => {
|
|
1050
|
-
const destination = migratedHref(href, currentUrl, source, scopePath, importedIds);
|
|
1293
|
+
const destination = migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHomePageId);
|
|
1051
1294
|
return destination === href ? original : `[${label}](${destination}${title})`;
|
|
1052
1295
|
});
|
|
1053
1296
|
return markdown.replace(/\bhref=(['"])([^'"]+)\1/g, (_original, quote, href) => {
|
|
1054
|
-
const destination = migratedHref(href, currentUrl, source, scopePath, importedIds);
|
|
1297
|
+
const destination = migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHomePageId);
|
|
1055
1298
|
return `href=${quote}${destination}${quote}`;
|
|
1056
1299
|
});
|
|
1057
1300
|
}
|
|
@@ -1080,19 +1323,126 @@ function remoteMarkdownMetadata(body) {
|
|
|
1080
1323
|
description: scalar("description")
|
|
1081
1324
|
};
|
|
1082
1325
|
}
|
|
1326
|
+
function stripMintlifyDocumentationIndex(body) {
|
|
1327
|
+
const lines = body.split(/\r?\n/);
|
|
1328
|
+
if (!/^>\s*##\s+Documentation Index\s*$/i.test(lines[0] ?? "") || !/^>.*\/llms(?:-full)?\.txt/i.test(lines[1] ?? "") || !/^>.*discover all available pages/i.test(lines[2] ?? "")) return body;
|
|
1329
|
+
let index = 3;
|
|
1330
|
+
while (index < lines.length && /^>/.test(lines[index])) index += 1;
|
|
1331
|
+
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
1332
|
+
return lines.slice(index).join("\n");
|
|
1333
|
+
}
|
|
1334
|
+
function extractEmbeddedOpenApi(content) {
|
|
1335
|
+
const fencePattern = /^\s{0,3}(`{3,}|~{3,})(?:yaml|yml|json)\s+(\S+\.(?:yaml|yml|json))\s+(delete|get|head|options|patch|post|put|trace)\s+(\/\S+)\s*\r?\n([\s\S]*?)^\s{0,3}\1\s*$/im;
|
|
1336
|
+
const match = fencePattern.exec(content);
|
|
1337
|
+
if (!match) return { content };
|
|
1338
|
+
try {
|
|
1339
|
+
const parsed = parseYaml(match[5], { maxAliasCount: 50 });
|
|
1340
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
1341
|
+
const document = parsed;
|
|
1342
|
+
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|
|
1343
|
+
const method = match[3].toUpperCase();
|
|
1344
|
+
const path = match[4];
|
|
1345
|
+
const paths = document.paths;
|
|
1346
|
+
if (!paths || typeof paths !== "object" || Array.isArray(paths)) return { content };
|
|
1347
|
+
const pathItem = paths[path];
|
|
1348
|
+
if (!pathItem || typeof pathItem !== "object" || Array.isArray(pathItem) || !(match[3].toLowerCase() in pathItem)) return { content };
|
|
1349
|
+
const before = content.slice(0, match.index).replace(/(?:^|\n)##\s+OpenAPI\s*\n\s*$/i, "\n");
|
|
1350
|
+
const after = content.slice(match.index + match[0].length);
|
|
1351
|
+
return {
|
|
1352
|
+
content: `${before}${after}`.trim(),
|
|
1353
|
+
openapi: `${method} ${path}`,
|
|
1354
|
+
fragment: {
|
|
1355
|
+
method,
|
|
1356
|
+
path,
|
|
1357
|
+
document
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
} catch {
|
|
1361
|
+
return { content };
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1083
1364
|
function markdownPage(document, id) {
|
|
1084
1365
|
const parsed = remoteMarkdownMetadata(document.body);
|
|
1085
|
-
|
|
1366
|
+
let content = stripMintlifyDocumentationIndex(parsed.content).trimStart();
|
|
1367
|
+
const titleMatch = content.match(/^#\s+(.+)\r?\n/);
|
|
1086
1368
|
const title = parsed.title ?? titleMatch?.[1] ?? id.split("/").at(-1) ?? "Introduction";
|
|
1369
|
+
if (titleMatch) content = content.slice(titleMatch[0].length).trimStart();
|
|
1370
|
+
let description = parsed.description;
|
|
1371
|
+
if (!description) {
|
|
1372
|
+
const quoteMatch = content.match(/^(>[^\r\n]*(?:\r?\n>[^\r\n]*)*)\r?\n/);
|
|
1373
|
+
if (quoteMatch) {
|
|
1374
|
+
description = plainDescription(quoteMatch[1].replace(/^>\s?/gm, ""));
|
|
1375
|
+
content = content.slice(quoteMatch[0].length).trimStart();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
const embeddedOpenApi = extractEmbeddedOpenApi(content);
|
|
1087
1379
|
const raw = [
|
|
1088
1380
|
"---",
|
|
1089
1381
|
`title: ${JSON.stringify(title)}`,
|
|
1090
|
-
...
|
|
1382
|
+
...description ? [`description: ${JSON.stringify(description)}`] : [],
|
|
1383
|
+
...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
|
|
1091
1384
|
"---",
|
|
1092
1385
|
"",
|
|
1093
|
-
|
|
1386
|
+
embeddedOpenApi.content
|
|
1094
1387
|
].join("\n");
|
|
1095
|
-
return
|
|
1388
|
+
return {
|
|
1389
|
+
page: parseMarkdownPage({ id, raw, source: document.finalUrl.toString() }),
|
|
1390
|
+
openApiFragment: embeddedOpenApi.fragment
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function mergeRecord(target, source) {
|
|
1394
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return;
|
|
1395
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1396
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) continue;
|
|
1397
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
1398
|
+
target[key] = value;
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const current = target[key];
|
|
1402
|
+
if (current && value && typeof current === "object" && typeof value === "object" && !Array.isArray(current) && !Array.isArray(value)) {
|
|
1403
|
+
mergeRecord(current, value);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
function mergeEmbeddedOpenApi(fragments) {
|
|
1408
|
+
const first = fragments[0];
|
|
1409
|
+
if (!first) return null;
|
|
1410
|
+
const combined = {
|
|
1411
|
+
...first.document,
|
|
1412
|
+
paths: {},
|
|
1413
|
+
components: {}
|
|
1414
|
+
};
|
|
1415
|
+
const combinedPaths = combined.paths;
|
|
1416
|
+
const combinedComponents = combined.components;
|
|
1417
|
+
const tags = /* @__PURE__ */ new Map();
|
|
1418
|
+
for (const fragment of fragments) {
|
|
1419
|
+
const sourcePaths = fragment.document.paths;
|
|
1420
|
+
const sourcePathItem = sourcePaths?.[fragment.path];
|
|
1421
|
+
if (!sourcePathItem || typeof sourcePathItem !== "object" || Array.isArray(sourcePathItem)) continue;
|
|
1422
|
+
const sourceOperation = sourcePathItem[fragment.method.toLowerCase()];
|
|
1423
|
+
if (!sourceOperation || typeof sourceOperation !== "object" || Array.isArray(sourceOperation)) continue;
|
|
1424
|
+
const operation = { ...sourceOperation };
|
|
1425
|
+
if (!operation.servers && Array.isArray(fragment.document.servers)) operation.servers = fragment.document.servers;
|
|
1426
|
+
if (!operation.security && Array.isArray(fragment.document.security)) operation.security = fragment.document.security;
|
|
1427
|
+
const existingPath = combinedPaths[fragment.path];
|
|
1428
|
+
const pathItem = existingPath && typeof existingPath === "object" && !Array.isArray(existingPath) ? existingPath : {};
|
|
1429
|
+
for (const sharedKey of ["parameters", "servers", "summary", "description"]) {
|
|
1430
|
+
const sharedValue = sourcePathItem[sharedKey];
|
|
1431
|
+
if (sharedValue !== void 0 && pathItem[sharedKey] === void 0) pathItem[sharedKey] = sharedValue;
|
|
1432
|
+
}
|
|
1433
|
+
pathItem[fragment.method.toLowerCase()] = operation;
|
|
1434
|
+
combinedPaths[fragment.path] = pathItem;
|
|
1435
|
+
mergeRecord(combinedComponents, fragment.document.components);
|
|
1436
|
+
for (const tag of Array.isArray(fragment.document.tags) ? fragment.document.tags : []) {
|
|
1437
|
+
if (!tag || typeof tag !== "object" || Array.isArray(tag)) continue;
|
|
1438
|
+
const name = tag.name;
|
|
1439
|
+
if (typeof name === "string" && !tags.has(name)) tags.set(name, tag);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (Object.keys(combinedPaths).length === 0) return null;
|
|
1443
|
+
if (Object.keys(combinedComponents).length === 0) delete combined.components;
|
|
1444
|
+
if (tags.size > 0) combined.tags = [...tags.values()];
|
|
1445
|
+
return new TextEncoder().encode(stringifyYaml(combined));
|
|
1096
1446
|
}
|
|
1097
1447
|
function htmlPage(document, id) {
|
|
1098
1448
|
const $ = load(document.body);
|
|
@@ -1166,6 +1516,48 @@ ${fence}
|
|
|
1166
1516
|
links
|
|
1167
1517
|
};
|
|
1168
1518
|
}
|
|
1519
|
+
function htmlNavigationLinks(document) {
|
|
1520
|
+
const $ = load(document.body);
|
|
1521
|
+
return $('.nav-tabs a[href], nav a[href], aside a[href], [role="navigation"] a[href]').map((_index, element) => $(element).attr("href") ?? "").get().filter(Boolean);
|
|
1522
|
+
}
|
|
1523
|
+
function htmlTopLevelNavigation(document, source, scopePath) {
|
|
1524
|
+
const $ = load(document.body);
|
|
1525
|
+
const collectCandidates = (selector) => {
|
|
1526
|
+
const candidates2 = [];
|
|
1527
|
+
$(selector).each((_containerIndex, container) => {
|
|
1528
|
+
const entries = [];
|
|
1529
|
+
const seenSections = /* @__PURE__ */ new Set();
|
|
1530
|
+
let anchorCount = 0;
|
|
1531
|
+
$(container).find("a[href]").each((_index, element) => {
|
|
1532
|
+
const label = $(element).text().replace(/\s+/g, " ").trim();
|
|
1533
|
+
const href = $(element).attr("href");
|
|
1534
|
+
if (!label || !href) return;
|
|
1535
|
+
try {
|
|
1536
|
+
const target = new URL(href, document.finalUrl);
|
|
1537
|
+
if (!isInScope(target, source, scopePath)) return;
|
|
1538
|
+
anchorCount += 1;
|
|
1539
|
+
const targetId = canonicalLocalizedPageId(pageIdForUrl(target, scopePath) ?? "");
|
|
1540
|
+
const pageId = targetId;
|
|
1541
|
+
const section = targetId.split("/", 1)[0] || "introduction";
|
|
1542
|
+
if (!pageId || seenSections.has(section)) return;
|
|
1543
|
+
seenSections.add(section);
|
|
1544
|
+
entries.push({ section, label, pageId });
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
if (entries.length > 1) {
|
|
1549
|
+
candidates2.push({ entries, anchorCount });
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
return candidates2;
|
|
1553
|
+
};
|
|
1554
|
+
const tabCandidates = collectCandidates(".nav-tabs");
|
|
1555
|
+
const candidates = tabCandidates.length > 0 ? tabCandidates : collectCandidates('nav, [role="navigation"]');
|
|
1556
|
+
return candidates.sort((left, right) => {
|
|
1557
|
+
const densityDifference = right.entries.length / right.anchorCount - left.entries.length / left.anchorCount;
|
|
1558
|
+
return densityDifference || right.entries.length - left.entries.length;
|
|
1559
|
+
})[0]?.entries ?? [];
|
|
1560
|
+
}
|
|
1169
1561
|
function markdownLinks(body) {
|
|
1170
1562
|
return [...body.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s+['"][^'"]*['"])?\)/g)].map((match) => match[1]);
|
|
1171
1563
|
}
|
|
@@ -1199,12 +1591,17 @@ async function migrateUrl(options) {
|
|
|
1199
1591
|
const maxPages = Math.max(1, Math.min(options.maxPages ?? DEFAULT_MAX_PAGES, 1e3));
|
|
1200
1592
|
const concurrency = Math.max(1, Math.min(options.concurrency ?? 5, 10));
|
|
1201
1593
|
const maxTotalBytes = Math.max(1e6, Math.min(options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES, 5e8));
|
|
1202
|
-
const
|
|
1594
|
+
const submittedScopePath = docsScopePath(source);
|
|
1203
1595
|
const initial = await fetcher(source, { accept: "text/markdown,text/html,application/xhtml+xml;q=0.9" });
|
|
1204
|
-
if (!isInScope(initial.finalUrl, source,
|
|
1596
|
+
if (!isInScope(initial.finalUrl, source, submittedScopePath)) {
|
|
1205
1597
|
throw new Error("The documentation URL redirected outside the submitted docs origin or path.");
|
|
1206
1598
|
}
|
|
1207
|
-
const
|
|
1599
|
+
const htmlProbe = /html/i.test(initial.contentType) ? initial : await safeFetch(fetcher, source, "text/html,application/xhtml+xml");
|
|
1600
|
+
const scopedHtmlProbe = htmlProbe && isInScope(htmlProbe.finalUrl, source, submittedScopePath) ? htmlProbe : null;
|
|
1601
|
+
const platform = detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
1602
|
+
const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : submittedScopePath;
|
|
1603
|
+
const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
|
|
1604
|
+
const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
|
|
1208
1605
|
const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
|
|
1209
1606
|
const queue = [source];
|
|
1210
1607
|
const queued = /* @__PURE__ */ new Set([candidateIdentity(source)]);
|
|
@@ -1217,6 +1614,9 @@ async function migrateUrl(options) {
|
|
|
1217
1614
|
queued.add(candidateIdentity(url));
|
|
1218
1615
|
queue.push(url);
|
|
1219
1616
|
}
|
|
1617
|
+
if (scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType)) {
|
|
1618
|
+
for (const link of htmlNavigationLinks(scopedHtmlProbe)) enqueue(link, scopedHtmlProbe.finalUrl);
|
|
1619
|
+
}
|
|
1220
1620
|
async function discoverDocumentLinks(document, depth = 0) {
|
|
1221
1621
|
const links = sitemapLinks(document.body);
|
|
1222
1622
|
for (const link of links) {
|
|
@@ -1282,7 +1682,9 @@ async function migrateUrl(options) {
|
|
|
1282
1682
|
}
|
|
1283
1683
|
}
|
|
1284
1684
|
const pages = [];
|
|
1685
|
+
const openApiFragments = [];
|
|
1285
1686
|
const seenIds = /* @__PURE__ */ new Set();
|
|
1687
|
+
const seenSources = /* @__PURE__ */ new Set();
|
|
1286
1688
|
const visited = /* @__PURE__ */ new Set();
|
|
1287
1689
|
let importedBytes = 0;
|
|
1288
1690
|
let isByteBudgetExhausted = false;
|
|
@@ -1320,6 +1722,8 @@ async function migrateUrl(options) {
|
|
|
1320
1722
|
const htmlDocument = await safeFetch(fetcher, htmlUrl, "text/html,application/xhtml+xml");
|
|
1321
1723
|
if (htmlDocument && isInScope(htmlDocument.finalUrl, source, scopePath) && /html/i.test(htmlDocument.contentType)) {
|
|
1322
1724
|
document = htmlDocument;
|
|
1725
|
+
} else {
|
|
1726
|
+
return { candidate, page: null, links: [], failure: false };
|
|
1323
1727
|
}
|
|
1324
1728
|
} else if (sanitizedMarkdown !== null) {
|
|
1325
1729
|
document = { ...document, body: sanitizedMarkdown };
|
|
@@ -1330,12 +1734,15 @@ async function migrateUrl(options) {
|
|
|
1330
1734
|
return { candidate, page: null, links: [], failure: false, budgetExceeded: true };
|
|
1331
1735
|
}
|
|
1332
1736
|
importedBytes += responseBytes;
|
|
1333
|
-
const
|
|
1737
|
+
const discoveredId = pageIdForUrl(document.finalUrl, scopePath) ?? pageIdForUrl(candidate, scopePath);
|
|
1738
|
+
const id = discoveredId && canonicalLocalizedPageId(discoveredId) === sourceHomePageId ? "introduction" : discoveredId;
|
|
1334
1739
|
if (!id) return { candidate, page: null, links: [], failure: false };
|
|
1335
1740
|
if (/(?:markdown|text\/plain)/i.test(document.contentType) || /\.mdx?$/i.test(document.finalUrl.pathname)) {
|
|
1741
|
+
const markdown = markdownPage(document, id);
|
|
1336
1742
|
return {
|
|
1337
1743
|
candidate,
|
|
1338
|
-
page:
|
|
1744
|
+
page: markdown.page,
|
|
1745
|
+
openApiFragment: markdown.openApiFragment,
|
|
1339
1746
|
links: markdownLinks(document.body),
|
|
1340
1747
|
base: document.finalUrl,
|
|
1341
1748
|
failure: false
|
|
@@ -1350,9 +1757,15 @@ async function migrateUrl(options) {
|
|
|
1350
1757
|
continue;
|
|
1351
1758
|
}
|
|
1352
1759
|
if (result.budgetExceeded) continue;
|
|
1353
|
-
if (result.page
|
|
1760
|
+
if (result.page) result.page.id = canonicalLocalizedPageId(result.page.id);
|
|
1761
|
+
const sourceIdentity = result.page ? candidateIdentity(new URL(result.page.source)) : null;
|
|
1762
|
+
if (result.page && sourceIdentity && !seenSources.has(sourceIdentity) && !seenIds.has(result.page.id) && pages.length < maxPages) {
|
|
1354
1763
|
pages.push(result.page);
|
|
1764
|
+
if ("openApiFragment" in result && result.openApiFragment) {
|
|
1765
|
+
openApiFragments.push(result.openApiFragment);
|
|
1766
|
+
}
|
|
1355
1767
|
seenIds.add(result.page.id);
|
|
1768
|
+
seenSources.add(sourceIdentity);
|
|
1356
1769
|
}
|
|
1357
1770
|
for (const link of result.links) enqueue(link, result.base ?? result.candidate);
|
|
1358
1771
|
}
|
|
@@ -1368,14 +1781,41 @@ async function migrateUrl(options) {
|
|
|
1368
1781
|
}
|
|
1369
1782
|
const importedIds = new Set(pages.map((page) => page.id));
|
|
1370
1783
|
for (const page of pages) {
|
|
1371
|
-
page.body = rewriteInternalLinks(
|
|
1784
|
+
page.body = rewriteInternalLinks(
|
|
1785
|
+
page.body,
|
|
1786
|
+
new URL(page.source),
|
|
1787
|
+
source,
|
|
1788
|
+
scopePath,
|
|
1789
|
+
importedIds,
|
|
1790
|
+
sourceHomePageId
|
|
1791
|
+
);
|
|
1372
1792
|
}
|
|
1373
1793
|
if (isByteBudgetExhausted) {
|
|
1374
1794
|
warnings.push({ code: "limit-reached", message: `Import stopped after reaching the ${Math.round(maxTotalBytes / 1e6)} MB content budget.` });
|
|
1375
1795
|
} else if (queue.length > 0 || queued.size >= MAX_DISCOVERED_URLS) {
|
|
1376
1796
|
warnings.push({ code: "limit-reached", message: `Import stopped after ${pages.length} pages; narrow the URL or raise the caller limit to import more.` });
|
|
1377
1797
|
}
|
|
1378
|
-
const
|
|
1798
|
+
const topLevelNavigation = sourceTopLevelNavigation?.map((entry) => ({
|
|
1799
|
+
...entry,
|
|
1800
|
+
pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
|
|
1801
|
+
}));
|
|
1802
|
+
const docsConfig = buildNavigationFromPages(pages, {
|
|
1803
|
+
topLevelTabs: platform === "mintlify",
|
|
1804
|
+
topLevelNavigation
|
|
1805
|
+
});
|
|
1806
|
+
const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
|
|
1807
|
+
if (openApiAsset) {
|
|
1808
|
+
const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
|
|
1809
|
+
const apiTab = docsConfig.tabs.find((tab) => tab.groups?.some((group) => {
|
|
1810
|
+
const containsOperation = (pages2) => pages2.some((page) => {
|
|
1811
|
+
return typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages);
|
|
1812
|
+
});
|
|
1813
|
+
return containsOperation(group.pages);
|
|
1814
|
+
})) ?? docsConfig.tabs[0];
|
|
1815
|
+
if (apiTab) {
|
|
1816
|
+
apiTab.api = { source: "/openapi.yaml", navigation: false };
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1379
1819
|
const locales = [...new Set(pages.map((page) => page.locale).filter((value) => Boolean(value)))];
|
|
1380
1820
|
if (locales.length > 0) {
|
|
1381
1821
|
docsConfig.i18n = {
|
|
@@ -1391,7 +1831,7 @@ async function migrateUrl(options) {
|
|
|
1391
1831
|
sourceKind: "url",
|
|
1392
1832
|
platform,
|
|
1393
1833
|
pages,
|
|
1394
|
-
assets: [],
|
|
1834
|
+
assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
|
|
1395
1835
|
docsConfig,
|
|
1396
1836
|
warnings,
|
|
1397
1837
|
stats: {
|