@thallylabs/migrate 0.1.2 → 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 +18 -1
- package/dist/index.js +1107 -116
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
-
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
33
|
-
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(input.navigationId ?? input.id);
|
|
34
|
-
const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
|
|
35
|
-
return {
|
|
36
|
-
id: input.id,
|
|
37
|
-
navigationId: input.navigationId ?? input.id,
|
|
38
|
-
locale: input.locale,
|
|
39
|
-
title,
|
|
40
|
-
description,
|
|
41
|
-
keywords,
|
|
42
|
-
openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
|
|
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
|
|
|
@@ -861,7 +1562,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
861
1562
|
// src/url.ts
|
|
862
1563
|
import { load } from "cheerio";
|
|
863
1564
|
import TurndownService from "turndown";
|
|
864
|
-
import { parse as
|
|
1565
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
865
1566
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
866
1567
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
867
1568
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -1336,7 +2037,7 @@ function extractEmbeddedOpenApi(content) {
|
|
|
1336
2037
|
const match = fencePattern.exec(content);
|
|
1337
2038
|
if (!match) return { content };
|
|
1338
2039
|
try {
|
|
1339
|
-
const parsed =
|
|
2040
|
+
const parsed = parseYaml2(match[5], { maxAliasCount: 50 });
|
|
1340
2041
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
1341
2042
|
const document = parsed;
|
|
1342
2043
|
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|
|
@@ -1452,6 +2153,7 @@ function htmlPage(document, id) {
|
|
|
1452
2153
|
const content = $('main article, article, main, [role="main"]').first();
|
|
1453
2154
|
const root = content.length ? content : $("body");
|
|
1454
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();
|
|
1455
2157
|
root.find("a[href]").each((_index, element) => {
|
|
1456
2158
|
const href = $(element).attr("href");
|
|
1457
2159
|
if (!href) return;
|
|
@@ -1482,12 +2184,78 @@ function htmlPage(document, id) {
|
|
|
1482
2184
|
});
|
|
1483
2185
|
const turndown = new TurndownService({ codeBlockStyle: "fenced", emDelimiter: "_", headingStyle: "atx" });
|
|
1484
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
|
+
});
|
|
1485
2251
|
turndown.addRule("migration-safe-fenced-code", {
|
|
1486
2252
|
filter: (node) => node.nodeName === "PRE",
|
|
1487
2253
|
replacement: (_content, node) => {
|
|
1488
2254
|
const element = node;
|
|
1489
|
-
const
|
|
1490
|
-
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] ?? "";
|
|
1491
2259
|
const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
|
|
1492
2260
|
const fence = "`".repeat(Math.max(3, longestFence + 1));
|
|
1493
2261
|
return `
|
|
@@ -1502,7 +2270,7 @@ ${fence}
|
|
|
1502
2270
|
const escape = turndown.escape.bind(turndown);
|
|
1503
2271
|
turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
|
|
1504
2272
|
const body = turndown.turndown(root.html() ?? "").trim();
|
|
1505
|
-
if (!title || body.length < 40) return { page: null, links };
|
|
2273
|
+
if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
|
|
1506
2274
|
return {
|
|
1507
2275
|
page: {
|
|
1508
2276
|
id,
|
|
@@ -1520,6 +2288,176 @@ function htmlNavigationLinks(document) {
|
|
|
1520
2288
|
const $ = load(document.body);
|
|
1521
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);
|
|
1522
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
|
+
}
|
|
1523
2461
|
function htmlTopLevelNavigation(document, source, scopePath) {
|
|
1524
2462
|
const $ = load(document.body);
|
|
1525
2463
|
const collectCandidates = (selector) => {
|
|
@@ -1598,24 +2536,54 @@ async function migrateUrl(options) {
|
|
|
1598
2536
|
}
|
|
1599
2537
|
const htmlProbe = /html/i.test(initial.contentType) ? initial : await safeFetch(fetcher, source, "text/html,application/xhtml+xml");
|
|
1600
2538
|
const scopedHtmlProbe = htmlProbe && isInScope(htmlProbe.finalUrl, source, submittedScopePath) ? htmlProbe : null;
|
|
1601
|
-
const platform = detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
1602
|
-
|
|
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;
|
|
1603
2562
|
const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
|
|
1604
2563
|
const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
|
|
1605
2564
|
const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
|
|
2565
|
+
if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
|
|
1606
2566
|
const queue = [source];
|
|
1607
2567
|
const queued = /* @__PURE__ */ new Set([candidateIdentity(source)]);
|
|
1608
2568
|
const warnings = [];
|
|
1609
2569
|
const fetchedSitemaps = /* @__PURE__ */ new Set();
|
|
2570
|
+
const docusaurusSidebars = docusaurusDiscovery?.sidebar ? [docusaurusDiscovery.sidebar] : [];
|
|
2571
|
+
const docusaurusRedirects = [];
|
|
1610
2572
|
function enqueue(value, base) {
|
|
1611
2573
|
if (queued.size >= MAX_DISCOVERED_URLS) return;
|
|
1612
2574
|
const url = normalizeCandidate(value, base, source, scopePath);
|
|
1613
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;
|
|
1614
2581
|
queued.add(candidateIdentity(url));
|
|
1615
2582
|
queue.push(url);
|
|
1616
2583
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
2584
|
+
const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
|
|
2585
|
+
if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
|
|
2586
|
+
for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
|
|
1619
2587
|
}
|
|
1620
2588
|
async function discoverDocumentLinks(document, depth = 0) {
|
|
1621
2589
|
const links = sitemapLinks(document.body);
|
|
@@ -1734,11 +2702,13 @@ async function migrateUrl(options) {
|
|
|
1734
2702
|
return { candidate, page: null, links: [], failure: false, budgetExceeded: true };
|
|
1735
2703
|
}
|
|
1736
2704
|
importedBytes += responseBytes;
|
|
1737
|
-
const
|
|
1738
|
-
const
|
|
1739
|
-
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;
|
|
1740
2709
|
if (/(?:markdown|text\/plain)/i.test(document.contentType) || /\.mdx?$/i.test(document.finalUrl.pathname)) {
|
|
1741
2710
|
const markdown = markdownPage(document, id);
|
|
2711
|
+
if (markdown.page) markdown.page.navigationId = navigationId;
|
|
1742
2712
|
return {
|
|
1743
2713
|
candidate,
|
|
1744
2714
|
page: markdown.page,
|
|
@@ -1749,7 +2719,9 @@ async function migrateUrl(options) {
|
|
|
1749
2719
|
};
|
|
1750
2720
|
}
|
|
1751
2721
|
const extracted = htmlPage(document, id);
|
|
1752
|
-
|
|
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 };
|
|
1753
2725
|
}));
|
|
1754
2726
|
for (const result of results) {
|
|
1755
2727
|
if (result.failure) {
|
|
@@ -1757,7 +2729,10 @@ async function migrateUrl(options) {
|
|
|
1757
2729
|
continue;
|
|
1758
2730
|
}
|
|
1759
2731
|
if (result.budgetExceeded) continue;
|
|
1760
|
-
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
|
+
}
|
|
1761
2736
|
const sourceIdentity = result.page ? candidateIdentity(new URL(result.page.source)) : null;
|
|
1762
2737
|
if (result.page && sourceIdentity && !seenSources.has(sourceIdentity) && !seenIds.has(result.page.id) && pages.length < maxPages) {
|
|
1763
2738
|
pages.push(result.page);
|
|
@@ -1767,6 +2742,16 @@ async function migrateUrl(options) {
|
|
|
1767
2742
|
seenIds.add(result.page.id);
|
|
1768
2743
|
seenSources.add(sourceIdentity);
|
|
1769
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);
|
|
1770
2755
|
for (const link of result.links) enqueue(link, result.base ?? result.candidate);
|
|
1771
2756
|
}
|
|
1772
2757
|
}
|
|
@@ -1779,7 +2764,7 @@ async function migrateUrl(options) {
|
|
|
1779
2764
|
page.navigationId = navigationSegments.join("/") || "introduction";
|
|
1780
2765
|
}
|
|
1781
2766
|
}
|
|
1782
|
-
const importedIds = new Set(pages.map((page) => page.
|
|
2767
|
+
const importedIds = new Set(pages.map((page) => page.navigationId));
|
|
1783
2768
|
for (const page of pages) {
|
|
1784
2769
|
page.body = rewriteInternalLinks(
|
|
1785
2770
|
page.body,
|
|
@@ -1799,10 +2784,16 @@ async function migrateUrl(options) {
|
|
|
1799
2784
|
...entry,
|
|
1800
2785
|
pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
|
|
1801
2786
|
}));
|
|
1802
|
-
const docsConfig = buildNavigationFromPages(pages, {
|
|
2787
|
+
const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
|
|
1803
2788
|
topLevelTabs: platform === "mintlify",
|
|
1804
2789
|
topLevelNavigation
|
|
1805
2790
|
});
|
|
2791
|
+
if (docusaurusRedirects.length > 0) {
|
|
2792
|
+
docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
|
|
2793
|
+
`${redirect.source}:${redirect.destination}`,
|
|
2794
|
+
redirect
|
|
2795
|
+
])).values()];
|
|
2796
|
+
}
|
|
1806
2797
|
const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
|
|
1807
2798
|
if (openApiAsset) {
|
|
1808
2799
|
const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
|