@thallylabs/migrate 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.js +1277 -125
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,53 +1,18 @@
|
|
|
1
1
|
// src/repository.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import {
|
|
4
|
-
existsSync as
|
|
5
|
-
lstatSync,
|
|
6
|
-
readFileSync as
|
|
4
|
+
existsSync as existsSync3,
|
|
5
|
+
lstatSync as lstatSync2,
|
|
6
|
+
readFileSync as readFileSync3,
|
|
7
7
|
readdirSync
|
|
8
8
|
} from "fs";
|
|
9
|
-
import { basename, dirname as dirname2, extname as
|
|
9
|
+
import { basename, dirname as dirname2, extname as extname3, relative as relative3, resolve as resolvePath } from "path";
|
|
10
10
|
|
|
11
|
-
// src/
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
17
|
-
}
|
|
18
|
-
function firstParagraph(content) {
|
|
19
|
-
for (const block of content.split(/\n\s*\n/)) {
|
|
20
|
-
const value = block.replace(/\s+/g, " ").trim();
|
|
21
|
-
if (!value || /^(?:#|```|:::|<|import\s|export\s)/.test(value)) continue;
|
|
22
|
-
return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").slice(0, 240);
|
|
23
|
-
}
|
|
24
|
-
return "";
|
|
25
|
-
}
|
|
26
|
-
function normalizeMdx(body) {
|
|
27
|
-
return body.replace(/<!--([\s\S]*?)-->/g, (_match, content) => `{/*${content}*/}`).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
|
|
28
|
-
}
|
|
29
|
-
function parseMarkdownPage(input) {
|
|
30
|
-
const parsed = matter(input.raw);
|
|
31
|
-
const body = normalizeMdx(parsed.content).trim();
|
|
32
|
-
if (parsed.data.openapi && !body) return null;
|
|
33
|
-
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
34
|
-
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(input.navigationId ?? input.id);
|
|
35
|
-
const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
|
|
36
|
-
return {
|
|
37
|
-
id: input.id,
|
|
38
|
-
navigationId: input.navigationId ?? input.id,
|
|
39
|
-
locale: input.locale,
|
|
40
|
-
title,
|
|
41
|
-
description,
|
|
42
|
-
keywords,
|
|
43
|
-
body,
|
|
44
|
-
source: input.source
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/navigation.ts
|
|
49
|
-
import { existsSync, readFileSync } from "fs";
|
|
50
|
-
import { dirname, extname, relative as relative2 } from "path";
|
|
11
|
+
// src/docusaurus.ts
|
|
12
|
+
import { existsSync, lstatSync, readFileSync } from "fs";
|
|
13
|
+
import { extname, posix } from "path";
|
|
14
|
+
import JSON5 from "json5";
|
|
15
|
+
import { parse as parseYaml } from "yaml";
|
|
51
16
|
|
|
52
17
|
// src/path.ts
|
|
53
18
|
import { isAbsolute, relative, resolve, sep } from "path";
|
|
@@ -92,7 +57,553 @@ function normalizeAssetPath(value) {
|
|
|
92
57
|
return segments.join("/");
|
|
93
58
|
}
|
|
94
59
|
|
|
60
|
+
// src/docusaurus.ts
|
|
61
|
+
var MAX_CONFIG_BYTES = 1e6;
|
|
62
|
+
var CATEGORY_FILENAMES = ["_category_.json", "_category_.yml", "_category_.yaml"];
|
|
63
|
+
var SIDEBAR_FILENAMES = [
|
|
64
|
+
"sidebars.json",
|
|
65
|
+
"sidebars.js",
|
|
66
|
+
"sidebars.cjs",
|
|
67
|
+
"sidebars.mjs",
|
|
68
|
+
"sidebars.ts"
|
|
69
|
+
];
|
|
70
|
+
function objectValue(value) {
|
|
71
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
72
|
+
}
|
|
73
|
+
function stripNumberPrefix(value) {
|
|
74
|
+
return value.replace(/^\d+[-_]+/, "");
|
|
75
|
+
}
|
|
76
|
+
function normalizeDocId(value) {
|
|
77
|
+
return value.split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").split("/").filter(Boolean).map(stripNumberPrefix).join("/");
|
|
78
|
+
}
|
|
79
|
+
function docusaurusSlugifySegment(value) {
|
|
80
|
+
let decoded = value;
|
|
81
|
+
try {
|
|
82
|
+
decoded = decodeURIComponent(value);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
return decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
|
|
86
|
+
}
|
|
87
|
+
function docusaurusRouteId(value) {
|
|
88
|
+
const segments = normalizeDocId(value).split("/").map(docusaurusSlugifySegment).filter(Boolean);
|
|
89
|
+
return segments.join("/") || "introduction";
|
|
90
|
+
}
|
|
91
|
+
function defaultDocusaurusRouteId(value) {
|
|
92
|
+
const segments = normalizeDocId(value).split("/").filter(Boolean);
|
|
93
|
+
if (/^(?:index|readme)$/i.test(segments.at(-1) ?? "")) segments.pop();
|
|
94
|
+
const normalized = segments.map(docusaurusSlugifySegment).filter(Boolean);
|
|
95
|
+
return normalized.join("/") || "introduction";
|
|
96
|
+
}
|
|
97
|
+
function storageIdForRoute(routeId) {
|
|
98
|
+
return /(?:^|\/)(?:index|readme)$/i.test(routeId) ? `${routeId}/index` : routeId;
|
|
99
|
+
}
|
|
100
|
+
function titleCase(value) {
|
|
101
|
+
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(" ");
|
|
102
|
+
}
|
|
103
|
+
function routeFromSlug(slug, fallback, sourceDirectory) {
|
|
104
|
+
if (slug === "/") return "introduction";
|
|
105
|
+
if (slug.startsWith("/")) return docusaurusRouteId(slug);
|
|
106
|
+
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" : posix.dirname(fallback));
|
|
107
|
+
const resolved = posix.normalize(posix.join(fallbackDirectory, slug));
|
|
108
|
+
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
109
|
+
return docusaurusRouteId(resolved);
|
|
110
|
+
}
|
|
111
|
+
function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
|
|
112
|
+
const sourceDocId = normalizeDocId(sourcePath);
|
|
113
|
+
const sourceDirectory = posix.dirname(sourceDocId);
|
|
114
|
+
const configuredId = typeof frontmatter.id === "string" && frontmatter.id.trim() ? stripNumberPrefix(frontmatter.id.trim()) : null;
|
|
115
|
+
const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId : posix.join(sourceDirectory, configuredId)) : sourceDocId;
|
|
116
|
+
const defaultNavigationId = defaultDocusaurusRouteId(docId) ?? fallback.navigationId;
|
|
117
|
+
const configuredSlug = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
118
|
+
const navigationId = configuredSlug ? routeFromSlug(
|
|
119
|
+
configuredSlug,
|
|
120
|
+
defaultNavigationId,
|
|
121
|
+
sourceDirectory === "." ? "" : sourceDirectory
|
|
122
|
+
) ?? defaultNavigationId : defaultNavigationId;
|
|
123
|
+
const position = typeof frontmatter.sidebar_position === "number" && Number.isFinite(frontmatter.sidebar_position) ? frontmatter.sidebar_position : void 0;
|
|
124
|
+
return {
|
|
125
|
+
identity: { id: storageIdForRoute(navigationId), navigationId },
|
|
126
|
+
descriptor: {
|
|
127
|
+
sourcePath: sourcePath.replace(/\\/g, "/"),
|
|
128
|
+
docId,
|
|
129
|
+
navigationId,
|
|
130
|
+
...position === void 0 ? {} : { sidebarPosition: position }
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function sourceReferenceKey(value) {
|
|
135
|
+
return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").replace(/\/$/, "");
|
|
136
|
+
}
|
|
137
|
+
function rewriteDocusaurusLinks(body, current, descriptors) {
|
|
138
|
+
const routes = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const descriptor of descriptors) {
|
|
140
|
+
const sourceKey = sourceReferenceKey(descriptor.sourcePath);
|
|
141
|
+
routes.set(sourceKey, descriptor.navigationId);
|
|
142
|
+
routes.set(sourceReferenceKey(descriptor.docId), descriptor.navigationId);
|
|
143
|
+
routes.set(sourceReferenceKey(descriptor.navigationId), descriptor.navigationId);
|
|
144
|
+
const sourceRoute = pageIdFromReference(sourceKey);
|
|
145
|
+
if (sourceRoute) routes.set(sourceRoute, descriptor.navigationId);
|
|
146
|
+
}
|
|
147
|
+
const currentDirectory = posix.dirname(sourceReferenceKey(current.sourcePath));
|
|
148
|
+
function rewriteTarget(target) {
|
|
149
|
+
if (!target || target.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
|
|
150
|
+
const suffixIndex = target.search(/[?#]/);
|
|
151
|
+
const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
|
|
152
|
+
const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
|
|
153
|
+
const candidates = path.startsWith("/") ? [sourceReferenceKey(path), sourceReferenceKey(path).replace(/^docs\//, "")] : [
|
|
154
|
+
sourceReferenceKey(posix.normalize(posix.join(currentDirectory, path))),
|
|
155
|
+
sourceReferenceKey(path)
|
|
156
|
+
];
|
|
157
|
+
const route = candidates.map((candidate) => routes.get(candidate)).find(Boolean);
|
|
158
|
+
if (!route) return target;
|
|
159
|
+
return `${route === "introduction" ? "/" : `/${route}`}${suffix}`;
|
|
160
|
+
}
|
|
161
|
+
let codeFence = null;
|
|
162
|
+
return body.split("\n").map((line) => {
|
|
163
|
+
const fence = line.match(/^\s*(`{3,}|~{3,})/);
|
|
164
|
+
if (fence) {
|
|
165
|
+
if (!codeFence) codeFence = fence[1][0];
|
|
166
|
+
else if (fence[1][0] === codeFence) codeFence = null;
|
|
167
|
+
return line;
|
|
168
|
+
}
|
|
169
|
+
if (codeFence) return line;
|
|
170
|
+
return line.replace(/(\]\()([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(target)}`).replace(/(\bhref=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(target)}${suffix}`);
|
|
171
|
+
}).join("\n");
|
|
172
|
+
}
|
|
173
|
+
function readBoundedText(path) {
|
|
174
|
+
if (lstatSync(path).size > MAX_CONFIG_BYTES) {
|
|
175
|
+
throw new Error("Docusaurus sidebar config exceeded the 1 MB static-parser limit.");
|
|
176
|
+
}
|
|
177
|
+
return readFileSync(path, "utf8");
|
|
178
|
+
}
|
|
179
|
+
function matchingObjectLiteral(source, start) {
|
|
180
|
+
let depth = 0;
|
|
181
|
+
let quote = null;
|
|
182
|
+
let isEscaped = false;
|
|
183
|
+
let lineComment = false;
|
|
184
|
+
let blockComment = false;
|
|
185
|
+
let objectStart = -1;
|
|
186
|
+
for (let index = start; index < source.length; index++) {
|
|
187
|
+
const char = source[index];
|
|
188
|
+
const next = source[index + 1];
|
|
189
|
+
if (lineComment) {
|
|
190
|
+
if (char === "\n") lineComment = false;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (blockComment) {
|
|
194
|
+
if (char === "*" && next === "/") {
|
|
195
|
+
blockComment = false;
|
|
196
|
+
index++;
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (quote) {
|
|
201
|
+
if (isEscaped) isEscaped = false;
|
|
202
|
+
else if (char === "\\") isEscaped = true;
|
|
203
|
+
else if (char === quote) quote = null;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (char === "/" && next === "/") {
|
|
207
|
+
lineComment = true;
|
|
208
|
+
index++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (char === "/" && next === "*") {
|
|
212
|
+
blockComment = true;
|
|
213
|
+
index++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
217
|
+
quote = char;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (char === "{") {
|
|
221
|
+
if (objectStart < 0) objectStart = index;
|
|
222
|
+
depth++;
|
|
223
|
+
} else if (char === "}" && objectStart >= 0) {
|
|
224
|
+
depth--;
|
|
225
|
+
if (depth === 0) return source.slice(objectStart, index + 1);
|
|
226
|
+
} else if (objectStart < 0 && !/\s/.test(char)) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function parseStaticSidebarModule(source) {
|
|
233
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
234
|
+
const normalizedSource = replaceExternalFbContent(source);
|
|
235
|
+
const parseFailures = [];
|
|
236
|
+
const assignmentPatterns = [
|
|
237
|
+
/\bmodule\.exports\s*=\s*/g,
|
|
238
|
+
/\bexport\s+default\s*/g,
|
|
239
|
+
/\b(?:const|let|var)\s+[A-Za-z_$][\w$]*(?:\s*:\s*[^=;]+)?\s*=\s*/g
|
|
240
|
+
];
|
|
241
|
+
const candidates = assignmentPatterns.flatMap((pattern) => [...normalizedSource.matchAll(pattern)]).sort((left, right) => (left.index ?? 0) - (right.index ?? 0));
|
|
242
|
+
for (const candidate of candidates) {
|
|
243
|
+
const literal = matchingObjectLiteral(normalizedSource, (candidate.index ?? 0) + candidate[0].length);
|
|
244
|
+
if (!literal) continue;
|
|
245
|
+
try {
|
|
246
|
+
const substituted = [...bindings].reduce(
|
|
247
|
+
(value, [name, binding]) => value.replace(
|
|
248
|
+
new RegExp(`(:\\s*)${name}\\b`, "g"),
|
|
249
|
+
(_match, prefix) => `${prefix}${JSON.stringify(binding)}`
|
|
250
|
+
),
|
|
251
|
+
literal
|
|
252
|
+
);
|
|
253
|
+
const parsed = JSON5.parse(substituted);
|
|
254
|
+
const object = objectValue(parsed);
|
|
255
|
+
if (!object) continue;
|
|
256
|
+
const bindingName = candidate[0].match(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/)?.[1];
|
|
257
|
+
if (bindingName) {
|
|
258
|
+
bindings.set(bindingName, object);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
return object;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
264
|
+
const line = Number(message.match(/at (\d+):/)?.[1] ?? 0);
|
|
265
|
+
const context = line > 0 ? literal.split("\n").slice(Math.max(0, line - 2), line + 1).join(" ") : "";
|
|
266
|
+
parseFailures.push(`${message}${context ? ` near ${context}` : ""}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const exportedBinding = normalizedSource.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\b/)?.[1] ?? normalizedSource.match(/\bmodule\.exports\s*=\s*([A-Za-z_$][\w$]*)\b/)?.[1];
|
|
270
|
+
if (exportedBinding && bindings.has(exportedBinding)) return bindings.get(exportedBinding);
|
|
271
|
+
throw new Error(`Sidebar config is executable or outside the supported data-only syntax.${parseFailures[0] ? ` ${parseFailures[0]}` : ""}`);
|
|
272
|
+
}
|
|
273
|
+
function replaceExternalFbContent(source) {
|
|
274
|
+
const marker = "...fbContent(";
|
|
275
|
+
let result = source;
|
|
276
|
+
let searchFrom = 0;
|
|
277
|
+
while (true) {
|
|
278
|
+
const start = result.indexOf(marker, searchFrom);
|
|
279
|
+
if (start < 0) return result.replace(/,\s*,/g, ",");
|
|
280
|
+
const objectStart = result.indexOf("{", start + marker.length);
|
|
281
|
+
if (objectStart < 0) return result;
|
|
282
|
+
const objectLiteral = matchingObjectLiteral(result, objectStart);
|
|
283
|
+
if (!objectLiteral) {
|
|
284
|
+
searchFrom = start + marker.length;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const objectEnd = objectStart + objectLiteral.length;
|
|
288
|
+
const close = result.indexOf(")", objectEnd);
|
|
289
|
+
if (close < 0) return result;
|
|
290
|
+
const externalMatch = /\bexternal\s*:\s*/g.exec(objectLiteral);
|
|
291
|
+
let replacement = "";
|
|
292
|
+
if (externalMatch) {
|
|
293
|
+
const arrayStart = objectLiteral.indexOf("[", externalMatch.index + externalMatch[0].length);
|
|
294
|
+
if (arrayStart >= 0) {
|
|
295
|
+
const array = matchingArrayLiteral(objectLiteral, arrayStart);
|
|
296
|
+
if (array) replacement = array.slice(1, -1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
let replaceEnd = close + 1;
|
|
300
|
+
if (!replacement) {
|
|
301
|
+
const trailingComma = result.slice(replaceEnd).match(/^\s*,/);
|
|
302
|
+
if (trailingComma) replaceEnd += trailingComma[0].length;
|
|
303
|
+
}
|
|
304
|
+
result = `${result.slice(0, start)}${replacement}${result.slice(replaceEnd)}`;
|
|
305
|
+
searchFrom = start + replacement.length;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function matchingArrayLiteral(source, start) {
|
|
309
|
+
let depth = 0;
|
|
310
|
+
let quote = "";
|
|
311
|
+
let isEscaped = false;
|
|
312
|
+
for (let index = start; index < source.length; index++) {
|
|
313
|
+
const character = source[index];
|
|
314
|
+
if (quote) {
|
|
315
|
+
if (isEscaped) isEscaped = false;
|
|
316
|
+
else if (character === "\\") isEscaped = true;
|
|
317
|
+
else if (character === quote) quote = "";
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
321
|
+
quote = character;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (character === "[") depth++;
|
|
325
|
+
if (character === "]" && --depth === 0) return source.slice(start, index + 1);
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function configuredSidebarPath(repositoryRoot) {
|
|
330
|
+
for (const filename of ["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"]) {
|
|
331
|
+
const path = resolveWithin(repositoryRoot, filename);
|
|
332
|
+
if (!existsSync(path)) continue;
|
|
333
|
+
const source = readBoundedText(path);
|
|
334
|
+
const match = source.match(/\bsidebarPath\s*:\s*(?:require\.resolve\(\s*)?(['"])([^'"]+)\1/);
|
|
335
|
+
if (!match) continue;
|
|
336
|
+
const candidate = match[2].replace(/^\.\//, "");
|
|
337
|
+
const resolved = resolveWithin(repositoryRoot, candidate);
|
|
338
|
+
if (existsSync(resolved) && lstatSync(resolved).isFile()) return candidate;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
function readDocusaurusSidebars(repositoryRoot) {
|
|
343
|
+
const configured = configuredSidebarPath(repositoryRoot);
|
|
344
|
+
const sourcePath = [configured, ...SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) => existsSync(resolveWithin(repositoryRoot, candidate)));
|
|
345
|
+
if (!sourcePath) return null;
|
|
346
|
+
const absolutePath = resolveWithin(repositoryRoot, sourcePath);
|
|
347
|
+
const source = readBoundedText(absolutePath);
|
|
348
|
+
const parsed = extname(sourcePath).toLowerCase() === ".json" ? objectValue(JSON5.parse(source)) : parseStaticSidebarModule(source);
|
|
349
|
+
if (!parsed) throw new Error("Docusaurus sidebar config must export an object.");
|
|
350
|
+
return { config: parsed, sourcePath };
|
|
351
|
+
}
|
|
352
|
+
function readCategoryMetadata(contentRoot, directory) {
|
|
353
|
+
for (const filename of CATEGORY_FILENAMES) {
|
|
354
|
+
const path = resolveWithin(contentRoot, posix.join(directory, filename));
|
|
355
|
+
if (!existsSync(path) || !lstatSync(path).isFile()) continue;
|
|
356
|
+
try {
|
|
357
|
+
const raw = readBoundedText(path);
|
|
358
|
+
const parsed = filename.endsWith(".json") ? JSON5.parse(raw) : parseYaml(raw);
|
|
359
|
+
const object = objectValue(parsed);
|
|
360
|
+
if (!object) return {};
|
|
361
|
+
return {
|
|
362
|
+
...typeof object.label === "string" ? { label: object.label } : {},
|
|
363
|
+
...typeof object.position === "number" ? { position: object.position } : {},
|
|
364
|
+
..."link" in object ? { link: object.link } : {}
|
|
365
|
+
};
|
|
366
|
+
} catch {
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return {};
|
|
371
|
+
}
|
|
372
|
+
function descriptorSort(left, right) {
|
|
373
|
+
const leftPosition = left.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
|
|
374
|
+
const rightPosition = right.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
|
|
375
|
+
if (leftPosition !== rightPosition) return leftPosition - rightPosition;
|
|
376
|
+
return left.sourcePath.localeCompare(right.sourcePath, void 0, { numeric: true });
|
|
377
|
+
}
|
|
378
|
+
function registerDoc(docId, context) {
|
|
379
|
+
const key = normalizeDocId(docId);
|
|
380
|
+
const descriptor = context.descriptorByDocId.get(key) ?? context.descriptors.find((page) => page.navigationId === docusaurusRouteId(key));
|
|
381
|
+
if (!descriptor) {
|
|
382
|
+
context.warnings.push({
|
|
383
|
+
code: "missing-page",
|
|
384
|
+
message: "A Docusaurus sidebar document did not resolve to an imported page.",
|
|
385
|
+
source: docId
|
|
386
|
+
});
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
context.referencedNavigationIds.add(descriptor.navigationId);
|
|
390
|
+
return descriptor.navigationId;
|
|
391
|
+
}
|
|
392
|
+
function generatedIndexPage(label, linkValue, context) {
|
|
393
|
+
const link = objectValue(linkValue);
|
|
394
|
+
if (!link) return null;
|
|
395
|
+
if (link.type === "doc" && typeof link.id === "string") return registerDoc(link.id, context);
|
|
396
|
+
if (link.type !== "generated-index") return null;
|
|
397
|
+
const fallback = `category/${slugifySegment(label)}`;
|
|
398
|
+
const unprefixedNavigationId = typeof link.slug === "string" ? routeFromSlug(link.slug, fallback) ?? fallback : fallback;
|
|
399
|
+
const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ? posix.join(context.routePrefix, unprefixedNavigationId) : unprefixedNavigationId;
|
|
400
|
+
if (!context.generatedIds.has(navigationId) && !context.descriptors.some((page) => page.navigationId === navigationId)) {
|
|
401
|
+
const description = typeof link.description === "string" ? link.description : `Browse the ${label} documentation.`;
|
|
402
|
+
context.generatedPages.push({
|
|
403
|
+
id: storageIdForRoute(navigationId),
|
|
404
|
+
navigationId,
|
|
405
|
+
title: typeof link.title === "string" ? link.title : label,
|
|
406
|
+
description,
|
|
407
|
+
keywords: Array.isArray(link.keywords) ? link.keywords.filter((value) => typeof value === "string") : [],
|
|
408
|
+
body: description,
|
|
409
|
+
source: `${context.sourceUrl}#${context.sidebarSource}`
|
|
410
|
+
});
|
|
411
|
+
context.generatedIds.add(navigationId);
|
|
412
|
+
}
|
|
413
|
+
context.referencedNavigationIds.add(navigationId);
|
|
414
|
+
return navigationId;
|
|
415
|
+
}
|
|
416
|
+
function autogeneratedItems(dirName, context) {
|
|
417
|
+
const normalizedDir = dirName === "." ? "" : normalizeDocId(dirName);
|
|
418
|
+
const inDirectory = context.descriptors.filter((descriptor) => {
|
|
419
|
+
const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
|
|
420
|
+
return !normalizedDir || source === normalizedDir || source.startsWith(`${normalizedDir}/`);
|
|
421
|
+
});
|
|
422
|
+
const directPages = [];
|
|
423
|
+
const childDirectories = /* @__PURE__ */ new Set();
|
|
424
|
+
for (const descriptor of inDirectory) {
|
|
425
|
+
const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
|
|
426
|
+
const relativePath = normalizedDir ? posix.relative(normalizedDir, source) : source;
|
|
427
|
+
const segments = relativePath.split("/").filter(Boolean);
|
|
428
|
+
if (segments.length <= 1) directPages.push(descriptor);
|
|
429
|
+
else childDirectories.add(segments[0]);
|
|
430
|
+
}
|
|
431
|
+
const items = directPages.sort(descriptorSort).map((descriptor) => {
|
|
432
|
+
context.referencedNavigationIds.add(descriptor.navigationId);
|
|
433
|
+
return descriptor.navigationId;
|
|
434
|
+
});
|
|
435
|
+
const groups = [...childDirectories].map((segment) => {
|
|
436
|
+
const directory = normalizedDir ? posix.join(normalizedDir, segment) : segment;
|
|
437
|
+
const metadata = readCategoryMetadata(context.contentRoot, directory);
|
|
438
|
+
const pages = autogeneratedItems(directory, context);
|
|
439
|
+
const landing = generatedIndexPage(metadata.label ?? titleCase(segment), metadata.link, context);
|
|
440
|
+
if (landing && !pages.includes(landing)) pages.unshift(landing);
|
|
441
|
+
return {
|
|
442
|
+
position: metadata.position ?? Number.MAX_SAFE_INTEGER,
|
|
443
|
+
segment,
|
|
444
|
+
group: {
|
|
445
|
+
group: metadata.label ?? titleCase(segment),
|
|
446
|
+
pages
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}).sort((left, right) => left.position - right.position || left.segment.localeCompare(right.segment, void 0, { numeric: true }));
|
|
450
|
+
items.push(...groups.map((entry) => entry.group));
|
|
451
|
+
return items;
|
|
452
|
+
}
|
|
453
|
+
function convertItems(value, context) {
|
|
454
|
+
if (typeof value === "string") {
|
|
455
|
+
const page = registerDoc(value, context);
|
|
456
|
+
return page ? [page] : [];
|
|
457
|
+
}
|
|
458
|
+
if (Array.isArray(value)) return value.flatMap((entry) => convertItems(entry, context));
|
|
459
|
+
const object = objectValue(value);
|
|
460
|
+
if (!object) return [];
|
|
461
|
+
if (object.type === "doc" || object.type === "ref") {
|
|
462
|
+
const page = typeof object.id === "string" ? registerDoc(object.id, context) : null;
|
|
463
|
+
return page ? [page] : [];
|
|
464
|
+
}
|
|
465
|
+
if (object.type === "autogenerated") {
|
|
466
|
+
return autogeneratedItems(typeof object.dirName === "string" ? object.dirName : ".", context);
|
|
467
|
+
}
|
|
468
|
+
if (object.type === "category") {
|
|
469
|
+
const label = typeof object.label === "string" ? object.label : "Documentation";
|
|
470
|
+
const pages = convertItems(object.items, context);
|
|
471
|
+
const landing = generatedIndexPage(label, object.link, context);
|
|
472
|
+
if (landing && !pages.includes(landing)) pages.unshift(landing);
|
|
473
|
+
return pages.length > 0 ? [{ group: label, pages }] : [];
|
|
474
|
+
}
|
|
475
|
+
if (object.type === "link" || object.type === "html") return [];
|
|
476
|
+
return Object.entries(object).flatMap(([label, items]) => {
|
|
477
|
+
const pages = convertItems(items, context);
|
|
478
|
+
return pages.length > 0 ? [{ group: label, pages }] : [];
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function projectDocusaurusNavigation(input) {
|
|
482
|
+
const warnings = [];
|
|
483
|
+
const descriptorByDocId = new Map(input.descriptors.map((page) => [page.docId, page]));
|
|
484
|
+
const context = {
|
|
485
|
+
contentRoot: input.contentRoot,
|
|
486
|
+
descriptors: input.descriptors,
|
|
487
|
+
descriptorByDocId,
|
|
488
|
+
generatedPages: [],
|
|
489
|
+
generatedIds: /* @__PURE__ */ new Set(),
|
|
490
|
+
referencedNavigationIds: /* @__PURE__ */ new Set(),
|
|
491
|
+
sourceUrl: input.sourceUrl,
|
|
492
|
+
routePrefix: input.routePrefix ?? "",
|
|
493
|
+
sidebarSource: input.sidebars?.sourcePath ?? "autogenerated sidebar",
|
|
494
|
+
warnings
|
|
495
|
+
};
|
|
496
|
+
const sidebarEntries = input.sidebars ? Object.entries(input.sidebars.config) : [["docs", [
|
|
497
|
+
{ type: "autogenerated", dirName: "." }
|
|
498
|
+
]]];
|
|
499
|
+
const tabs = sidebarEntries.flatMap(([sidebarId, value]) => {
|
|
500
|
+
const converted = convertItems(value, context);
|
|
501
|
+
const loosePages = converted.filter((entry) => typeof entry === "string");
|
|
502
|
+
const groups = converted.filter((entry) => typeof entry !== "string");
|
|
503
|
+
if (loosePages.length > 0) groups.unshift({ group: "Overview", pages: loosePages });
|
|
504
|
+
if (groups.length === 0) return [];
|
|
505
|
+
return [{
|
|
506
|
+
tab: sidebarEntries.length === 1 ? "Documentation" : titleCase(sidebarId),
|
|
507
|
+
groups
|
|
508
|
+
}];
|
|
509
|
+
});
|
|
510
|
+
const unreferenced = input.descriptors.filter((page) => !context.referencedNavigationIds.has(page.navigationId)).sort(descriptorSort).map((page) => page.navigationId);
|
|
511
|
+
if (unreferenced.length > 0) {
|
|
512
|
+
if (tabs.length === 0) tabs.push({ tab: "Documentation", groups: [] });
|
|
513
|
+
tabs[0].groups?.push({ group: "Additional", pages: unreferenced });
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
docsConfig: { tabs },
|
|
517
|
+
generatedPages: context.generatedPages,
|
|
518
|
+
referencedNavigationIds: context.referencedNavigationIds,
|
|
519
|
+
warnings
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/mdx.ts
|
|
524
|
+
import matter from "gray-matter";
|
|
525
|
+
function titleFromId(id) {
|
|
526
|
+
const value = id.split("/").at(-1) ?? id;
|
|
527
|
+
if (value === "introduction") return "Introduction";
|
|
528
|
+
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
529
|
+
}
|
|
530
|
+
function firstParagraph(content) {
|
|
531
|
+
for (const block of content.split(/\n\s*\n/)) {
|
|
532
|
+
const value = block.replace(/\s+/g, " ").trim();
|
|
533
|
+
if (!value || /^(?:#|```|:::|<|import\s|export\s)/.test(value)) continue;
|
|
534
|
+
return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").slice(0, 240);
|
|
535
|
+
}
|
|
536
|
+
return "";
|
|
537
|
+
}
|
|
538
|
+
function docusaurusAdmonitionTag(kind) {
|
|
539
|
+
if (kind === "danger") return "Error";
|
|
540
|
+
if (kind === "info") return "Info";
|
|
541
|
+
if (kind === "caution" || kind === "warning") return "Warning";
|
|
542
|
+
return "Note";
|
|
543
|
+
}
|
|
544
|
+
function normalizeDocusaurusAdmonitions(body) {
|
|
545
|
+
const lines = body.split("\n");
|
|
546
|
+
const openAdmonitions = [];
|
|
547
|
+
let codeFence = null;
|
|
548
|
+
return lines.map((line) => {
|
|
549
|
+
const codeMatch = line.match(/^\s*(`{3,}|~{3,})/);
|
|
550
|
+
if (codeMatch) {
|
|
551
|
+
if (!codeFence) codeFence = codeMatch[1][0];
|
|
552
|
+
else if (codeMatch[1][0] === codeFence) codeFence = null;
|
|
553
|
+
return line;
|
|
554
|
+
}
|
|
555
|
+
if (codeFence) return line;
|
|
556
|
+
const opening = line.match(/^\s*(:{3,})(note|tip|info|warning|caution|danger)(?:\[([^\]]+)\]|\s+(.+))?\s*$/i);
|
|
557
|
+
if (opening) {
|
|
558
|
+
const tag = docusaurusAdmonitionTag(opening[2].toLowerCase());
|
|
559
|
+
openAdmonitions.push({ delimiter: opening[1], tag });
|
|
560
|
+
const title = (opening[3] ?? opening[4])?.trim();
|
|
561
|
+
return title ? `<${tag}>
|
|
562
|
+
**${title}**` : `<${tag}>`;
|
|
563
|
+
}
|
|
564
|
+
const closing = line.match(/^\s*(:{3,})\s*$/);
|
|
565
|
+
const current = openAdmonitions.at(-1);
|
|
566
|
+
if (closing && current?.delimiter === closing[1]) {
|
|
567
|
+
openAdmonitions.pop();
|
|
568
|
+
return `</${current.tag}>`;
|
|
569
|
+
}
|
|
570
|
+
return line;
|
|
571
|
+
}).join("\n");
|
|
572
|
+
}
|
|
573
|
+
function normalizeMdx(body) {
|
|
574
|
+
return normalizeDocusaurusAdmonitions(body).replace(/^import\s+(?:Tabs|TabItem|Link|DocCardList|TOCInline)\s+from\s+['"]@(?:theme|docusaurus)\/[^'"]+['"]\s*;?\s*$/gm, "").replace(/<TabItem\b([^>]*)>/g, (_match, attributes) => {
|
|
575
|
+
const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
|
|
576
|
+
return `<Tab title="${title.replace(/"/g, """)}">`;
|
|
577
|
+
}).replace(/<\/TabItem>/g, "</Tab>").replace(/<Link\b([^>]*)\bto=(?:"([^"]*)"|'([^']*)')([^>]*)>/g, (_match, before, doubleQuoted, singleQuoted, after) => `<a${before}href="${doubleQuoted ?? singleQuoted}"${after}>`).replace(/<\/Link>/g, "</a>").replace(/<(?:DocCardList|TOCInline)\b[^>]*\/>/g, "").replace(/<!--([\s\S]*?)-->/g, (_match, content) => `{/*${content}*/}`).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
|
|
578
|
+
}
|
|
579
|
+
function parseMarkdownPage(input) {
|
|
580
|
+
const parsed = matter(input.raw);
|
|
581
|
+
const fallbackIdentity = {
|
|
582
|
+
id: input.id,
|
|
583
|
+
navigationId: input.navigationId ?? input.id,
|
|
584
|
+
...input.locale ? { locale: input.locale } : {}
|
|
585
|
+
};
|
|
586
|
+
const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
|
|
587
|
+
const body = normalizeMdx(parsed.content).trim();
|
|
588
|
+
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
589
|
+
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(identity.navigationId);
|
|
590
|
+
const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
|
|
591
|
+
return {
|
|
592
|
+
id: identity.id,
|
|
593
|
+
navigationId: identity.navigationId,
|
|
594
|
+
locale: identity.locale,
|
|
595
|
+
title,
|
|
596
|
+
description,
|
|
597
|
+
keywords,
|
|
598
|
+
openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
|
|
599
|
+
body,
|
|
600
|
+
source: input.source
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
95
604
|
// src/navigation.ts
|
|
605
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
606
|
+
import { dirname, extname as extname2, relative as relative2 } from "path";
|
|
96
607
|
var LANGUAGE_LABELS = {
|
|
97
608
|
ar: "Arabic",
|
|
98
609
|
de: "German",
|
|
@@ -111,7 +622,7 @@ var LANGUAGE_LABELS = {
|
|
|
111
622
|
uk: "Ukrainian",
|
|
112
623
|
zh: "Chinese"
|
|
113
624
|
};
|
|
114
|
-
function
|
|
625
|
+
function objectValue2(value) {
|
|
115
626
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
116
627
|
}
|
|
117
628
|
function labelFor(value, fallback) {
|
|
@@ -127,7 +638,7 @@ function jsonPointer(root, pointer) {
|
|
|
127
638
|
if (!pointer || pointer === "#") return root;
|
|
128
639
|
const tokens = pointer.replace(/^#\/?/, "").split("/").filter(Boolean);
|
|
129
640
|
return tokens.reduce((current, token) => {
|
|
130
|
-
const object =
|
|
641
|
+
const object = objectValue2(current);
|
|
131
642
|
if (!object) return void 0;
|
|
132
643
|
return object[token.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
133
644
|
}, root);
|
|
@@ -136,7 +647,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
136
647
|
if (Array.isArray(value)) {
|
|
137
648
|
return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
|
|
138
649
|
}
|
|
139
|
-
const object =
|
|
650
|
+
const object = objectValue2(value);
|
|
140
651
|
if (!object) return value;
|
|
141
652
|
if (typeof object.$ref === "string") {
|
|
142
653
|
const [filePart, pointer = ""] = object.$ref.split("#", 2);
|
|
@@ -146,7 +657,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
146
657
|
const stackKey = `${referencedFile}#${pointer}`;
|
|
147
658
|
if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
|
|
148
659
|
stack.add(stackKey);
|
|
149
|
-
const referencedRoot = JSON.parse(
|
|
660
|
+
const referencedRoot = JSON.parse(readFileSync2(referencedFile, "utf8"));
|
|
150
661
|
const referenced = jsonPointer(referencedRoot, pointer ? `#${pointer}` : "");
|
|
151
662
|
const resolved = resolveJsonReferences(referenced, referencedFile, repositoryRoot, stack);
|
|
152
663
|
stack.delete(stackKey);
|
|
@@ -160,9 +671,9 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
160
671
|
);
|
|
161
672
|
}
|
|
162
673
|
function readMintlifyConfig(repositoryRoot) {
|
|
163
|
-
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(
|
|
674
|
+
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(existsSync2);
|
|
164
675
|
if (!configPath) return null;
|
|
165
|
-
const raw = JSON.parse(
|
|
676
|
+
const raw = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
166
677
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
167
678
|
}
|
|
168
679
|
function normalizePageRef(value) {
|
|
@@ -184,7 +695,7 @@ function registerReference(value, context) {
|
|
|
184
695
|
}
|
|
185
696
|
function convertPage(value, context) {
|
|
186
697
|
if (typeof value === "string") return registerReference(value, context);
|
|
187
|
-
const object =
|
|
698
|
+
const object = objectValue2(value);
|
|
188
699
|
if (!object) return null;
|
|
189
700
|
if (typeof object.page === "string") return registerReference(object.page, context);
|
|
190
701
|
const pages = Array.isArray(object.pages) ? object.pages : [];
|
|
@@ -220,12 +731,12 @@ function convertGroups(values, context, fallbackGroup) {
|
|
|
220
731
|
return groups;
|
|
221
732
|
}
|
|
222
733
|
function convertContainerToTabs(containerValue, context, fallbackTab) {
|
|
223
|
-
const container =
|
|
734
|
+
const container = objectValue2(containerValue);
|
|
224
735
|
if (!container) return [];
|
|
225
736
|
for (const key of ["tabs", "anchors", "products", "dropdowns", "versions", "menus"]) {
|
|
226
737
|
if (!Array.isArray(container[key])) continue;
|
|
227
738
|
const tabs = container[key].flatMap((value, index) => {
|
|
228
|
-
const object =
|
|
739
|
+
const object = objectValue2(value);
|
|
229
740
|
if (!object) return [];
|
|
230
741
|
const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
|
|
231
742
|
if (typeof object.href === "string" && !object.pages && !object.groups) {
|
|
@@ -252,8 +763,8 @@ function projectMintlifyNavigation(config) {
|
|
|
252
763
|
const warnings = [];
|
|
253
764
|
const references = [];
|
|
254
765
|
const seenReferences = /* @__PURE__ */ new Set();
|
|
255
|
-
const navigation =
|
|
256
|
-
const languages = Array.isArray(navigation.languages) ? navigation.languages.map(
|
|
766
|
+
const navigation = objectValue2(config.navigation) ?? config;
|
|
767
|
+
const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
|
|
257
768
|
let tabs = [];
|
|
258
769
|
let i18n;
|
|
259
770
|
if (languages.length > 0) {
|
|
@@ -291,7 +802,7 @@ function projectMintlifyNavigation(config) {
|
|
|
291
802
|
tabs.push({ tab: "Changelog", href: "/changelog" });
|
|
292
803
|
}
|
|
293
804
|
const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
|
|
294
|
-
const redirect =
|
|
805
|
+
const redirect = objectValue2(value);
|
|
295
806
|
if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
|
|
296
807
|
return [{
|
|
297
808
|
source: redirect.source,
|
|
@@ -305,7 +816,7 @@ function projectMintlifyNavigation(config) {
|
|
|
305
816
|
warnings
|
|
306
817
|
};
|
|
307
818
|
}
|
|
308
|
-
function
|
|
819
|
+
function titleCase2(value) {
|
|
309
820
|
return value.split(/[-_]/).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
310
821
|
}
|
|
311
822
|
var TOP_LEVEL_LABELS = {
|
|
@@ -317,7 +828,7 @@ var TOP_LEVEL_LABELS = {
|
|
|
317
828
|
sdk: "SDK"
|
|
318
829
|
};
|
|
319
830
|
function topLevelLabel(segment) {
|
|
320
|
-
return TOP_LEVEL_LABELS[segment] ??
|
|
831
|
+
return TOP_LEVEL_LABELS[segment] ?? titleCase2(segment);
|
|
321
832
|
}
|
|
322
833
|
function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(section)) {
|
|
323
834
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -329,7 +840,7 @@ function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(sect
|
|
|
329
840
|
groups.set(nestedSegment, group);
|
|
330
841
|
}
|
|
331
842
|
return [...groups].map(([segment, pages]) => ({
|
|
332
|
-
group: segment === "overview" ? sectionLabel :
|
|
843
|
+
group: segment === "overview" ? sectionLabel : titleCase2(segment),
|
|
333
844
|
pages
|
|
334
845
|
}));
|
|
335
846
|
}
|
|
@@ -401,20 +912,20 @@ function buildNavigationFromPages(pages, options = {}) {
|
|
|
401
912
|
buckets.set(segment, bucket);
|
|
402
913
|
}
|
|
403
914
|
const groups = [...buckets].map(([segment, pageIds]) => ({
|
|
404
|
-
group: segment === "overview" ? "Overview" :
|
|
915
|
+
group: segment === "overview" ? "Overview" : titleCase2(segment),
|
|
405
916
|
pages: pageIds
|
|
406
917
|
}));
|
|
407
918
|
return { tabs: [{ tab: "Documentation", groups }] };
|
|
408
919
|
}
|
|
409
920
|
function isDocumentationExtension(filename) {
|
|
410
|
-
return [".md", ".mdx", ".rst", ".txt"].includes(
|
|
921
|
+
return [".md", ".mdx", ".rst", ".txt"].includes(extname2(filename).toLowerCase());
|
|
411
922
|
}
|
|
412
923
|
|
|
413
924
|
// src/repository.ts
|
|
414
925
|
var MAX_SOURCE_FILES = 5e3;
|
|
415
926
|
var MAX_PAGE_BYTES = 2e6;
|
|
416
927
|
var MAX_ASSET_BYTES = 25e6;
|
|
417
|
-
var MAX_TOTAL_ASSET_BYTES =
|
|
928
|
+
var MAX_TOTAL_ASSET_BYTES = 5e8;
|
|
418
929
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
419
930
|
".git",
|
|
420
931
|
".github",
|
|
@@ -461,6 +972,71 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
461
972
|
"swagger.yaml",
|
|
462
973
|
"swagger.yml"
|
|
463
974
|
]);
|
|
975
|
+
var DOCUSAURUS_CONFIG_FILENAMES = [
|
|
976
|
+
"docusaurus.config.js",
|
|
977
|
+
"docusaurus.config.ts",
|
|
978
|
+
"docusaurus.config.mjs"
|
|
979
|
+
];
|
|
980
|
+
function hasDocusaurusConfig(directory) {
|
|
981
|
+
return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
|
|
982
|
+
const path = resolveWithin(directory, filename);
|
|
983
|
+
return existsSync3(path) && lstatSync2(path).isFile();
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
987
|
+
if (docsDir !== void 0) {
|
|
988
|
+
let candidate = docsDir.replace(/\/+$/, "");
|
|
989
|
+
while (true) {
|
|
990
|
+
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
991
|
+
if (hasDocusaurusConfig(path)) return path;
|
|
992
|
+
if (!candidate) break;
|
|
993
|
+
const parent = dirname2(candidate);
|
|
994
|
+
candidate = parent === "." ? "" : parent;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const queue = [{ directory: repositoryDir, depth: 0 }];
|
|
998
|
+
let visited = 0;
|
|
999
|
+
while (queue.length > 0 && visited < 500) {
|
|
1000
|
+
const current = queue.shift();
|
|
1001
|
+
visited++;
|
|
1002
|
+
if (hasDocusaurusConfig(current.directory)) return current.directory;
|
|
1003
|
+
if (current.depth >= 4) continue;
|
|
1004
|
+
for (const entry of readdirSync(current.directory, { withFileTypes: true })) {
|
|
1005
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
1006
|
+
queue.push({
|
|
1007
|
+
directory: resolveWithin(current.directory, entry.name),
|
|
1008
|
+
depth: current.depth + 1
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
function readDocusaurusConfigSource(projectRoot) {
|
|
1015
|
+
const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync3(candidate) && lstatSync2(candidate).isFile());
|
|
1016
|
+
if (!path || lstatSync2(path).size > 1e6) return "";
|
|
1017
|
+
return readFileSync3(path, "utf8");
|
|
1018
|
+
}
|
|
1019
|
+
function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
1020
|
+
const source = readDocusaurusConfigSource(projectRoot);
|
|
1021
|
+
const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
|
|
1022
|
+
return configured?.replace(/^\.\//, "").replace(/\/+$/, "") || "docs";
|
|
1023
|
+
}
|
|
1024
|
+
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
1025
|
+
const source = readDocusaurusConfigSource(projectRoot);
|
|
1026
|
+
const projectRelative = relative3(repositoryDir, projectRoot).replace(/\\/g, "/");
|
|
1027
|
+
const plugins = [];
|
|
1028
|
+
const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
|
|
1029
|
+
for (const match of source.matchAll(matcher)) {
|
|
1030
|
+
const localPath = match[2].replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1031
|
+
const routePrefix = match[4].replace(/^\/+|\/+$/g, "");
|
|
1032
|
+
if (!localPath || !routePrefix) continue;
|
|
1033
|
+
const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
|
|
1034
|
+
const absolute = resolveWithin(repositoryDir, docsDir);
|
|
1035
|
+
if (!existsSync3(absolute) || !lstatSync2(absolute).isDirectory()) continue;
|
|
1036
|
+
plugins.push({ docsDir, routePrefix });
|
|
1037
|
+
}
|
|
1038
|
+
return plugins;
|
|
1039
|
+
}
|
|
464
1040
|
function parseGitHubRepositoryUrl(rawUrl) {
|
|
465
1041
|
let url;
|
|
466
1042
|
try {
|
|
@@ -514,21 +1090,22 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
514
1090
|
});
|
|
515
1091
|
}
|
|
516
1092
|
function detectRepositoryPlatform(repositoryDir) {
|
|
517
|
-
if (
|
|
1093
|
+
if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
|
|
518
1094
|
const docsJson = resolveWithin(repositoryDir, "docs.json");
|
|
519
|
-
if (
|
|
1095
|
+
if (existsSync3(docsJson)) {
|
|
520
1096
|
try {
|
|
521
|
-
const config = JSON.parse(
|
|
1097
|
+
const config = JSON.parse(readFileSync3(docsJson, "utf8"));
|
|
522
1098
|
if (String(config.$schema ?? "").includes("mintlify") || "navigation" in config) return "mintlify";
|
|
523
1099
|
if (Array.isArray(config.tabs)) return "thally";
|
|
524
1100
|
} catch {
|
|
525
1101
|
}
|
|
526
1102
|
}
|
|
527
|
-
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) =>
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
531
|
-
if (["
|
|
1103
|
+
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "docusaurus";
|
|
1104
|
+
if (findDocusaurusProjectRoot(repositoryDir)) return "docusaurus";
|
|
1105
|
+
if (existsSync3(resolveWithin(repositoryDir, "SUMMARY.md"))) return "gitbook";
|
|
1106
|
+
if (existsSync3(resolveWithin(repositoryDir, ".vitepress"))) return "vitepress";
|
|
1107
|
+
if (["astro.config.mjs", "astro.config.ts"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "starlight";
|
|
1108
|
+
if (["pages/_meta.json", "_meta.json"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "nextra";
|
|
532
1109
|
return "unknown";
|
|
533
1110
|
}
|
|
534
1111
|
function containsMarkdown(directory, depth = 0) {
|
|
@@ -536,7 +1113,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
536
1113
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
537
1114
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
538
1115
|
const path = resolveWithin(directory, entry.name);
|
|
539
|
-
if (entry.isFile() && [".md", ".mdx"].includes(
|
|
1116
|
+
if (entry.isFile() && [".md", ".mdx"].includes(extname3(entry.name).toLowerCase())) return true;
|
|
540
1117
|
if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
|
|
541
1118
|
}
|
|
542
1119
|
return false;
|
|
@@ -544,7 +1121,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
544
1121
|
function detectRepositoryDocsDir(repositoryDir) {
|
|
545
1122
|
for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
|
|
546
1123
|
const path = resolveWithin(repositoryDir, candidate);
|
|
547
|
-
if (
|
|
1124
|
+
if (existsSync3(path) && lstatSync2(path).isDirectory() && containsMarkdown(path)) return candidate;
|
|
548
1125
|
}
|
|
549
1126
|
return "";
|
|
550
1127
|
}
|
|
@@ -572,22 +1149,24 @@ function findOpenApi(files) {
|
|
|
572
1149
|
function withoutFrontmatter(value) {
|
|
573
1150
|
return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
574
1151
|
}
|
|
575
|
-
function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0) {
|
|
1152
|
+
function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot) {
|
|
576
1153
|
if (depth >= 8) return raw;
|
|
577
1154
|
const snippets = /* @__PURE__ */ new Map();
|
|
578
1155
|
const withoutImports = raw.replace(
|
|
579
|
-
/^import\s+([A-Z][A-Za-z0-9_]*)\s+from\s+['"]([^'"]+\.mdx?)['"]\s
|
|
1156
|
+
/^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm,
|
|
580
1157
|
(_statement, componentName, sourcePath) => {
|
|
581
1158
|
try {
|
|
582
|
-
const
|
|
1159
|
+
const repositoryRelative = sourcePath.startsWith("@site/") ? relative3(repositoryRoot, resolveWithin(siteRoot, sourcePath.slice("@site/".length))).replace(/\\/g, "/") : sourcePath.startsWith("/") ? sourcePath.replace(/^\/+/, "") : relative3(repositoryRoot, resolvePath(dirname2(currentFile), sourcePath)).replace(/\\/g, "/");
|
|
1160
|
+
const candidate = resolveWithin(repositoryRoot, repositoryRelative);
|
|
583
1161
|
resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
|
|
584
|
-
if (!
|
|
1162
|
+
if (!existsSync3(candidate) || !lstatSync2(candidate).isFile()) throw new Error("file not found");
|
|
585
1163
|
const nested = inlineMdxSnippets(
|
|
586
|
-
withoutFrontmatter(
|
|
1164
|
+
withoutFrontmatter(readFileSync3(candidate, "utf8")),
|
|
587
1165
|
candidate,
|
|
588
1166
|
repositoryRoot,
|
|
589
1167
|
warnings,
|
|
590
|
-
depth + 1
|
|
1168
|
+
depth + 1,
|
|
1169
|
+
siteRoot
|
|
591
1170
|
);
|
|
592
1171
|
snippets.set(componentName, nested);
|
|
593
1172
|
return "";
|
|
@@ -622,10 +1201,27 @@ function injectOpenApi(config, filename) {
|
|
|
622
1201
|
function migrateRepository(options) {
|
|
623
1202
|
const repositoryDir = options.repositoryDir;
|
|
624
1203
|
const platform = options.platform ?? detectRepositoryPlatform(repositoryDir);
|
|
1204
|
+
const docusaurusProjectRoot = platform === "docusaurus" ? findDocusaurusProjectRoot(repositoryDir, options.docsDir) : null;
|
|
1205
|
+
const configuredDocsDir = (() => {
|
|
1206
|
+
if (options.docsDir !== void 0) {
|
|
1207
|
+
const selected = resolveWithin(repositoryDir, options.docsDir || ".");
|
|
1208
|
+
if (platform === "docusaurus" && hasDocusaurusConfig(selected)) {
|
|
1209
|
+
const projectRelative = relative3(repositoryDir, selected).replace(/\\/g, "/");
|
|
1210
|
+
return [projectRelative, primaryDocusaurusDocsDirectory(selected)].filter(Boolean).join("/");
|
|
1211
|
+
}
|
|
1212
|
+
return options.docsDir;
|
|
1213
|
+
}
|
|
1214
|
+
if (docusaurusProjectRoot) {
|
|
1215
|
+
const projectRelative = relative3(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
|
|
1216
|
+
return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
|
|
1217
|
+
}
|
|
1218
|
+
return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
|
|
1219
|
+
})();
|
|
625
1220
|
const warnings = [];
|
|
626
1221
|
let docsConfig = { tabs: [] };
|
|
627
1222
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
628
1223
|
const referenceOrder = /* @__PURE__ */ new Map();
|
|
1224
|
+
let docusaurusSidebars = null;
|
|
629
1225
|
if (platform === "mintlify") {
|
|
630
1226
|
try {
|
|
631
1227
|
const config = readMintlifyConfig(repositoryDir);
|
|
@@ -646,16 +1242,27 @@ function migrateRepository(options) {
|
|
|
646
1242
|
});
|
|
647
1243
|
}
|
|
648
1244
|
}
|
|
649
|
-
|
|
1245
|
+
if (platform === "docusaurus") {
|
|
1246
|
+
try {
|
|
1247
|
+
docusaurusSidebars = options.docusaurusSkipSidebar || !docusaurusProjectRoot ? null : readDocusaurusSidebars(docusaurusProjectRoot);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
warnings.push({
|
|
1250
|
+
code: "unsupported-config",
|
|
1251
|
+
message: `Docusaurus sidebar could not be read safely: ${error instanceof Error ? error.message : String(error)} Generated navigation will be used.`
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
650
1255
|
const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
|
|
651
|
-
if (!
|
|
1256
|
+
if (!existsSync3(contentRoot) || !lstatSync2(contentRoot).isDirectory()) {
|
|
652
1257
|
throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
|
|
653
1258
|
}
|
|
654
1259
|
const files = scanFiles(contentRoot);
|
|
655
1260
|
const pages = [];
|
|
656
1261
|
const assets = [];
|
|
1262
|
+
const docusaurusDescriptors = [];
|
|
657
1263
|
const seenPageIds = /* @__PURE__ */ new Set();
|
|
658
1264
|
let skipped = 0;
|
|
1265
|
+
let discovered = files.length;
|
|
659
1266
|
const localeConfig = docsConfig.i18n;
|
|
660
1267
|
const pageFiles = [...files].sort((left, right) => {
|
|
661
1268
|
const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -664,21 +1271,22 @@ function migrateRepository(options) {
|
|
|
664
1271
|
});
|
|
665
1272
|
for (const file of pageFiles) {
|
|
666
1273
|
if (!isDocumentationExtension(file.relativePath)) continue;
|
|
667
|
-
|
|
1274
|
+
const sourceSegments = file.relativePath.split("/");
|
|
1275
|
+
if (sourceSegments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase())) || platform === "docusaurus" && basename(file.relativePath).startsWith("_")) {
|
|
668
1276
|
skipped++;
|
|
669
1277
|
continue;
|
|
670
1278
|
}
|
|
671
1279
|
const rootFilename = !file.relativePath.includes("/") ? basename(file.relativePath).toLowerCase() : "";
|
|
672
|
-
if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
|
|
1280
|
+
if (!configuredDocsDir && REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
|
|
673
1281
|
skipped++;
|
|
674
1282
|
continue;
|
|
675
1283
|
}
|
|
676
|
-
if (![".md", ".mdx"].includes(
|
|
1284
|
+
if (![".md", ".mdx"].includes(extname3(file.relativePath).toLowerCase())) {
|
|
677
1285
|
skipped++;
|
|
678
1286
|
warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
|
|
679
1287
|
continue;
|
|
680
1288
|
}
|
|
681
|
-
const size =
|
|
1289
|
+
const size = lstatSync2(file.absolutePath).size;
|
|
682
1290
|
if (size > MAX_PAGE_BYTES) {
|
|
683
1291
|
skipped++;
|
|
684
1292
|
warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
|
|
@@ -702,30 +1310,57 @@ function migrateRepository(options) {
|
|
|
702
1310
|
}
|
|
703
1311
|
const isDefaultLocale = !locale || locale === localeConfig?.defaultLocale;
|
|
704
1312
|
const id = isDefaultLocale ? navigationId : `${locale}/${navigationId}`;
|
|
705
|
-
if (seenPageIds.has(id)) {
|
|
706
|
-
skipped++;
|
|
707
|
-
warnings.push({ code: "collision", message: `Multiple source files map to ${id}; the first file was kept.`, source: file.relativePath });
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
1313
|
const raw = inlineMdxSnippets(
|
|
711
|
-
|
|
1314
|
+
readFileSync3(file.absolutePath, "utf8"),
|
|
712
1315
|
file.absolutePath,
|
|
713
1316
|
repositoryDir,
|
|
714
|
-
warnings
|
|
1317
|
+
warnings,
|
|
1318
|
+
0,
|
|
1319
|
+
docusaurusProjectRoot ?? repositoryDir
|
|
715
1320
|
);
|
|
1321
|
+
let docusaurusDescriptor;
|
|
716
1322
|
const page = parseMarkdownPage({
|
|
717
1323
|
id,
|
|
718
1324
|
navigationId,
|
|
719
1325
|
...locale ? { locale } : {},
|
|
720
1326
|
raw,
|
|
721
|
-
source: `${options.sourceUrl}#${file.relativePath}
|
|
1327
|
+
source: `${options.sourceUrl}#${file.relativePath}`,
|
|
1328
|
+
...platform === "docusaurus" ? {
|
|
1329
|
+
resolveIdentity: (frontmatter, fallback) => {
|
|
1330
|
+
const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
|
|
1331
|
+
const routePrefix = options.docusaurusRoutePrefix?.replace(/^\/+|\/+$/g, "");
|
|
1332
|
+
if (!routePrefix) {
|
|
1333
|
+
docusaurusDescriptor = resolved.descriptor;
|
|
1334
|
+
return resolved.identity;
|
|
1335
|
+
}
|
|
1336
|
+
const prefixedNavigationId = resolved.identity.navigationId === "introduction" ? routePrefix : `${routePrefix}/${resolved.identity.navigationId}`;
|
|
1337
|
+
const prefixedStorageId = resolved.identity.id === "introduction" ? routePrefix : `${routePrefix}/${resolved.identity.id}`;
|
|
1338
|
+
docusaurusDescriptor = {
|
|
1339
|
+
...resolved.descriptor,
|
|
1340
|
+
navigationId: prefixedNavigationId
|
|
1341
|
+
};
|
|
1342
|
+
return {
|
|
1343
|
+
...resolved.identity,
|
|
1344
|
+
id: prefixedStorageId,
|
|
1345
|
+
navigationId: prefixedNavigationId
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
} : {}
|
|
722
1349
|
});
|
|
723
1350
|
if (!page) {
|
|
724
1351
|
skipped++;
|
|
725
1352
|
continue;
|
|
726
1353
|
}
|
|
727
|
-
seenPageIds.
|
|
1354
|
+
if (seenPageIds.has(page.id)) {
|
|
1355
|
+
skipped++;
|
|
1356
|
+
warnings.push({ code: "collision", message: `Multiple source files map to ${page.id}; the first file was kept.`, source: file.relativePath });
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
seenPageIds.add(page.id);
|
|
728
1360
|
pages.push(page);
|
|
1361
|
+
if (docusaurusDescriptor) {
|
|
1362
|
+
docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
|
|
1363
|
+
}
|
|
729
1364
|
}
|
|
730
1365
|
const discoveredReferenceKeys = new Set(files.map((file) => normalizedReferenceKey(file.relativePath)));
|
|
731
1366
|
for (const [key] of referenceMap) {
|
|
@@ -733,32 +1368,98 @@ function migrateRepository(options) {
|
|
|
733
1368
|
warnings.push({ code: "missing-page", message: "A navigation entry did not resolve to a source page.", source: key });
|
|
734
1369
|
}
|
|
735
1370
|
}
|
|
1371
|
+
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
1372
|
+
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
1373
|
+
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
1374
|
+
if (!existsSync3(root) || !lstatSync2(root).isDirectory()) return [];
|
|
1375
|
+
return scanFiles(root).map((file) => ({
|
|
1376
|
+
...file,
|
|
1377
|
+
relativePath: `${directory}/${file.relativePath}`
|
|
1378
|
+
}));
|
|
1379
|
+
}) : [];
|
|
736
1380
|
let totalAssetBytes = 0;
|
|
737
|
-
for (const file of files) {
|
|
1381
|
+
for (const file of [...files, ...repositoryAssets]) {
|
|
738
1382
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
739
|
-
if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(
|
|
740
|
-
const size =
|
|
1383
|
+
if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
|
|
1384
|
+
const size = lstatSync2(file.absolutePath).size;
|
|
741
1385
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
742
1386
|
warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
|
|
743
1387
|
continue;
|
|
744
1388
|
}
|
|
745
|
-
const
|
|
1389
|
+
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
1390
|
+
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
746
1391
|
if (!assetPath) continue;
|
|
747
|
-
assets.push({ path: assetPath, content:
|
|
1392
|
+
assets.push({ path: assetPath, content: readFileSync3(file.absolutePath) });
|
|
748
1393
|
totalAssetBytes += size;
|
|
749
1394
|
}
|
|
750
1395
|
if (files.length >= MAX_SOURCE_FILES) {
|
|
751
1396
|
warnings.push({ code: "limit-reached", message: `Repository discovery stopped at ${MAX_SOURCE_FILES} files.` });
|
|
752
1397
|
}
|
|
1398
|
+
if (platform === "docusaurus") {
|
|
1399
|
+
const projected = projectDocusaurusNavigation({
|
|
1400
|
+
sidebars: docusaurusSidebars,
|
|
1401
|
+
descriptors: docusaurusDescriptors,
|
|
1402
|
+
contentRoot,
|
|
1403
|
+
sourceUrl: options.sourceUrl,
|
|
1404
|
+
routePrefix: options.docusaurusRoutePrefix
|
|
1405
|
+
});
|
|
1406
|
+
docsConfig = projected.docsConfig;
|
|
1407
|
+
warnings.push(...projected.warnings);
|
|
1408
|
+
for (const page of projected.generatedPages) {
|
|
1409
|
+
if (seenPageIds.has(page.id)) continue;
|
|
1410
|
+
seenPageIds.add(page.id);
|
|
1411
|
+
pages.push(page);
|
|
1412
|
+
}
|
|
1413
|
+
const descriptorByNavigationId = new Map(
|
|
1414
|
+
docusaurusDescriptors.map((descriptor) => [descriptor.navigationId, descriptor])
|
|
1415
|
+
);
|
|
1416
|
+
for (const page of pages) {
|
|
1417
|
+
const descriptor = descriptorByNavigationId.get(page.navigationId);
|
|
1418
|
+
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
753
1421
|
if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
|
|
754
1422
|
const openApi = findOpenApi(files);
|
|
755
1423
|
if (openApi) {
|
|
756
1424
|
const filename = basename(openApi.relativePath);
|
|
757
1425
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
758
|
-
assets.push({ path: filename, content:
|
|
1426
|
+
assets.push({ path: filename, content: readFileSync3(openApi.absolutePath) });
|
|
759
1427
|
}
|
|
760
1428
|
docsConfig = injectOpenApi(docsConfig, filename);
|
|
761
1429
|
}
|
|
1430
|
+
if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
|
|
1431
|
+
for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
|
|
1432
|
+
if (plugin.docsDir === configuredDocsDir) continue;
|
|
1433
|
+
const pluginBundle = migrateRepository({
|
|
1434
|
+
...options,
|
|
1435
|
+
docsDir: plugin.docsDir,
|
|
1436
|
+
platform: "docusaurus",
|
|
1437
|
+
docusaurusRoutePrefix: plugin.routePrefix,
|
|
1438
|
+
docusaurusSkipPlugins: true,
|
|
1439
|
+
docusaurusSkipSidebar: true,
|
|
1440
|
+
docusaurusSkipAssets: true
|
|
1441
|
+
});
|
|
1442
|
+
for (const page of pluginBundle.pages) {
|
|
1443
|
+
if (seenPageIds.has(page.id)) continue;
|
|
1444
|
+
seenPageIds.add(page.id);
|
|
1445
|
+
pages.push(page);
|
|
1446
|
+
}
|
|
1447
|
+
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1448
|
+
for (const asset of pluginBundle.assets) {
|
|
1449
|
+
if (assetPaths.has(asset.path)) continue;
|
|
1450
|
+
assetPaths.add(asset.path);
|
|
1451
|
+
assets.push(asset);
|
|
1452
|
+
}
|
|
1453
|
+
const pluginLabel = plugin.routePrefix.split(/[-_/]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1454
|
+
docsConfig.tabs.push(...pluginBundle.docsConfig.tabs.map((tab, index) => ({
|
|
1455
|
+
...tab,
|
|
1456
|
+
tab: index === 0 ? pluginLabel : `${pluginLabel}: ${tab.tab}`
|
|
1457
|
+
})));
|
|
1458
|
+
warnings.push(...pluginBundle.warnings);
|
|
1459
|
+
discovered += pluginBundle.stats.discovered;
|
|
1460
|
+
skipped += pluginBundle.stats.skipped;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
762
1463
|
if (pages.length === 0) throw new Error("No importable Markdown or MDX pages were found in the repository.");
|
|
763
1464
|
return {
|
|
764
1465
|
sourceUrl: options.sourceUrl,
|
|
@@ -768,7 +1469,7 @@ function migrateRepository(options) {
|
|
|
768
1469
|
assets,
|
|
769
1470
|
docsConfig,
|
|
770
1471
|
warnings,
|
|
771
|
-
stats: { discovered
|
|
1472
|
+
stats: { discovered, imported: pages.length, skipped }
|
|
772
1473
|
};
|
|
773
1474
|
}
|
|
774
1475
|
|
|
@@ -782,6 +1483,7 @@ function renderPage(bundle, page) {
|
|
|
782
1483
|
`title: ${yamlString(page.title)}`,
|
|
783
1484
|
`description: ${yamlString(page.description)}`,
|
|
784
1485
|
page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
|
|
1486
|
+
page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
|
|
785
1487
|
bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
|
|
786
1488
|
"---",
|
|
787
1489
|
"",
|
|
@@ -860,6 +1562,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
860
1562
|
// src/url.ts
|
|
861
1563
|
import { load } from "cheerio";
|
|
862
1564
|
import TurndownService from "turndown";
|
|
1565
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
863
1566
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
864
1567
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
865
1568
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -1041,7 +1744,7 @@ function normalizeCandidate(value, base, source, scopePath) {
|
|
|
1041
1744
|
if (!isInScope(url, source, scopePath)) return null;
|
|
1042
1745
|
if (url.pathname.startsWith("/cdn-cgi/")) return null;
|
|
1043
1746
|
if (/\/(?:llms(?:-full)?|robots)\.txt$/i.test(url.pathname) || /\/sitemap\.xml$/i.test(url.pathname)) return null;
|
|
1044
|
-
if (/\.(?:avif|bmp|gif|ico|jpe?g|mp[34]|pdf|png|svg|webm|webp|zip)$/i.test(url.pathname)) return null;
|
|
1747
|
+
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;
|
|
1045
1748
|
return url;
|
|
1046
1749
|
} catch {
|
|
1047
1750
|
return null;
|
|
@@ -1166,10 +1869,57 @@ function hasUnsafePortableUrlProp(value) {
|
|
|
1166
1869
|
}
|
|
1167
1870
|
return false;
|
|
1168
1871
|
}
|
|
1872
|
+
function portableHtmlToMarkdown(value) {
|
|
1873
|
+
return value.replace(/<span\b[^>]*>|<\/span>/gi, "").replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_original, attributes, label) => {
|
|
1874
|
+
const href = attributes.match(/\bhref\s*=\s*(['"])(.*?)\1/i)?.[2];
|
|
1875
|
+
const plainLabel = label.replace(/<[^>]+>/g, "").trim();
|
|
1876
|
+
if (!href || hasUnsafeUrlScheme(href)) return plainLabel;
|
|
1877
|
+
return `[${plainLabel.replace(/]/g, "\\]")}](${href})`;
|
|
1878
|
+
}).replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_original, level, content) => {
|
|
1879
|
+
return `
|
|
1880
|
+
${"#".repeat(Number(level))} ${content.trim()}
|
|
1881
|
+
`;
|
|
1882
|
+
}).replace(/<p\b[^>]*>([\s\S]*?)<\/p>/gi, (_original, content) => `
|
|
1883
|
+
${content.trim()}
|
|
1884
|
+
`).replace(/<br\s*\/?\s*>/gi, "\n").replace(/<\/?(?:article|div|main|section)\b[^>]*>/gi, "");
|
|
1885
|
+
}
|
|
1886
|
+
function transformMarkdownProse(body, transform) {
|
|
1887
|
+
const output = [];
|
|
1888
|
+
let prose = [];
|
|
1889
|
+
let fenceCharacter = "";
|
|
1890
|
+
let fenceLength = 0;
|
|
1891
|
+
const flushProse = () => {
|
|
1892
|
+
if (prose.length === 0) return;
|
|
1893
|
+
output.push(transform(prose.join("\n")));
|
|
1894
|
+
prose = [];
|
|
1895
|
+
};
|
|
1896
|
+
for (const line of body.split("\n")) {
|
|
1897
|
+
const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
1898
|
+
if (fence && !fenceCharacter) {
|
|
1899
|
+
flushProse();
|
|
1900
|
+
fenceCharacter = fence[1][0];
|
|
1901
|
+
fenceLength = fence[1].length;
|
|
1902
|
+
output.push(line);
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
if (fenceCharacter) {
|
|
1906
|
+
output.push(line);
|
|
1907
|
+
if (fence && fence[1][0] === fenceCharacter && fence[1].length >= fenceLength && fence[2].trim() === "") {
|
|
1908
|
+
fenceCharacter = "";
|
|
1909
|
+
fenceLength = 0;
|
|
1910
|
+
}
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
prose.push(line);
|
|
1914
|
+
}
|
|
1915
|
+
flushProse();
|
|
1916
|
+
return output.join("\n");
|
|
1917
|
+
}
|
|
1169
1918
|
function sanitizeRemoteMarkdown(body) {
|
|
1170
1919
|
let fenceCharacter = "";
|
|
1171
1920
|
let fenceLength = 0;
|
|
1172
|
-
const
|
|
1921
|
+
const portableBody = transformMarkdownProse(body, portableHtmlToMarkdown);
|
|
1922
|
+
const sanitized = portableBody.split("\n").map((line) => {
|
|
1173
1923
|
const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
1174
1924
|
if (fence && !fenceCharacter) {
|
|
1175
1925
|
fenceCharacter = fence[1][0];
|
|
@@ -1182,14 +1932,14 @@ function sanitizeRemoteMarkdown(body) {
|
|
|
1182
1932
|
return line;
|
|
1183
1933
|
}
|
|
1184
1934
|
if (fenceCharacter) return line;
|
|
1185
|
-
return stripEventHandlerProps(line.replace(/!\[([^\]]*)\]\(\s*([^)]+)\)/g, (original, alternative, destination) => {
|
|
1935
|
+
return portableHtmlToMarkdown(stripEventHandlerProps(line.replace(/!\[([^\]]*)\]\(\s*([^)]+)\)/g, (original, alternative, destination) => {
|
|
1186
1936
|
return hasUnsafeUrlScheme(destination.replace(/^<|>$/g, "")) ? alternative : original;
|
|
1187
|
-
}).replace(
|
|
1937
|
+
}).replace(/<img\s+([^>]*?)\/?\s*>/gi, (original, attributes) => {
|
|
1188
1938
|
const source = attributes.match(/\bsrc\s*=\s*(['"])(.*?)\1/i)?.[2];
|
|
1189
1939
|
const alternative = attributes.match(/\balt\s*=\s*(['"])(.*?)\1/i)?.[2] ?? "";
|
|
1190
1940
|
if (!source || hasUnsafeUrlScheme(source)) return "";
|
|
1191
1941
|
return `![${alternative.replace(/]/g, "\\]")}](${source})`;
|
|
1192
|
-
}));
|
|
1942
|
+
})));
|
|
1193
1943
|
}).join("\n");
|
|
1194
1944
|
const executable = withoutFencedCode(sanitized).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<(?:https?:\/\/|mailto:)[^>]+>/gi, "").replace(/(`+)[\s\S]*?\1/g, "");
|
|
1195
1945
|
if (/^\s*(?:import|export)\s/m.test(executable)) return null;
|
|
@@ -1226,8 +1976,12 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
|
|
|
1226
1976
|
const id = pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
|
|
1227
1977
|
const localizedId = id ? canonicalLocalizedPageId(id) : id;
|
|
1228
1978
|
const destinationId = localizedId === sourceHomePageId ? "introduction" : localizedId;
|
|
1229
|
-
|
|
1230
|
-
|
|
1979
|
+
let importedDestinationId = destinationId && importedIds.has(destinationId) ? destinationId : void 0;
|
|
1980
|
+
if (destinationId && !importedDestinationId) {
|
|
1981
|
+
importedDestinationId = [`${destinationId}/overview`, `${destinationId}/introduction`].find((candidate) => importedIds.has(candidate)) ?? [...importedIds].find((candidate) => candidate.startsWith(`${destinationId}/`));
|
|
1982
|
+
}
|
|
1983
|
+
if (importedDestinationId) {
|
|
1984
|
+
const destination = importedDestinationId === "introduction" ? "/" : `/${importedDestinationId}`;
|
|
1231
1985
|
return `${destination}${target.search}${target.hash}`;
|
|
1232
1986
|
}
|
|
1233
1987
|
return target.toString();
|
|
@@ -1278,6 +2032,36 @@ function stripMintlifyDocumentationIndex(body) {
|
|
|
1278
2032
|
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
1279
2033
|
return lines.slice(index).join("\n");
|
|
1280
2034
|
}
|
|
2035
|
+
function extractEmbeddedOpenApi(content) {
|
|
2036
|
+
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;
|
|
2037
|
+
const match = fencePattern.exec(content);
|
|
2038
|
+
if (!match) return { content };
|
|
2039
|
+
try {
|
|
2040
|
+
const parsed = parseYaml2(match[5], { maxAliasCount: 50 });
|
|
2041
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
2042
|
+
const document = parsed;
|
|
2043
|
+
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|
|
2044
|
+
const method = match[3].toUpperCase();
|
|
2045
|
+
const path = match[4];
|
|
2046
|
+
const paths = document.paths;
|
|
2047
|
+
if (!paths || typeof paths !== "object" || Array.isArray(paths)) return { content };
|
|
2048
|
+
const pathItem = paths[path];
|
|
2049
|
+
if (!pathItem || typeof pathItem !== "object" || Array.isArray(pathItem) || !(match[3].toLowerCase() in pathItem)) return { content };
|
|
2050
|
+
const before = content.slice(0, match.index).replace(/(?:^|\n)##\s+OpenAPI\s*\n\s*$/i, "\n");
|
|
2051
|
+
const after = content.slice(match.index + match[0].length);
|
|
2052
|
+
return {
|
|
2053
|
+
content: `${before}${after}`.trim(),
|
|
2054
|
+
openapi: `${method} ${path}`,
|
|
2055
|
+
fragment: {
|
|
2056
|
+
method,
|
|
2057
|
+
path,
|
|
2058
|
+
document
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
} catch {
|
|
2062
|
+
return { content };
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
1281
2065
|
function markdownPage(document, id) {
|
|
1282
2066
|
const parsed = remoteMarkdownMetadata(document.body);
|
|
1283
2067
|
let content = stripMintlifyDocumentationIndex(parsed.content).trimStart();
|
|
@@ -1292,15 +2076,74 @@ function markdownPage(document, id) {
|
|
|
1292
2076
|
content = content.slice(quoteMatch[0].length).trimStart();
|
|
1293
2077
|
}
|
|
1294
2078
|
}
|
|
2079
|
+
const embeddedOpenApi = extractEmbeddedOpenApi(content);
|
|
1295
2080
|
const raw = [
|
|
1296
2081
|
"---",
|
|
1297
2082
|
`title: ${JSON.stringify(title)}`,
|
|
1298
2083
|
...description ? [`description: ${JSON.stringify(description)}`] : [],
|
|
2084
|
+
...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
|
|
1299
2085
|
"---",
|
|
1300
2086
|
"",
|
|
1301
|
-
content
|
|
2087
|
+
embeddedOpenApi.content
|
|
1302
2088
|
].join("\n");
|
|
1303
|
-
return
|
|
2089
|
+
return {
|
|
2090
|
+
page: parseMarkdownPage({ id, raw, source: document.finalUrl.toString() }),
|
|
2091
|
+
openApiFragment: embeddedOpenApi.fragment
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
function mergeRecord(target, source) {
|
|
2095
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return;
|
|
2096
|
+
for (const [key, value] of Object.entries(source)) {
|
|
2097
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) continue;
|
|
2098
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
2099
|
+
target[key] = value;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
const current = target[key];
|
|
2103
|
+
if (current && value && typeof current === "object" && typeof value === "object" && !Array.isArray(current) && !Array.isArray(value)) {
|
|
2104
|
+
mergeRecord(current, value);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function mergeEmbeddedOpenApi(fragments) {
|
|
2109
|
+
const first = fragments[0];
|
|
2110
|
+
if (!first) return null;
|
|
2111
|
+
const combined = {
|
|
2112
|
+
...first.document,
|
|
2113
|
+
paths: {},
|
|
2114
|
+
components: {}
|
|
2115
|
+
};
|
|
2116
|
+
const combinedPaths = combined.paths;
|
|
2117
|
+
const combinedComponents = combined.components;
|
|
2118
|
+
const tags = /* @__PURE__ */ new Map();
|
|
2119
|
+
for (const fragment of fragments) {
|
|
2120
|
+
const sourcePaths = fragment.document.paths;
|
|
2121
|
+
const sourcePathItem = sourcePaths?.[fragment.path];
|
|
2122
|
+
if (!sourcePathItem || typeof sourcePathItem !== "object" || Array.isArray(sourcePathItem)) continue;
|
|
2123
|
+
const sourceOperation = sourcePathItem[fragment.method.toLowerCase()];
|
|
2124
|
+
if (!sourceOperation || typeof sourceOperation !== "object" || Array.isArray(sourceOperation)) continue;
|
|
2125
|
+
const operation = { ...sourceOperation };
|
|
2126
|
+
if (!operation.servers && Array.isArray(fragment.document.servers)) operation.servers = fragment.document.servers;
|
|
2127
|
+
if (!operation.security && Array.isArray(fragment.document.security)) operation.security = fragment.document.security;
|
|
2128
|
+
const existingPath = combinedPaths[fragment.path];
|
|
2129
|
+
const pathItem = existingPath && typeof existingPath === "object" && !Array.isArray(existingPath) ? existingPath : {};
|
|
2130
|
+
for (const sharedKey of ["parameters", "servers", "summary", "description"]) {
|
|
2131
|
+
const sharedValue = sourcePathItem[sharedKey];
|
|
2132
|
+
if (sharedValue !== void 0 && pathItem[sharedKey] === void 0) pathItem[sharedKey] = sharedValue;
|
|
2133
|
+
}
|
|
2134
|
+
pathItem[fragment.method.toLowerCase()] = operation;
|
|
2135
|
+
combinedPaths[fragment.path] = pathItem;
|
|
2136
|
+
mergeRecord(combinedComponents, fragment.document.components);
|
|
2137
|
+
for (const tag of Array.isArray(fragment.document.tags) ? fragment.document.tags : []) {
|
|
2138
|
+
if (!tag || typeof tag !== "object" || Array.isArray(tag)) continue;
|
|
2139
|
+
const name = tag.name;
|
|
2140
|
+
if (typeof name === "string" && !tags.has(name)) tags.set(name, tag);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
if (Object.keys(combinedPaths).length === 0) return null;
|
|
2144
|
+
if (Object.keys(combinedComponents).length === 0) delete combined.components;
|
|
2145
|
+
if (tags.size > 0) combined.tags = [...tags.values()];
|
|
2146
|
+
return new TextEncoder().encode(stringifyYaml(combined));
|
|
1304
2147
|
}
|
|
1305
2148
|
function htmlPage(document, id) {
|
|
1306
2149
|
const $ = load(document.body);
|
|
@@ -1310,6 +2153,7 @@ function htmlPage(document, id) {
|
|
|
1310
2153
|
const content = $('main article, article, main, [role="main"]').first();
|
|
1311
2154
|
const root = content.length ? content : $("body");
|
|
1312
2155
|
root.find("script,style,noscript,nav,header,footer,aside,form,button,svg,iframe,object,embed,link,meta").remove();
|
|
2156
|
+
root.find(".theme-doc-version-badge,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
|
|
1313
2157
|
root.find("a[href]").each((_index, element) => {
|
|
1314
2158
|
const href = $(element).attr("href");
|
|
1315
2159
|
if (!href) return;
|
|
@@ -1340,12 +2184,78 @@ function htmlPage(document, id) {
|
|
|
1340
2184
|
});
|
|
1341
2185
|
const turndown = new TurndownService({ codeBlockStyle: "fenced", emDelimiter: "_", headingStyle: "atx" });
|
|
1342
2186
|
turndown.remove(["script", "style", "noscript"]);
|
|
2187
|
+
turndown.addRule("docusaurus-admonition", {
|
|
2188
|
+
filter: (node) => node.nodeName === "DIV" && node.classList.contains("theme-admonition"),
|
|
2189
|
+
replacement: (_content, node) => {
|
|
2190
|
+
const element = node;
|
|
2191
|
+
const className = element.className;
|
|
2192
|
+
const tag = /admonition-(?:danger)/.test(className) ? "Error" : /admonition-(?:caution|warning)/.test(className) ? "Warning" : /admonition-info/.test(className) ? "Info" : "Note";
|
|
2193
|
+
const fragment = load(element.outerHTML);
|
|
2194
|
+
const clone = fragment(".theme-admonition").first();
|
|
2195
|
+
const title2 = clone.find('[class*="admonitionHeading"],.admonition-heading').first();
|
|
2196
|
+
const titleText = title2.text().trim();
|
|
2197
|
+
title2.remove();
|
|
2198
|
+
const markdown = turndown.turndown(clone.html() ?? "").trim();
|
|
2199
|
+
return `
|
|
2200
|
+
|
|
2201
|
+
<${tag}>
|
|
2202
|
+
${titleText ? `**${titleText}**
|
|
2203
|
+
|
|
2204
|
+
` : ""}${markdown}
|
|
2205
|
+
</${tag}>
|
|
2206
|
+
|
|
2207
|
+
`;
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
turndown.addRule("docusaurus-tabs", {
|
|
2211
|
+
filter: (node) => node.nodeName === "DIV" && node.classList.contains("tabs-container"),
|
|
2212
|
+
replacement: (_content, node) => {
|
|
2213
|
+
const element = node;
|
|
2214
|
+
const fragment = load(element.outerHTML);
|
|
2215
|
+
const container = fragment(".tabs-container").first();
|
|
2216
|
+
const labels = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
|
|
2217
|
+
const panels = container.children('div[role="tabpanel"]').toArray();
|
|
2218
|
+
if (panels.length === 0) return _content;
|
|
2219
|
+
const tabs = panels.map((panel, index) => {
|
|
2220
|
+
const title2 = (labels[index] ?? `Tab ${index + 1}`).replace(/"/g, """);
|
|
2221
|
+
return `<Tab title="${title2}">
|
|
2222
|
+
${turndown.turndown(fragment(panel).html() ?? "").trim()}
|
|
2223
|
+
</Tab>`;
|
|
2224
|
+
});
|
|
2225
|
+
return `
|
|
2226
|
+
|
|
2227
|
+
<Tabs>
|
|
2228
|
+
${tabs.join("\n\n")}
|
|
2229
|
+
</Tabs>
|
|
2230
|
+
|
|
2231
|
+
`;
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
turndown.addRule("gfm-table", {
|
|
2235
|
+
filter: "table",
|
|
2236
|
+
replacement: (_content, node) => {
|
|
2237
|
+
const fragment = load(node.outerHTML);
|
|
2238
|
+
const rows = fragment("tr").toArray().map((row) => fragment(row).children("th,td").toArray().map((cell) => fragment(cell).text().replace(/\s+/g, " ").replace(/\|/g, "\\|").replace(/</g, "<").replace(/>/g, ">").replace(/\{/g, "{").replace(/\}/g, "}").trim())).filter((row) => row.length > 0);
|
|
2239
|
+
if (rows.length === 0) return "";
|
|
2240
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
2241
|
+
const normalized = rows.map((row) => [...row, ...Array(width - row.length).fill("")]);
|
|
2242
|
+
return `
|
|
2243
|
+
|
|
2244
|
+
| ${normalized[0].join(" | ")} |
|
|
2245
|
+
| ${Array(width).fill("---").join(" | ")} |
|
|
2246
|
+
${normalized.slice(1).map((row) => `| ${row.join(" | ")} |`).join("\n")}
|
|
2247
|
+
|
|
2248
|
+
`;
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
1343
2251
|
turndown.addRule("migration-safe-fenced-code", {
|
|
1344
2252
|
filter: (node) => node.nodeName === "PRE",
|
|
1345
2253
|
replacement: (_content, node) => {
|
|
1346
2254
|
const element = node;
|
|
1347
|
-
const
|
|
1348
|
-
const
|
|
2255
|
+
const fragment = load(element.outerHTML);
|
|
2256
|
+
const tokenLines = fragment(".token-line").toArray();
|
|
2257
|
+
const code = (tokenLines.length > 0 ? tokenLines.map((line) => fragment(line).text()).join("\n") : fragment("pre").text()).replace(/\n$/, "");
|
|
2258
|
+
const language = fragment("code").attr("class")?.match(/(?:language-|lang-)([A-Za-z0-9_+-]+)/)?.[1] ?? "";
|
|
1349
2259
|
const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
|
|
1350
2260
|
const fence = "`".repeat(Math.max(3, longestFence + 1));
|
|
1351
2261
|
return `
|
|
@@ -1360,7 +2270,7 @@ ${fence}
|
|
|
1360
2270
|
const escape = turndown.escape.bind(turndown);
|
|
1361
2271
|
turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
|
|
1362
2272
|
const body = turndown.turndown(root.html() ?? "").trim();
|
|
1363
|
-
if (!title || body.length < 40) return { page: null, links };
|
|
2273
|
+
if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
|
|
1364
2274
|
return {
|
|
1365
2275
|
page: {
|
|
1366
2276
|
id,
|
|
@@ -1378,6 +2288,176 @@ function htmlNavigationLinks(document) {
|
|
|
1378
2288
|
const $ = load(document.body);
|
|
1379
2289
|
return $('.nav-tabs a[href], nav a[href], aside a[href], [role="navigation"] a[href]').map((_index, element) => $(element).attr("href") ?? "").get().filter(Boolean);
|
|
1380
2290
|
}
|
|
2291
|
+
function normalizedLinkLabel(value) {
|
|
2292
|
+
return value.replace(/\s+/g, " ").trim();
|
|
2293
|
+
}
|
|
2294
|
+
function readDocusaurusSidebar(document) {
|
|
2295
|
+
if (!/html/i.test(document.contentType)) return null;
|
|
2296
|
+
const $ = load(document.body);
|
|
2297
|
+
const sidebar = $("ul.theme-doc-sidebar-menu").first();
|
|
2298
|
+
if (sidebar.length === 0) return null;
|
|
2299
|
+
function readList(list) {
|
|
2300
|
+
return $(list).children("li").toArray().flatMap((item) => {
|
|
2301
|
+
const directLink = $(item).children("a.menu__link").first();
|
|
2302
|
+
const collapsibleLink = $(item).children(".menu__list-item-collapsible").children("a.menu__link").first();
|
|
2303
|
+
const link = directLink.length > 0 ? directLink : collapsibleLink;
|
|
2304
|
+
const label = normalizedLinkLabel(link.text());
|
|
2305
|
+
const href = link.attr("href");
|
|
2306
|
+
const nested = $(item).children("ul.menu__list").first();
|
|
2307
|
+
const children = nested.length > 0 ? readList(nested.get(0)) : [];
|
|
2308
|
+
const isCategory = $(item).hasClass("theme-doc-sidebar-item-category") || link.hasClass("menu__link--sublist");
|
|
2309
|
+
if (!label && children.length === 0) return [];
|
|
2310
|
+
return [{
|
|
2311
|
+
label: label || "Documentation",
|
|
2312
|
+
...href ? { href: new URL(href, document.finalUrl).toString() } : {},
|
|
2313
|
+
...children.length > 0 ? { items: children } : {},
|
|
2314
|
+
...isCategory ? { isCategory: true } : {}
|
|
2315
|
+
}];
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
return { items: readList(sidebar.get(0)) };
|
|
2319
|
+
}
|
|
2320
|
+
function docusaurusSidebarHrefs(snapshot) {
|
|
2321
|
+
const hrefs = [];
|
|
2322
|
+
const visit = (items) => {
|
|
2323
|
+
for (const item of items) {
|
|
2324
|
+
if (item.href) hrefs.push(item.href);
|
|
2325
|
+
if (item.items) visit(item.items);
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
if (snapshot) visit(snapshot.items);
|
|
2329
|
+
return hrefs;
|
|
2330
|
+
}
|
|
2331
|
+
function deriveDocusaurusScope(source, document, submittedScopePath) {
|
|
2332
|
+
const $ = load(document.body);
|
|
2333
|
+
const sidebar = readDocusaurusSidebar(document);
|
|
2334
|
+
const internalPaths = docusaurusSidebarHrefs(sidebar).flatMap((href) => {
|
|
2335
|
+
try {
|
|
2336
|
+
const url = new URL(href);
|
|
2337
|
+
return url.origin === source.origin ? [url.pathname.replace(/\/+$/, "") || "/"] : [];
|
|
2338
|
+
} catch {
|
|
2339
|
+
return [];
|
|
2340
|
+
}
|
|
2341
|
+
});
|
|
2342
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2343
|
+
for (const path of internalPaths) {
|
|
2344
|
+
const segments = path.split("/").filter(Boolean);
|
|
2345
|
+
for (let length = 1; length < segments.length; length++) {
|
|
2346
|
+
const prefix = `/${segments.slice(0, length).join("/")}`;
|
|
2347
|
+
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const threshold = Math.max(2, Math.ceil(internalPaths.length * 0.6));
|
|
2351
|
+
const scopePath = [...counts].filter(([, count]) => count >= threshold).sort((left, right) => right[0].split("/").length - left[0].split("/").length || right[1] - left[1])[0]?.[0] ?? submittedScopePath;
|
|
2352
|
+
const excludedVersionPrefixes = $(".dropdown__menu a[href]").toArray().flatMap((element) => {
|
|
2353
|
+
const href = $(element).attr("href");
|
|
2354
|
+
if (!href) return [];
|
|
2355
|
+
try {
|
|
2356
|
+
const target = new URL(href, document.finalUrl);
|
|
2357
|
+
if (target.origin !== source.origin || !isInScope(target, source, scopePath)) return [];
|
|
2358
|
+
const relativePath = target.pathname.slice(scopePath.length).replace(/^\/+/, "");
|
|
2359
|
+
const version = relativePath.split("/", 1)[0];
|
|
2360
|
+
if (!/^(?:legacy|next|v\d[\w.-]*)$/i.test(version)) return [];
|
|
2361
|
+
return [`${scopePath}/${version}`];
|
|
2362
|
+
} catch {
|
|
2363
|
+
return [];
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
return {
|
|
2367
|
+
scopePath,
|
|
2368
|
+
excludedVersionPrefixes: [...new Set(excludedVersionPrefixes)],
|
|
2369
|
+
sidebar
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
function docusaurusRouteIdForUrl(url, scopePath) {
|
|
2373
|
+
let path = url.pathname.replace(/\/+$/, "");
|
|
2374
|
+
if (scopePath === "/") path = path.replace(/^\/+/, "");
|
|
2375
|
+
else if (path === scopePath) path = "";
|
|
2376
|
+
else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
|
|
2377
|
+
else return null;
|
|
2378
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
2379
|
+
let decoded = segment;
|
|
2380
|
+
try {
|
|
2381
|
+
decoded = decodeURIComponent(segment);
|
|
2382
|
+
} catch {
|
|
2383
|
+
}
|
|
2384
|
+
return decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
|
|
2385
|
+
}).filter(Boolean);
|
|
2386
|
+
return segments.join("/") || "introduction";
|
|
2387
|
+
}
|
|
2388
|
+
function storageIdForDocusaurusRoute(routeId) {
|
|
2389
|
+
return /(?:^|\/)(?:index|readme)$/i.test(routeId) ? `${routeId}/index` : routeId;
|
|
2390
|
+
}
|
|
2391
|
+
function docusaurusNavigationFromSnapshots(snapshots, pages, scopePath) {
|
|
2392
|
+
const imported = new Set(pages.map((page) => page.navigationId));
|
|
2393
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
2394
|
+
const uniqueSnapshots = [...new Map(snapshots.map((snapshot) => [
|
|
2395
|
+
JSON.stringify(snapshot.items),
|
|
2396
|
+
snapshot
|
|
2397
|
+
])).values()];
|
|
2398
|
+
const mergedSnapshots = [];
|
|
2399
|
+
function mergeItems(target, incoming) {
|
|
2400
|
+
for (const item of incoming) {
|
|
2401
|
+
const existing = target.find((candidate) => candidate.label === item.label && (candidate.items || item.items)) ?? target.find((candidate) => candidate.label === item.label && candidate.href === item.href);
|
|
2402
|
+
if (!existing) {
|
|
2403
|
+
target.push(structuredClone(item));
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (!existing.href && item.href) existing.href = item.href;
|
|
2407
|
+
if (item.items) {
|
|
2408
|
+
existing.items ??= [];
|
|
2409
|
+
mergeItems(existing.items, item.items);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
for (const snapshot of uniqueSnapshots) {
|
|
2414
|
+
const incomingLabels = new Set(snapshot.items.map((item) => item.label));
|
|
2415
|
+
const cluster = mergedSnapshots.find((candidate) => {
|
|
2416
|
+
const candidateLabels = new Set(candidate.items.map((item) => item.label));
|
|
2417
|
+
const overlap = [...incomingLabels].filter((label) => candidateLabels.has(label)).length;
|
|
2418
|
+
return overlap >= Math.max(2, Math.ceil(Math.min(incomingLabels.size, candidateLabels.size) * 0.5));
|
|
2419
|
+
});
|
|
2420
|
+
if (cluster) mergeItems(cluster.items, snapshot.items);
|
|
2421
|
+
else mergedSnapshots.push(structuredClone(snapshot));
|
|
2422
|
+
}
|
|
2423
|
+
const pageIdsFor = (snapshot) => docusaurusSidebarHrefs(snapshot).flatMap((href) => {
|
|
2424
|
+
const id = docusaurusRouteIdForUrl(new URL(href), scopePath);
|
|
2425
|
+
return id && imported.has(id) ? [id] : [];
|
|
2426
|
+
});
|
|
2427
|
+
mergedSnapshots.sort((left, right) => pageIdsFor(right).length - pageIdsFor(left).length);
|
|
2428
|
+
const tabs = mergedSnapshots.flatMap((snapshot, snapshotIndex) => {
|
|
2429
|
+
function convert(items) {
|
|
2430
|
+
const converted2 = [];
|
|
2431
|
+
for (const item of items) {
|
|
2432
|
+
const routeId = item.href ? docusaurusRouteIdForUrl(new URL(item.href), scopePath) : null;
|
|
2433
|
+
const page = routeId && imported.has(routeId) && !claimed.has(routeId) ? routeId : null;
|
|
2434
|
+
if (page) claimed.add(page);
|
|
2435
|
+
const children = item.items ? convert(item.items) : [];
|
|
2436
|
+
if (children.length > 0 || item.isCategory) {
|
|
2437
|
+
converted2.push({ group: item.label, pages: page ? [page, ...children] : children });
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (page) converted2.push(page);
|
|
2441
|
+
}
|
|
2442
|
+
return converted2;
|
|
2443
|
+
}
|
|
2444
|
+
const converted = convert(snapshot.items);
|
|
2445
|
+
const loosePages = converted.filter((item) => typeof item === "string");
|
|
2446
|
+
const groups = converted.filter((item) => typeof item !== "string");
|
|
2447
|
+
if (loosePages.length > 0) groups.unshift({ group: "Overview", pages: loosePages });
|
|
2448
|
+
if (groups.length === 0) return [];
|
|
2449
|
+
const snapshotPageIds = pageIdsFor(snapshot);
|
|
2450
|
+
const firstSegments = new Set(snapshotPageIds.map((id) => id.split("/", 1)[0]));
|
|
2451
|
+
const tab = snapshotIndex > 0 && firstSegments.size === 1 ? normalizedLinkLabel([...firstSegments][0].replace(/[-_]/g, " ")).replace(/\b\w/g, (letter) => letter.toUpperCase()) : "Documentation";
|
|
2452
|
+
return [{ tab, groups }];
|
|
2453
|
+
});
|
|
2454
|
+
const additional = pages.map((page) => page.navigationId).filter((id) => !claimed.has(id));
|
|
2455
|
+
if (additional.length > 0) {
|
|
2456
|
+
if (tabs.length === 0) return buildNavigationFromPages(pages);
|
|
2457
|
+
tabs[0].groups?.push({ group: "Additional", pages: additional });
|
|
2458
|
+
}
|
|
2459
|
+
return { tabs };
|
|
2460
|
+
}
|
|
1381
2461
|
function htmlTopLevelNavigation(document, source, scopePath) {
|
|
1382
2462
|
const $ = load(document.body);
|
|
1383
2463
|
const collectCandidates = (selector) => {
|
|
@@ -1456,24 +2536,54 @@ async function migrateUrl(options) {
|
|
|
1456
2536
|
}
|
|
1457
2537
|
const htmlProbe = /html/i.test(initial.contentType) ? initial : await safeFetch(fetcher, source, "text/html,application/xhtml+xml");
|
|
1458
2538
|
const scopedHtmlProbe = htmlProbe && isInScope(htmlProbe.finalUrl, source, submittedScopePath) ? htmlProbe : null;
|
|
1459
|
-
const platform = detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
1460
|
-
|
|
2539
|
+
const platform = options.platform ?? detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
2540
|
+
let docusaurusHtmlProbe = platform === "docusaurus" ? scopedHtmlProbe : null;
|
|
2541
|
+
if (docusaurusHtmlProbe && !readDocusaurusSidebar(docusaurusHtmlProbe)) {
|
|
2542
|
+
const $ = load(docusaurusHtmlProbe.body);
|
|
2543
|
+
const docsHref = $("nav a[href]").toArray().map((element) => ({
|
|
2544
|
+
href: $(element).attr("href"),
|
|
2545
|
+
label: normalizedLinkLabel($(element).text())
|
|
2546
|
+
})).find((link) => /^docs(?:umentation)?$/i.test(link.label))?.href;
|
|
2547
|
+
if (docsHref) {
|
|
2548
|
+
try {
|
|
2549
|
+
const docsUrl = new URL(docsHref, docusaurusHtmlProbe.finalUrl);
|
|
2550
|
+
if (docsUrl.origin === source.origin && isInScope(docsUrl, source, submittedScopePath)) {
|
|
2551
|
+
const probe = await safeFetch(fetcher, docsUrl, "text/html,application/xhtml+xml");
|
|
2552
|
+
if (probe && probe.finalUrl.origin === source.origin && /html/i.test(probe.contentType)) {
|
|
2553
|
+
docusaurusHtmlProbe = probe;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
} catch {
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
|
|
2561
|
+
const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : docusaurusDiscovery?.scopePath ?? submittedScopePath;
|
|
1461
2562
|
const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
|
|
1462
2563
|
const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
|
|
1463
2564
|
const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
|
|
2565
|
+
if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
|
|
1464
2566
|
const queue = [source];
|
|
1465
2567
|
const queued = /* @__PURE__ */ new Set([candidateIdentity(source)]);
|
|
1466
2568
|
const warnings = [];
|
|
1467
2569
|
const fetchedSitemaps = /* @__PURE__ */ new Set();
|
|
2570
|
+
const docusaurusSidebars = docusaurusDiscovery?.sidebar ? [docusaurusDiscovery.sidebar] : [];
|
|
2571
|
+
const docusaurusRedirects = [];
|
|
1468
2572
|
function enqueue(value, base) {
|
|
1469
2573
|
if (queued.size >= MAX_DISCOVERED_URLS) return;
|
|
1470
2574
|
const url = normalizeCandidate(value, base, source, scopePath);
|
|
1471
2575
|
if (!url || queued.has(candidateIdentity(url))) return;
|
|
2576
|
+
if (platform === "docusaurus" && url.pathname.replace(/\/+$/, "") === `${scopePath}/search`) return;
|
|
2577
|
+
if (docusaurusDiscovery?.excludedVersionPrefixes.some((prefix) => {
|
|
2578
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
2579
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
2580
|
+
})) return;
|
|
1472
2581
|
queued.add(candidateIdentity(url));
|
|
1473
2582
|
queue.push(url);
|
|
1474
2583
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
2584
|
+
const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
|
|
2585
|
+
if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
|
|
2586
|
+
for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
|
|
1477
2587
|
}
|
|
1478
2588
|
async function discoverDocumentLinks(document, depth = 0) {
|
|
1479
2589
|
const links = sitemapLinks(document.body);
|
|
@@ -1540,6 +2650,7 @@ async function migrateUrl(options) {
|
|
|
1540
2650
|
}
|
|
1541
2651
|
}
|
|
1542
2652
|
const pages = [];
|
|
2653
|
+
const openApiFragments = [];
|
|
1543
2654
|
const seenIds = /* @__PURE__ */ new Set();
|
|
1544
2655
|
const seenSources = /* @__PURE__ */ new Set();
|
|
1545
2656
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1591,20 +2702,26 @@ async function migrateUrl(options) {
|
|
|
1591
2702
|
return { candidate, page: null, links: [], failure: false, budgetExceeded: true };
|
|
1592
2703
|
}
|
|
1593
2704
|
importedBytes += responseBytes;
|
|
1594
|
-
const
|
|
1595
|
-
const
|
|
1596
|
-
if (!
|
|
2705
|
+
const discoveredNavigationId = platform === "docusaurus" ? docusaurusRouteIdForUrl(document.finalUrl, scopePath) ?? docusaurusRouteIdForUrl(candidate, scopePath) : pageIdForUrl(document.finalUrl, scopePath) ?? pageIdForUrl(candidate, scopePath);
|
|
2706
|
+
const navigationId = discoveredNavigationId && canonicalLocalizedPageId(discoveredNavigationId) === sourceHomePageId ? "introduction" : discoveredNavigationId;
|
|
2707
|
+
if (!navigationId) return { candidate, page: null, links: [], failure: false };
|
|
2708
|
+
const id = platform === "docusaurus" ? storageIdForDocusaurusRoute(navigationId) : navigationId;
|
|
1597
2709
|
if (/(?:markdown|text\/plain)/i.test(document.contentType) || /\.mdx?$/i.test(document.finalUrl.pathname)) {
|
|
2710
|
+
const markdown = markdownPage(document, id);
|
|
2711
|
+
if (markdown.page) markdown.page.navigationId = navigationId;
|
|
1598
2712
|
return {
|
|
1599
2713
|
candidate,
|
|
1600
|
-
page:
|
|
2714
|
+
page: markdown.page,
|
|
2715
|
+
openApiFragment: markdown.openApiFragment,
|
|
1601
2716
|
links: markdownLinks(document.body),
|
|
1602
2717
|
base: document.finalUrl,
|
|
1603
2718
|
failure: false
|
|
1604
2719
|
};
|
|
1605
2720
|
}
|
|
1606
2721
|
const extracted = htmlPage(document, id);
|
|
1607
|
-
|
|
2722
|
+
if (extracted.page) extracted.page.navigationId = navigationId;
|
|
2723
|
+
const sidebar = platform === "docusaurus" ? readDocusaurusSidebar(document) : null;
|
|
2724
|
+
return { candidate, page: extracted.page, links: extracted.links, base: document.finalUrl, sidebar, failure: false };
|
|
1608
2725
|
}));
|
|
1609
2726
|
for (const result of results) {
|
|
1610
2727
|
if (result.failure) {
|
|
@@ -1612,13 +2729,29 @@ async function migrateUrl(options) {
|
|
|
1612
2729
|
continue;
|
|
1613
2730
|
}
|
|
1614
2731
|
if (result.budgetExceeded) continue;
|
|
1615
|
-
if (result.page
|
|
2732
|
+
if (result.page && platform !== "docusaurus") {
|
|
2733
|
+
result.page.id = canonicalLocalizedPageId(result.page.id);
|
|
2734
|
+
result.page.navigationId = canonicalLocalizedPageId(result.page.navigationId);
|
|
2735
|
+
}
|
|
1616
2736
|
const sourceIdentity = result.page ? candidateIdentity(new URL(result.page.source)) : null;
|
|
1617
2737
|
if (result.page && sourceIdentity && !seenSources.has(sourceIdentity) && !seenIds.has(result.page.id) && pages.length < maxPages) {
|
|
1618
2738
|
pages.push(result.page);
|
|
2739
|
+
if ("openApiFragment" in result && result.openApiFragment) {
|
|
2740
|
+
openApiFragments.push(result.openApiFragment);
|
|
2741
|
+
}
|
|
1619
2742
|
seenIds.add(result.page.id);
|
|
1620
2743
|
seenSources.add(sourceIdentity);
|
|
1621
2744
|
}
|
|
2745
|
+
if (platform === "docusaurus" && result.page) {
|
|
2746
|
+
const candidateRoute = docusaurusRouteIdForUrl(result.candidate, scopePath);
|
|
2747
|
+
if (candidateRoute && candidateRoute !== result.page.navigationId) {
|
|
2748
|
+
docusaurusRedirects.push({
|
|
2749
|
+
source: candidateRoute === "introduction" ? "/" : `/${candidateRoute}`,
|
|
2750
|
+
destination: result.page.navigationId === "introduction" ? "/" : `/${result.page.navigationId}`
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if ("sidebar" in result && result.sidebar) docusaurusSidebars.push(result.sidebar);
|
|
1622
2755
|
for (const link of result.links) enqueue(link, result.base ?? result.candidate);
|
|
1623
2756
|
}
|
|
1624
2757
|
}
|
|
@@ -1631,7 +2764,7 @@ async function migrateUrl(options) {
|
|
|
1631
2764
|
page.navigationId = navigationSegments.join("/") || "introduction";
|
|
1632
2765
|
}
|
|
1633
2766
|
}
|
|
1634
|
-
const importedIds = new Set(pages.map((page) => page.
|
|
2767
|
+
const importedIds = new Set(pages.map((page) => page.navigationId));
|
|
1635
2768
|
for (const page of pages) {
|
|
1636
2769
|
page.body = rewriteInternalLinks(
|
|
1637
2770
|
page.body,
|
|
@@ -1651,10 +2784,29 @@ async function migrateUrl(options) {
|
|
|
1651
2784
|
...entry,
|
|
1652
2785
|
pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
|
|
1653
2786
|
}));
|
|
1654
|
-
const docsConfig = buildNavigationFromPages(pages, {
|
|
2787
|
+
const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
|
|
1655
2788
|
topLevelTabs: platform === "mintlify",
|
|
1656
2789
|
topLevelNavigation
|
|
1657
2790
|
});
|
|
2791
|
+
if (docusaurusRedirects.length > 0) {
|
|
2792
|
+
docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
|
|
2793
|
+
`${redirect.source}:${redirect.destination}`,
|
|
2794
|
+
redirect
|
|
2795
|
+
])).values()];
|
|
2796
|
+
}
|
|
2797
|
+
const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
|
|
2798
|
+
if (openApiAsset) {
|
|
2799
|
+
const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
|
|
2800
|
+
const apiTab = docsConfig.tabs.find((tab) => tab.groups?.some((group) => {
|
|
2801
|
+
const containsOperation = (pages2) => pages2.some((page) => {
|
|
2802
|
+
return typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages);
|
|
2803
|
+
});
|
|
2804
|
+
return containsOperation(group.pages);
|
|
2805
|
+
})) ?? docsConfig.tabs[0];
|
|
2806
|
+
if (apiTab) {
|
|
2807
|
+
apiTab.api = { source: "/openapi.yaml", navigation: false };
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
1658
2810
|
const locales = [...new Set(pages.map((page) => page.locale).filter((value) => Boolean(value)))];
|
|
1659
2811
|
if (locales.length > 0) {
|
|
1660
2812
|
docsConfig.i18n = {
|
|
@@ -1670,7 +2822,7 @@ async function migrateUrl(options) {
|
|
|
1670
2822
|
sourceKind: "url",
|
|
1671
2823
|
platform,
|
|
1672
2824
|
pages,
|
|
1673
|
-
assets: [],
|
|
2825
|
+
assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
|
|
1674
2826
|
docsConfig,
|
|
1675
2827
|
warnings,
|
|
1676
2828
|
stats: {
|