@thallylabs/migrate 0.1.2 → 0.2.1
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 +1131 -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,564 @@ 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/frontmatter.ts
|
|
524
|
+
import matter from "gray-matter";
|
|
525
|
+
var FRONTMATTER_OPTIONS = {
|
|
526
|
+
engines: {
|
|
527
|
+
javascript: () => ({}),
|
|
528
|
+
js: () => ({})
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
function parseFrontmatter(raw) {
|
|
532
|
+
return matter(raw, FRONTMATTER_OPTIONS);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/mdx.ts
|
|
536
|
+
function titleFromId(id) {
|
|
537
|
+
const value = id.split("/").at(-1) ?? id;
|
|
538
|
+
if (value === "introduction") return "Introduction";
|
|
539
|
+
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
540
|
+
}
|
|
541
|
+
function firstParagraph(content) {
|
|
542
|
+
for (const block of content.split(/\n\s*\n/)) {
|
|
543
|
+
const value = block.replace(/\s+/g, " ").trim();
|
|
544
|
+
if (!value || /^(?:#|```|:::|<|import\s|export\s)/.test(value)) continue;
|
|
545
|
+
return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").slice(0, 240);
|
|
546
|
+
}
|
|
547
|
+
return "";
|
|
548
|
+
}
|
|
549
|
+
function docusaurusAdmonitionTag(kind) {
|
|
550
|
+
if (kind === "danger") return "Error";
|
|
551
|
+
if (kind === "info") return "Info";
|
|
552
|
+
if (kind === "caution" || kind === "warning") return "Warning";
|
|
553
|
+
return "Note";
|
|
554
|
+
}
|
|
555
|
+
function normalizeDocusaurusAdmonitions(body) {
|
|
556
|
+
const lines = body.split("\n");
|
|
557
|
+
const openAdmonitions = [];
|
|
558
|
+
let codeFence = null;
|
|
559
|
+
return lines.map((line) => {
|
|
560
|
+
const codeMatch = line.match(/^\s*(`{3,}|~{3,})/);
|
|
561
|
+
if (codeMatch) {
|
|
562
|
+
if (!codeFence) codeFence = codeMatch[1][0];
|
|
563
|
+
else if (codeMatch[1][0] === codeFence) codeFence = null;
|
|
564
|
+
return line;
|
|
565
|
+
}
|
|
566
|
+
if (codeFence) return line;
|
|
567
|
+
const opening = line.match(/^\s*(:{3,})(note|tip|info|warning|caution|danger)(?:\[([^\]]+)\]|\s+(.+))?\s*$/i);
|
|
568
|
+
if (opening) {
|
|
569
|
+
const tag = docusaurusAdmonitionTag(opening[2].toLowerCase());
|
|
570
|
+
openAdmonitions.push({ delimiter: opening[1], tag });
|
|
571
|
+
const title = (opening[3] ?? opening[4])?.trim();
|
|
572
|
+
return title ? `<${tag}>
|
|
573
|
+
**${title}**` : `<${tag}>`;
|
|
574
|
+
}
|
|
575
|
+
const closing = line.match(/^\s*(:{3,})\s*$/);
|
|
576
|
+
const current = openAdmonitions.at(-1);
|
|
577
|
+
if (closing && current?.delimiter === closing[1]) {
|
|
578
|
+
openAdmonitions.pop();
|
|
579
|
+
return `</${current.tag}>`;
|
|
580
|
+
}
|
|
581
|
+
return line;
|
|
582
|
+
}).join("\n");
|
|
583
|
+
}
|
|
584
|
+
function normalizeMdx(body) {
|
|
585
|
+
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) => {
|
|
586
|
+
const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
|
|
587
|
+
return `<Tab title="${title.replace(/"/g, """)}">`;
|
|
588
|
+
}).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>");
|
|
589
|
+
}
|
|
590
|
+
function parseMarkdownPage(input) {
|
|
591
|
+
const parsed = parseFrontmatter(input.raw);
|
|
592
|
+
const fallbackIdentity = {
|
|
593
|
+
id: input.id,
|
|
594
|
+
navigationId: input.navigationId ?? input.id,
|
|
595
|
+
...input.locale ? { locale: input.locale } : {}
|
|
596
|
+
};
|
|
597
|
+
const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
|
|
598
|
+
const body = normalizeMdx(parsed.content).trim();
|
|
599
|
+
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
600
|
+
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(identity.navigationId);
|
|
601
|
+
const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
|
|
602
|
+
return {
|
|
603
|
+
id: identity.id,
|
|
604
|
+
navigationId: identity.navigationId,
|
|
605
|
+
locale: identity.locale,
|
|
606
|
+
title,
|
|
607
|
+
description,
|
|
608
|
+
keywords,
|
|
609
|
+
openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
|
|
610
|
+
body,
|
|
611
|
+
source: input.source
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
95
615
|
// src/navigation.ts
|
|
616
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
617
|
+
import { dirname, extname as extname2, relative as relative2 } from "path";
|
|
96
618
|
var LANGUAGE_LABELS = {
|
|
97
619
|
ar: "Arabic",
|
|
98
620
|
de: "German",
|
|
@@ -111,7 +633,7 @@ var LANGUAGE_LABELS = {
|
|
|
111
633
|
uk: "Ukrainian",
|
|
112
634
|
zh: "Chinese"
|
|
113
635
|
};
|
|
114
|
-
function
|
|
636
|
+
function objectValue2(value) {
|
|
115
637
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
116
638
|
}
|
|
117
639
|
function labelFor(value, fallback) {
|
|
@@ -127,7 +649,7 @@ function jsonPointer(root, pointer) {
|
|
|
127
649
|
if (!pointer || pointer === "#") return root;
|
|
128
650
|
const tokens = pointer.replace(/^#\/?/, "").split("/").filter(Boolean);
|
|
129
651
|
return tokens.reduce((current, token) => {
|
|
130
|
-
const object =
|
|
652
|
+
const object = objectValue2(current);
|
|
131
653
|
if (!object) return void 0;
|
|
132
654
|
return object[token.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
133
655
|
}, root);
|
|
@@ -136,7 +658,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
136
658
|
if (Array.isArray(value)) {
|
|
137
659
|
return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
|
|
138
660
|
}
|
|
139
|
-
const object =
|
|
661
|
+
const object = objectValue2(value);
|
|
140
662
|
if (!object) return value;
|
|
141
663
|
if (typeof object.$ref === "string") {
|
|
142
664
|
const [filePart, pointer = ""] = object.$ref.split("#", 2);
|
|
@@ -146,7 +668,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
146
668
|
const stackKey = `${referencedFile}#${pointer}`;
|
|
147
669
|
if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
|
|
148
670
|
stack.add(stackKey);
|
|
149
|
-
const referencedRoot = JSON.parse(
|
|
671
|
+
const referencedRoot = JSON.parse(readFileSync2(referencedFile, "utf8"));
|
|
150
672
|
const referenced = jsonPointer(referencedRoot, pointer ? `#${pointer}` : "");
|
|
151
673
|
const resolved = resolveJsonReferences(referenced, referencedFile, repositoryRoot, stack);
|
|
152
674
|
stack.delete(stackKey);
|
|
@@ -160,9 +682,9 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
160
682
|
);
|
|
161
683
|
}
|
|
162
684
|
function readMintlifyConfig(repositoryRoot) {
|
|
163
|
-
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(
|
|
685
|
+
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(existsSync2);
|
|
164
686
|
if (!configPath) return null;
|
|
165
|
-
const raw = JSON.parse(
|
|
687
|
+
const raw = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
166
688
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
167
689
|
}
|
|
168
690
|
function normalizePageRef(value) {
|
|
@@ -184,7 +706,7 @@ function registerReference(value, context) {
|
|
|
184
706
|
}
|
|
185
707
|
function convertPage(value, context) {
|
|
186
708
|
if (typeof value === "string") return registerReference(value, context);
|
|
187
|
-
const object =
|
|
709
|
+
const object = objectValue2(value);
|
|
188
710
|
if (!object) return null;
|
|
189
711
|
if (typeof object.page === "string") return registerReference(object.page, context);
|
|
190
712
|
const pages = Array.isArray(object.pages) ? object.pages : [];
|
|
@@ -220,12 +742,12 @@ function convertGroups(values, context, fallbackGroup) {
|
|
|
220
742
|
return groups;
|
|
221
743
|
}
|
|
222
744
|
function convertContainerToTabs(containerValue, context, fallbackTab) {
|
|
223
|
-
const container =
|
|
745
|
+
const container = objectValue2(containerValue);
|
|
224
746
|
if (!container) return [];
|
|
225
747
|
for (const key of ["tabs", "anchors", "products", "dropdowns", "versions", "menus"]) {
|
|
226
748
|
if (!Array.isArray(container[key])) continue;
|
|
227
749
|
const tabs = container[key].flatMap((value, index) => {
|
|
228
|
-
const object =
|
|
750
|
+
const object = objectValue2(value);
|
|
229
751
|
if (!object) return [];
|
|
230
752
|
const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
|
|
231
753
|
if (typeof object.href === "string" && !object.pages && !object.groups) {
|
|
@@ -252,8 +774,8 @@ function projectMintlifyNavigation(config) {
|
|
|
252
774
|
const warnings = [];
|
|
253
775
|
const references = [];
|
|
254
776
|
const seenReferences = /* @__PURE__ */ new Set();
|
|
255
|
-
const navigation =
|
|
256
|
-
const languages = Array.isArray(navigation.languages) ? navigation.languages.map(
|
|
777
|
+
const navigation = objectValue2(config.navigation) ?? config;
|
|
778
|
+
const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
|
|
257
779
|
let tabs = [];
|
|
258
780
|
let i18n;
|
|
259
781
|
if (languages.length > 0) {
|
|
@@ -291,7 +813,7 @@ function projectMintlifyNavigation(config) {
|
|
|
291
813
|
tabs.push({ tab: "Changelog", href: "/changelog" });
|
|
292
814
|
}
|
|
293
815
|
const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
|
|
294
|
-
const redirect =
|
|
816
|
+
const redirect = objectValue2(value);
|
|
295
817
|
if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
|
|
296
818
|
return [{
|
|
297
819
|
source: redirect.source,
|
|
@@ -305,7 +827,7 @@ function projectMintlifyNavigation(config) {
|
|
|
305
827
|
warnings
|
|
306
828
|
};
|
|
307
829
|
}
|
|
308
|
-
function
|
|
830
|
+
function titleCase2(value) {
|
|
309
831
|
return value.split(/[-_]/).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
310
832
|
}
|
|
311
833
|
var TOP_LEVEL_LABELS = {
|
|
@@ -317,7 +839,7 @@ var TOP_LEVEL_LABELS = {
|
|
|
317
839
|
sdk: "SDK"
|
|
318
840
|
};
|
|
319
841
|
function topLevelLabel(segment) {
|
|
320
|
-
return TOP_LEVEL_LABELS[segment] ??
|
|
842
|
+
return TOP_LEVEL_LABELS[segment] ?? titleCase2(segment);
|
|
321
843
|
}
|
|
322
844
|
function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(section)) {
|
|
323
845
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -329,7 +851,7 @@ function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(sect
|
|
|
329
851
|
groups.set(nestedSegment, group);
|
|
330
852
|
}
|
|
331
853
|
return [...groups].map(([segment, pages]) => ({
|
|
332
|
-
group: segment === "overview" ? sectionLabel :
|
|
854
|
+
group: segment === "overview" ? sectionLabel : titleCase2(segment),
|
|
333
855
|
pages
|
|
334
856
|
}));
|
|
335
857
|
}
|
|
@@ -401,20 +923,20 @@ function buildNavigationFromPages(pages, options = {}) {
|
|
|
401
923
|
buckets.set(segment, bucket);
|
|
402
924
|
}
|
|
403
925
|
const groups = [...buckets].map(([segment, pageIds]) => ({
|
|
404
|
-
group: segment === "overview" ? "Overview" :
|
|
926
|
+
group: segment === "overview" ? "Overview" : titleCase2(segment),
|
|
405
927
|
pages: pageIds
|
|
406
928
|
}));
|
|
407
929
|
return { tabs: [{ tab: "Documentation", groups }] };
|
|
408
930
|
}
|
|
409
931
|
function isDocumentationExtension(filename) {
|
|
410
|
-
return [".md", ".mdx", ".rst", ".txt"].includes(
|
|
932
|
+
return [".md", ".mdx", ".rst", ".txt"].includes(extname2(filename).toLowerCase());
|
|
411
933
|
}
|
|
412
934
|
|
|
413
935
|
// src/repository.ts
|
|
414
936
|
var MAX_SOURCE_FILES = 5e3;
|
|
415
937
|
var MAX_PAGE_BYTES = 2e6;
|
|
416
938
|
var MAX_ASSET_BYTES = 25e6;
|
|
417
|
-
var MAX_TOTAL_ASSET_BYTES =
|
|
939
|
+
var MAX_TOTAL_ASSET_BYTES = 5e8;
|
|
418
940
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
419
941
|
".git",
|
|
420
942
|
".github",
|
|
@@ -461,6 +983,71 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
461
983
|
"swagger.yaml",
|
|
462
984
|
"swagger.yml"
|
|
463
985
|
]);
|
|
986
|
+
var DOCUSAURUS_CONFIG_FILENAMES = [
|
|
987
|
+
"docusaurus.config.js",
|
|
988
|
+
"docusaurus.config.ts",
|
|
989
|
+
"docusaurus.config.mjs"
|
|
990
|
+
];
|
|
991
|
+
function hasDocusaurusConfig(directory) {
|
|
992
|
+
return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
|
|
993
|
+
const path = resolveWithin(directory, filename);
|
|
994
|
+
return existsSync3(path) && lstatSync2(path).isFile();
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
998
|
+
if (docsDir !== void 0) {
|
|
999
|
+
let candidate = docsDir.replace(/\/+$/, "");
|
|
1000
|
+
while (true) {
|
|
1001
|
+
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
1002
|
+
if (hasDocusaurusConfig(path)) return path;
|
|
1003
|
+
if (!candidate) break;
|
|
1004
|
+
const parent = dirname2(candidate);
|
|
1005
|
+
candidate = parent === "." ? "" : parent;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
const queue = [{ directory: repositoryDir, depth: 0 }];
|
|
1009
|
+
let visited = 0;
|
|
1010
|
+
while (queue.length > 0 && visited < 500) {
|
|
1011
|
+
const current = queue.shift();
|
|
1012
|
+
visited++;
|
|
1013
|
+
if (hasDocusaurusConfig(current.directory)) return current.directory;
|
|
1014
|
+
if (current.depth >= 4) continue;
|
|
1015
|
+
for (const entry of readdirSync(current.directory, { withFileTypes: true })) {
|
|
1016
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
1017
|
+
queue.push({
|
|
1018
|
+
directory: resolveWithin(current.directory, entry.name),
|
|
1019
|
+
depth: current.depth + 1
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
function readDocusaurusConfigSource(projectRoot) {
|
|
1026
|
+
const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync3(candidate) && lstatSync2(candidate).isFile());
|
|
1027
|
+
if (!path || lstatSync2(path).size > 1e6) return "";
|
|
1028
|
+
return readFileSync3(path, "utf8");
|
|
1029
|
+
}
|
|
1030
|
+
function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
1031
|
+
const source = readDocusaurusConfigSource(projectRoot);
|
|
1032
|
+
const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
|
|
1033
|
+
return configured?.replace(/^\.\//, "").replace(/\/+$/, "") || "docs";
|
|
1034
|
+
}
|
|
1035
|
+
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
1036
|
+
const source = readDocusaurusConfigSource(projectRoot);
|
|
1037
|
+
const projectRelative = relative3(repositoryDir, projectRoot).replace(/\\/g, "/");
|
|
1038
|
+
const plugins = [];
|
|
1039
|
+
const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
|
|
1040
|
+
for (const match of source.matchAll(matcher)) {
|
|
1041
|
+
const localPath = match[2].replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1042
|
+
const routePrefix = match[4].replace(/^\/+|\/+$/g, "");
|
|
1043
|
+
if (!localPath || !routePrefix) continue;
|
|
1044
|
+
const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
|
|
1045
|
+
const absolute = resolveWithin(repositoryDir, docsDir);
|
|
1046
|
+
if (!existsSync3(absolute) || !lstatSync2(absolute).isDirectory()) continue;
|
|
1047
|
+
plugins.push({ docsDir, routePrefix });
|
|
1048
|
+
}
|
|
1049
|
+
return plugins;
|
|
1050
|
+
}
|
|
464
1051
|
function parseGitHubRepositoryUrl(rawUrl) {
|
|
465
1052
|
let url;
|
|
466
1053
|
try {
|
|
@@ -514,21 +1101,22 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
514
1101
|
});
|
|
515
1102
|
}
|
|
516
1103
|
function detectRepositoryPlatform(repositoryDir) {
|
|
517
|
-
if (
|
|
1104
|
+
if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
|
|
518
1105
|
const docsJson = resolveWithin(repositoryDir, "docs.json");
|
|
519
|
-
if (
|
|
1106
|
+
if (existsSync3(docsJson)) {
|
|
520
1107
|
try {
|
|
521
|
-
const config = JSON.parse(
|
|
1108
|
+
const config = JSON.parse(readFileSync3(docsJson, "utf8"));
|
|
522
1109
|
if (String(config.$schema ?? "").includes("mintlify") || "navigation" in config) return "mintlify";
|
|
523
1110
|
if (Array.isArray(config.tabs)) return "thally";
|
|
524
1111
|
} catch {
|
|
525
1112
|
}
|
|
526
1113
|
}
|
|
527
|
-
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) =>
|
|
528
|
-
if (
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
531
|
-
if (["
|
|
1114
|
+
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "docusaurus";
|
|
1115
|
+
if (findDocusaurusProjectRoot(repositoryDir)) return "docusaurus";
|
|
1116
|
+
if (existsSync3(resolveWithin(repositoryDir, "SUMMARY.md"))) return "gitbook";
|
|
1117
|
+
if (existsSync3(resolveWithin(repositoryDir, ".vitepress"))) return "vitepress";
|
|
1118
|
+
if (["astro.config.mjs", "astro.config.ts"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "starlight";
|
|
1119
|
+
if (["pages/_meta.json", "_meta.json"].some((name) => existsSync3(resolveWithin(repositoryDir, name)))) return "nextra";
|
|
532
1120
|
return "unknown";
|
|
533
1121
|
}
|
|
534
1122
|
function containsMarkdown(directory, depth = 0) {
|
|
@@ -536,7 +1124,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
536
1124
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
537
1125
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
538
1126
|
const path = resolveWithin(directory, entry.name);
|
|
539
|
-
if (entry.isFile() && [".md", ".mdx"].includes(
|
|
1127
|
+
if (entry.isFile() && [".md", ".mdx"].includes(extname3(entry.name).toLowerCase())) return true;
|
|
540
1128
|
if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
|
|
541
1129
|
}
|
|
542
1130
|
return false;
|
|
@@ -544,7 +1132,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
544
1132
|
function detectRepositoryDocsDir(repositoryDir) {
|
|
545
1133
|
for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
|
|
546
1134
|
const path = resolveWithin(repositoryDir, candidate);
|
|
547
|
-
if (
|
|
1135
|
+
if (existsSync3(path) && lstatSync2(path).isDirectory() && containsMarkdown(path)) return candidate;
|
|
548
1136
|
}
|
|
549
1137
|
return "";
|
|
550
1138
|
}
|
|
@@ -572,22 +1160,24 @@ function findOpenApi(files) {
|
|
|
572
1160
|
function withoutFrontmatter(value) {
|
|
573
1161
|
return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
574
1162
|
}
|
|
575
|
-
function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0) {
|
|
1163
|
+
function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot) {
|
|
576
1164
|
if (depth >= 8) return raw;
|
|
577
1165
|
const snippets = /* @__PURE__ */ new Map();
|
|
578
1166
|
const withoutImports = raw.replace(
|
|
579
|
-
/^import\s+([A-Z][A-Za-z0-9_]*)\s+from\s+['"]([^'"]+\.mdx?)['"]\s
|
|
1167
|
+
/^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm,
|
|
580
1168
|
(_statement, componentName, sourcePath) => {
|
|
581
1169
|
try {
|
|
582
|
-
const
|
|
1170
|
+
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, "/");
|
|
1171
|
+
const candidate = resolveWithin(repositoryRoot, repositoryRelative);
|
|
583
1172
|
resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
|
|
584
|
-
if (!
|
|
1173
|
+
if (!existsSync3(candidate) || !lstatSync2(candidate).isFile()) throw new Error("file not found");
|
|
585
1174
|
const nested = inlineMdxSnippets(
|
|
586
|
-
withoutFrontmatter(
|
|
1175
|
+
withoutFrontmatter(readFileSync3(candidate, "utf8")),
|
|
587
1176
|
candidate,
|
|
588
1177
|
repositoryRoot,
|
|
589
1178
|
warnings,
|
|
590
|
-
depth + 1
|
|
1179
|
+
depth + 1,
|
|
1180
|
+
siteRoot
|
|
591
1181
|
);
|
|
592
1182
|
snippets.set(componentName, nested);
|
|
593
1183
|
return "";
|
|
@@ -622,10 +1212,27 @@ function injectOpenApi(config, filename) {
|
|
|
622
1212
|
function migrateRepository(options) {
|
|
623
1213
|
const repositoryDir = options.repositoryDir;
|
|
624
1214
|
const platform = options.platform ?? detectRepositoryPlatform(repositoryDir);
|
|
1215
|
+
const docusaurusProjectRoot = platform === "docusaurus" ? findDocusaurusProjectRoot(repositoryDir, options.docsDir) : null;
|
|
1216
|
+
const configuredDocsDir = (() => {
|
|
1217
|
+
if (options.docsDir !== void 0) {
|
|
1218
|
+
const selected = resolveWithin(repositoryDir, options.docsDir || ".");
|
|
1219
|
+
if (platform === "docusaurus" && hasDocusaurusConfig(selected)) {
|
|
1220
|
+
const projectRelative = relative3(repositoryDir, selected).replace(/\\/g, "/");
|
|
1221
|
+
return [projectRelative, primaryDocusaurusDocsDirectory(selected)].filter(Boolean).join("/");
|
|
1222
|
+
}
|
|
1223
|
+
return options.docsDir;
|
|
1224
|
+
}
|
|
1225
|
+
if (docusaurusProjectRoot) {
|
|
1226
|
+
const projectRelative = relative3(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
|
|
1227
|
+
return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
|
|
1228
|
+
}
|
|
1229
|
+
return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
|
|
1230
|
+
})();
|
|
625
1231
|
const warnings = [];
|
|
626
1232
|
let docsConfig = { tabs: [] };
|
|
627
1233
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
628
1234
|
const referenceOrder = /* @__PURE__ */ new Map();
|
|
1235
|
+
let docusaurusSidebars = null;
|
|
629
1236
|
if (platform === "mintlify") {
|
|
630
1237
|
try {
|
|
631
1238
|
const config = readMintlifyConfig(repositoryDir);
|
|
@@ -646,16 +1253,27 @@ function migrateRepository(options) {
|
|
|
646
1253
|
});
|
|
647
1254
|
}
|
|
648
1255
|
}
|
|
649
|
-
|
|
1256
|
+
if (platform === "docusaurus") {
|
|
1257
|
+
try {
|
|
1258
|
+
docusaurusSidebars = options.docusaurusSkipSidebar || !docusaurusProjectRoot ? null : readDocusaurusSidebars(docusaurusProjectRoot);
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
warnings.push({
|
|
1261
|
+
code: "unsupported-config",
|
|
1262
|
+
message: `Docusaurus sidebar could not be read safely: ${error instanceof Error ? error.message : String(error)} Generated navigation will be used.`
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
650
1266
|
const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
|
|
651
|
-
if (!
|
|
1267
|
+
if (!existsSync3(contentRoot) || !lstatSync2(contentRoot).isDirectory()) {
|
|
652
1268
|
throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
|
|
653
1269
|
}
|
|
654
1270
|
const files = scanFiles(contentRoot);
|
|
655
1271
|
const pages = [];
|
|
656
1272
|
const assets = [];
|
|
1273
|
+
const docusaurusDescriptors = [];
|
|
657
1274
|
const seenPageIds = /* @__PURE__ */ new Set();
|
|
658
1275
|
let skipped = 0;
|
|
1276
|
+
let discovered = files.length;
|
|
659
1277
|
const localeConfig = docsConfig.i18n;
|
|
660
1278
|
const pageFiles = [...files].sort((left, right) => {
|
|
661
1279
|
const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -664,21 +1282,22 @@ function migrateRepository(options) {
|
|
|
664
1282
|
});
|
|
665
1283
|
for (const file of pageFiles) {
|
|
666
1284
|
if (!isDocumentationExtension(file.relativePath)) continue;
|
|
667
|
-
|
|
1285
|
+
const sourceSegments = file.relativePath.split("/");
|
|
1286
|
+
if (sourceSegments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase())) || platform === "docusaurus" && basename(file.relativePath).startsWith("_")) {
|
|
668
1287
|
skipped++;
|
|
669
1288
|
continue;
|
|
670
1289
|
}
|
|
671
1290
|
const rootFilename = !file.relativePath.includes("/") ? basename(file.relativePath).toLowerCase() : "";
|
|
672
|
-
if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
|
|
1291
|
+
if (!configuredDocsDir && REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
|
|
673
1292
|
skipped++;
|
|
674
1293
|
continue;
|
|
675
1294
|
}
|
|
676
|
-
if (![".md", ".mdx"].includes(
|
|
1295
|
+
if (![".md", ".mdx"].includes(extname3(file.relativePath).toLowerCase())) {
|
|
677
1296
|
skipped++;
|
|
678
1297
|
warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
|
|
679
1298
|
continue;
|
|
680
1299
|
}
|
|
681
|
-
const size =
|
|
1300
|
+
const size = lstatSync2(file.absolutePath).size;
|
|
682
1301
|
if (size > MAX_PAGE_BYTES) {
|
|
683
1302
|
skipped++;
|
|
684
1303
|
warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
|
|
@@ -702,30 +1321,57 @@ function migrateRepository(options) {
|
|
|
702
1321
|
}
|
|
703
1322
|
const isDefaultLocale = !locale || locale === localeConfig?.defaultLocale;
|
|
704
1323
|
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
1324
|
const raw = inlineMdxSnippets(
|
|
711
|
-
|
|
1325
|
+
readFileSync3(file.absolutePath, "utf8"),
|
|
712
1326
|
file.absolutePath,
|
|
713
1327
|
repositoryDir,
|
|
714
|
-
warnings
|
|
1328
|
+
warnings,
|
|
1329
|
+
0,
|
|
1330
|
+
docusaurusProjectRoot ?? repositoryDir
|
|
715
1331
|
);
|
|
1332
|
+
let docusaurusDescriptor;
|
|
716
1333
|
const page = parseMarkdownPage({
|
|
717
1334
|
id,
|
|
718
1335
|
navigationId,
|
|
719
1336
|
...locale ? { locale } : {},
|
|
720
1337
|
raw,
|
|
721
|
-
source: `${options.sourceUrl}#${file.relativePath}
|
|
1338
|
+
source: `${options.sourceUrl}#${file.relativePath}`,
|
|
1339
|
+
...platform === "docusaurus" ? {
|
|
1340
|
+
resolveIdentity: (frontmatter, fallback) => {
|
|
1341
|
+
const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
|
|
1342
|
+
const routePrefix = options.docusaurusRoutePrefix?.replace(/^\/+|\/+$/g, "");
|
|
1343
|
+
if (!routePrefix) {
|
|
1344
|
+
docusaurusDescriptor = resolved.descriptor;
|
|
1345
|
+
return resolved.identity;
|
|
1346
|
+
}
|
|
1347
|
+
const prefixedNavigationId = resolved.identity.navigationId === "introduction" ? routePrefix : `${routePrefix}/${resolved.identity.navigationId}`;
|
|
1348
|
+
const prefixedStorageId = resolved.identity.id === "introduction" ? routePrefix : `${routePrefix}/${resolved.identity.id}`;
|
|
1349
|
+
docusaurusDescriptor = {
|
|
1350
|
+
...resolved.descriptor,
|
|
1351
|
+
navigationId: prefixedNavigationId
|
|
1352
|
+
};
|
|
1353
|
+
return {
|
|
1354
|
+
...resolved.identity,
|
|
1355
|
+
id: prefixedStorageId,
|
|
1356
|
+
navigationId: prefixedNavigationId
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
} : {}
|
|
722
1360
|
});
|
|
723
1361
|
if (!page) {
|
|
724
1362
|
skipped++;
|
|
725
1363
|
continue;
|
|
726
1364
|
}
|
|
727
|
-
seenPageIds.
|
|
1365
|
+
if (seenPageIds.has(page.id)) {
|
|
1366
|
+
skipped++;
|
|
1367
|
+
warnings.push({ code: "collision", message: `Multiple source files map to ${page.id}; the first file was kept.`, source: file.relativePath });
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
seenPageIds.add(page.id);
|
|
728
1371
|
pages.push(page);
|
|
1372
|
+
if (docusaurusDescriptor) {
|
|
1373
|
+
docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
|
|
1374
|
+
}
|
|
729
1375
|
}
|
|
730
1376
|
const discoveredReferenceKeys = new Set(files.map((file) => normalizedReferenceKey(file.relativePath)));
|
|
731
1377
|
for (const [key] of referenceMap) {
|
|
@@ -733,32 +1379,98 @@ function migrateRepository(options) {
|
|
|
733
1379
|
warnings.push({ code: "missing-page", message: "A navigation entry did not resolve to a source page.", source: key });
|
|
734
1380
|
}
|
|
735
1381
|
}
|
|
1382
|
+
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
1383
|
+
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
1384
|
+
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
1385
|
+
if (!existsSync3(root) || !lstatSync2(root).isDirectory()) return [];
|
|
1386
|
+
return scanFiles(root).map((file) => ({
|
|
1387
|
+
...file,
|
|
1388
|
+
relativePath: `${directory}/${file.relativePath}`
|
|
1389
|
+
}));
|
|
1390
|
+
}) : [];
|
|
736
1391
|
let totalAssetBytes = 0;
|
|
737
|
-
for (const file of files) {
|
|
1392
|
+
for (const file of [...files, ...repositoryAssets]) {
|
|
738
1393
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
739
|
-
if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(
|
|
740
|
-
const size =
|
|
1394
|
+
if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
|
|
1395
|
+
const size = lstatSync2(file.absolutePath).size;
|
|
741
1396
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
742
1397
|
warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
|
|
743
1398
|
continue;
|
|
744
1399
|
}
|
|
745
|
-
const
|
|
1400
|
+
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
1401
|
+
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
746
1402
|
if (!assetPath) continue;
|
|
747
|
-
assets.push({ path: assetPath, content:
|
|
1403
|
+
assets.push({ path: assetPath, content: readFileSync3(file.absolutePath) });
|
|
748
1404
|
totalAssetBytes += size;
|
|
749
1405
|
}
|
|
750
1406
|
if (files.length >= MAX_SOURCE_FILES) {
|
|
751
1407
|
warnings.push({ code: "limit-reached", message: `Repository discovery stopped at ${MAX_SOURCE_FILES} files.` });
|
|
752
1408
|
}
|
|
1409
|
+
if (platform === "docusaurus") {
|
|
1410
|
+
const projected = projectDocusaurusNavigation({
|
|
1411
|
+
sidebars: docusaurusSidebars,
|
|
1412
|
+
descriptors: docusaurusDescriptors,
|
|
1413
|
+
contentRoot,
|
|
1414
|
+
sourceUrl: options.sourceUrl,
|
|
1415
|
+
routePrefix: options.docusaurusRoutePrefix
|
|
1416
|
+
});
|
|
1417
|
+
docsConfig = projected.docsConfig;
|
|
1418
|
+
warnings.push(...projected.warnings);
|
|
1419
|
+
for (const page of projected.generatedPages) {
|
|
1420
|
+
if (seenPageIds.has(page.id)) continue;
|
|
1421
|
+
seenPageIds.add(page.id);
|
|
1422
|
+
pages.push(page);
|
|
1423
|
+
}
|
|
1424
|
+
const descriptorByNavigationId = new Map(
|
|
1425
|
+
docusaurusDescriptors.map((descriptor) => [descriptor.navigationId, descriptor])
|
|
1426
|
+
);
|
|
1427
|
+
for (const page of pages) {
|
|
1428
|
+
const descriptor = descriptorByNavigationId.get(page.navigationId);
|
|
1429
|
+
if (descriptor) page.body = rewriteDocusaurusLinks(page.body, descriptor, docusaurusDescriptors);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
753
1432
|
if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
|
|
754
1433
|
const openApi = findOpenApi(files);
|
|
755
1434
|
if (openApi) {
|
|
756
1435
|
const filename = basename(openApi.relativePath);
|
|
757
1436
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
758
|
-
assets.push({ path: filename, content:
|
|
1437
|
+
assets.push({ path: filename, content: readFileSync3(openApi.absolutePath) });
|
|
759
1438
|
}
|
|
760
1439
|
docsConfig = injectOpenApi(docsConfig, filename);
|
|
761
1440
|
}
|
|
1441
|
+
if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
|
|
1442
|
+
for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
|
|
1443
|
+
if (plugin.docsDir === configuredDocsDir) continue;
|
|
1444
|
+
const pluginBundle = migrateRepository({
|
|
1445
|
+
...options,
|
|
1446
|
+
docsDir: plugin.docsDir,
|
|
1447
|
+
platform: "docusaurus",
|
|
1448
|
+
docusaurusRoutePrefix: plugin.routePrefix,
|
|
1449
|
+
docusaurusSkipPlugins: true,
|
|
1450
|
+
docusaurusSkipSidebar: true,
|
|
1451
|
+
docusaurusSkipAssets: true
|
|
1452
|
+
});
|
|
1453
|
+
for (const page of pluginBundle.pages) {
|
|
1454
|
+
if (seenPageIds.has(page.id)) continue;
|
|
1455
|
+
seenPageIds.add(page.id);
|
|
1456
|
+
pages.push(page);
|
|
1457
|
+
}
|
|
1458
|
+
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1459
|
+
for (const asset of pluginBundle.assets) {
|
|
1460
|
+
if (assetPaths.has(asset.path)) continue;
|
|
1461
|
+
assetPaths.add(asset.path);
|
|
1462
|
+
assets.push(asset);
|
|
1463
|
+
}
|
|
1464
|
+
const pluginLabel = plugin.routePrefix.split(/[-_/]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1465
|
+
docsConfig.tabs.push(...pluginBundle.docsConfig.tabs.map((tab, index) => ({
|
|
1466
|
+
...tab,
|
|
1467
|
+
tab: index === 0 ? pluginLabel : `${pluginLabel}: ${tab.tab}`
|
|
1468
|
+
})));
|
|
1469
|
+
warnings.push(...pluginBundle.warnings);
|
|
1470
|
+
discovered += pluginBundle.stats.discovered;
|
|
1471
|
+
skipped += pluginBundle.stats.skipped;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
762
1474
|
if (pages.length === 0) throw new Error("No importable Markdown or MDX pages were found in the repository.");
|
|
763
1475
|
return {
|
|
764
1476
|
sourceUrl: options.sourceUrl,
|
|
@@ -768,7 +1480,7 @@ function migrateRepository(options) {
|
|
|
768
1480
|
assets,
|
|
769
1481
|
docsConfig,
|
|
770
1482
|
warnings,
|
|
771
|
-
stats: { discovered
|
|
1483
|
+
stats: { discovered, imported: pages.length, skipped }
|
|
772
1484
|
};
|
|
773
1485
|
}
|
|
774
1486
|
|
|
@@ -861,7 +1573,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
861
1573
|
// src/url.ts
|
|
862
1574
|
import { load } from "cheerio";
|
|
863
1575
|
import TurndownService from "turndown";
|
|
864
|
-
import { parse as
|
|
1576
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
865
1577
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
866
1578
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
867
1579
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -882,13 +1594,22 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
882
1594
|
"Danger",
|
|
883
1595
|
"Error",
|
|
884
1596
|
"Expandable",
|
|
1597
|
+
"Agent",
|
|
1598
|
+
"Embed",
|
|
885
1599
|
"File",
|
|
886
1600
|
"Folder",
|
|
887
1601
|
"Frame",
|
|
1602
|
+
"GitHub",
|
|
1603
|
+
"Github",
|
|
888
1604
|
"Hero",
|
|
1605
|
+
"Human",
|
|
889
1606
|
"Icon",
|
|
890
1607
|
"Info",
|
|
1608
|
+
"InlinePanel",
|
|
1609
|
+
"InlineRequestExample",
|
|
1610
|
+
"InlineResponseExample",
|
|
891
1611
|
"Latex",
|
|
1612
|
+
"LegacyView",
|
|
892
1613
|
"Mermaid",
|
|
893
1614
|
"Note",
|
|
894
1615
|
"Panel",
|
|
@@ -903,6 +1624,9 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
903
1624
|
"Steps",
|
|
904
1625
|
"Tab",
|
|
905
1626
|
"Tabs",
|
|
1627
|
+
"Terminal",
|
|
1628
|
+
"TerminalInput",
|
|
1629
|
+
"TerminalOutput",
|
|
906
1630
|
"Tile",
|
|
907
1631
|
"TileGroup",
|
|
908
1632
|
"Tip",
|
|
@@ -910,6 +1634,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
910
1634
|
"Tree",
|
|
911
1635
|
"Update",
|
|
912
1636
|
"View",
|
|
1637
|
+
"Visibility",
|
|
913
1638
|
"Warning"
|
|
914
1639
|
]);
|
|
915
1640
|
var LOCALE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1336,7 +2061,7 @@ function extractEmbeddedOpenApi(content) {
|
|
|
1336
2061
|
const match = fencePattern.exec(content);
|
|
1337
2062
|
if (!match) return { content };
|
|
1338
2063
|
try {
|
|
1339
|
-
const parsed =
|
|
2064
|
+
const parsed = parseYaml2(match[5], { maxAliasCount: 50 });
|
|
1340
2065
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
1341
2066
|
const document = parsed;
|
|
1342
2067
|
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|
|
@@ -1452,6 +2177,7 @@ function htmlPage(document, id) {
|
|
|
1452
2177
|
const content = $('main article, article, main, [role="main"]').first();
|
|
1453
2178
|
const root = content.length ? content : $("body");
|
|
1454
2179
|
root.find("script,style,noscript,nav,header,footer,aside,form,button,svg,iframe,object,embed,link,meta").remove();
|
|
2180
|
+
root.find(".theme-doc-version-badge,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
|
|
1455
2181
|
root.find("a[href]").each((_index, element) => {
|
|
1456
2182
|
const href = $(element).attr("href");
|
|
1457
2183
|
if (!href) return;
|
|
@@ -1482,12 +2208,78 @@ function htmlPage(document, id) {
|
|
|
1482
2208
|
});
|
|
1483
2209
|
const turndown = new TurndownService({ codeBlockStyle: "fenced", emDelimiter: "_", headingStyle: "atx" });
|
|
1484
2210
|
turndown.remove(["script", "style", "noscript"]);
|
|
2211
|
+
turndown.addRule("docusaurus-admonition", {
|
|
2212
|
+
filter: (node) => node.nodeName === "DIV" && node.classList.contains("theme-admonition"),
|
|
2213
|
+
replacement: (_content, node) => {
|
|
2214
|
+
const element = node;
|
|
2215
|
+
const className = element.className;
|
|
2216
|
+
const tag = /admonition-(?:danger)/.test(className) ? "Error" : /admonition-(?:caution|warning)/.test(className) ? "Warning" : /admonition-info/.test(className) ? "Info" : "Note";
|
|
2217
|
+
const fragment = load(element.outerHTML);
|
|
2218
|
+
const clone = fragment(".theme-admonition").first();
|
|
2219
|
+
const title2 = clone.find('[class*="admonitionHeading"],.admonition-heading').first();
|
|
2220
|
+
const titleText = title2.text().trim();
|
|
2221
|
+
title2.remove();
|
|
2222
|
+
const markdown = turndown.turndown(clone.html() ?? "").trim();
|
|
2223
|
+
return `
|
|
2224
|
+
|
|
2225
|
+
<${tag}>
|
|
2226
|
+
${titleText ? `**${titleText}**
|
|
2227
|
+
|
|
2228
|
+
` : ""}${markdown}
|
|
2229
|
+
</${tag}>
|
|
2230
|
+
|
|
2231
|
+
`;
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
turndown.addRule("docusaurus-tabs", {
|
|
2235
|
+
filter: (node) => node.nodeName === "DIV" && node.classList.contains("tabs-container"),
|
|
2236
|
+
replacement: (_content, node) => {
|
|
2237
|
+
const element = node;
|
|
2238
|
+
const fragment = load(element.outerHTML);
|
|
2239
|
+
const container = fragment(".tabs-container").first();
|
|
2240
|
+
const labels = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
|
|
2241
|
+
const panels = container.children('div[role="tabpanel"]').toArray();
|
|
2242
|
+
if (panels.length === 0) return _content;
|
|
2243
|
+
const tabs = panels.map((panel, index) => {
|
|
2244
|
+
const title2 = (labels[index] ?? `Tab ${index + 1}`).replace(/"/g, """);
|
|
2245
|
+
return `<Tab title="${title2}">
|
|
2246
|
+
${turndown.turndown(fragment(panel).html() ?? "").trim()}
|
|
2247
|
+
</Tab>`;
|
|
2248
|
+
});
|
|
2249
|
+
return `
|
|
2250
|
+
|
|
2251
|
+
<Tabs>
|
|
2252
|
+
${tabs.join("\n\n")}
|
|
2253
|
+
</Tabs>
|
|
2254
|
+
|
|
2255
|
+
`;
|
|
2256
|
+
}
|
|
2257
|
+
});
|
|
2258
|
+
turndown.addRule("gfm-table", {
|
|
2259
|
+
filter: "table",
|
|
2260
|
+
replacement: (_content, node) => {
|
|
2261
|
+
const fragment = load(node.outerHTML);
|
|
2262
|
+
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);
|
|
2263
|
+
if (rows.length === 0) return "";
|
|
2264
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
2265
|
+
const normalized = rows.map((row) => [...row, ...Array(width - row.length).fill("")]);
|
|
2266
|
+
return `
|
|
2267
|
+
|
|
2268
|
+
| ${normalized[0].join(" | ")} |
|
|
2269
|
+
| ${Array(width).fill("---").join(" | ")} |
|
|
2270
|
+
${normalized.slice(1).map((row) => `| ${row.join(" | ")} |`).join("\n")}
|
|
2271
|
+
|
|
2272
|
+
`;
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
1485
2275
|
turndown.addRule("migration-safe-fenced-code", {
|
|
1486
2276
|
filter: (node) => node.nodeName === "PRE",
|
|
1487
2277
|
replacement: (_content, node) => {
|
|
1488
2278
|
const element = node;
|
|
1489
|
-
const
|
|
1490
|
-
const
|
|
2279
|
+
const fragment = load(element.outerHTML);
|
|
2280
|
+
const tokenLines = fragment(".token-line").toArray();
|
|
2281
|
+
const code = (tokenLines.length > 0 ? tokenLines.map((line) => fragment(line).text()).join("\n") : fragment("pre").text()).replace(/\n$/, "");
|
|
2282
|
+
const language = fragment("code").attr("class")?.match(/(?:language-|lang-)([A-Za-z0-9_+-]+)/)?.[1] ?? "";
|
|
1491
2283
|
const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
|
|
1492
2284
|
const fence = "`".repeat(Math.max(3, longestFence + 1));
|
|
1493
2285
|
return `
|
|
@@ -1502,7 +2294,7 @@ ${fence}
|
|
|
1502
2294
|
const escape = turndown.escape.bind(turndown);
|
|
1503
2295
|
turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
|
|
1504
2296
|
const body = turndown.turndown(root.html() ?? "").trim();
|
|
1505
|
-
if (!title || body.length < 40) return { page: null, links };
|
|
2297
|
+
if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
|
|
1506
2298
|
return {
|
|
1507
2299
|
page: {
|
|
1508
2300
|
id,
|
|
@@ -1520,6 +2312,176 @@ function htmlNavigationLinks(document) {
|
|
|
1520
2312
|
const $ = load(document.body);
|
|
1521
2313
|
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
2314
|
}
|
|
2315
|
+
function normalizedLinkLabel(value) {
|
|
2316
|
+
return value.replace(/\s+/g, " ").trim();
|
|
2317
|
+
}
|
|
2318
|
+
function readDocusaurusSidebar(document) {
|
|
2319
|
+
if (!/html/i.test(document.contentType)) return null;
|
|
2320
|
+
const $ = load(document.body);
|
|
2321
|
+
const sidebar = $("ul.theme-doc-sidebar-menu").first();
|
|
2322
|
+
if (sidebar.length === 0) return null;
|
|
2323
|
+
function readList(list) {
|
|
2324
|
+
return $(list).children("li").toArray().flatMap((item) => {
|
|
2325
|
+
const directLink = $(item).children("a.menu__link").first();
|
|
2326
|
+
const collapsibleLink = $(item).children(".menu__list-item-collapsible").children("a.menu__link").first();
|
|
2327
|
+
const link = directLink.length > 0 ? directLink : collapsibleLink;
|
|
2328
|
+
const label = normalizedLinkLabel(link.text());
|
|
2329
|
+
const href = link.attr("href");
|
|
2330
|
+
const nested = $(item).children("ul.menu__list").first();
|
|
2331
|
+
const children = nested.length > 0 ? readList(nested.get(0)) : [];
|
|
2332
|
+
const isCategory = $(item).hasClass("theme-doc-sidebar-item-category") || link.hasClass("menu__link--sublist");
|
|
2333
|
+
if (!label && children.length === 0) return [];
|
|
2334
|
+
return [{
|
|
2335
|
+
label: label || "Documentation",
|
|
2336
|
+
...href ? { href: new URL(href, document.finalUrl).toString() } : {},
|
|
2337
|
+
...children.length > 0 ? { items: children } : {},
|
|
2338
|
+
...isCategory ? { isCategory: true } : {}
|
|
2339
|
+
}];
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
return { items: readList(sidebar.get(0)) };
|
|
2343
|
+
}
|
|
2344
|
+
function docusaurusSidebarHrefs(snapshot) {
|
|
2345
|
+
const hrefs = [];
|
|
2346
|
+
const visit = (items) => {
|
|
2347
|
+
for (const item of items) {
|
|
2348
|
+
if (item.href) hrefs.push(item.href);
|
|
2349
|
+
if (item.items) visit(item.items);
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
if (snapshot) visit(snapshot.items);
|
|
2353
|
+
return hrefs;
|
|
2354
|
+
}
|
|
2355
|
+
function deriveDocusaurusScope(source, document, submittedScopePath) {
|
|
2356
|
+
const $ = load(document.body);
|
|
2357
|
+
const sidebar = readDocusaurusSidebar(document);
|
|
2358
|
+
const internalPaths = docusaurusSidebarHrefs(sidebar).flatMap((href) => {
|
|
2359
|
+
try {
|
|
2360
|
+
const url = new URL(href);
|
|
2361
|
+
return url.origin === source.origin ? [url.pathname.replace(/\/+$/, "") || "/"] : [];
|
|
2362
|
+
} catch {
|
|
2363
|
+
return [];
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2367
|
+
for (const path of internalPaths) {
|
|
2368
|
+
const segments = path.split("/").filter(Boolean);
|
|
2369
|
+
for (let length = 1; length < segments.length; length++) {
|
|
2370
|
+
const prefix = `/${segments.slice(0, length).join("/")}`;
|
|
2371
|
+
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
const threshold = Math.max(2, Math.ceil(internalPaths.length * 0.6));
|
|
2375
|
+
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;
|
|
2376
|
+
const excludedVersionPrefixes = $(".dropdown__menu a[href]").toArray().flatMap((element) => {
|
|
2377
|
+
const href = $(element).attr("href");
|
|
2378
|
+
if (!href) return [];
|
|
2379
|
+
try {
|
|
2380
|
+
const target = new URL(href, document.finalUrl);
|
|
2381
|
+
if (target.origin !== source.origin || !isInScope(target, source, scopePath)) return [];
|
|
2382
|
+
const relativePath = target.pathname.slice(scopePath.length).replace(/^\/+/, "");
|
|
2383
|
+
const version = relativePath.split("/", 1)[0];
|
|
2384
|
+
if (!/^(?:legacy|next|v\d[\w.-]*)$/i.test(version)) return [];
|
|
2385
|
+
return [`${scopePath}/${version}`];
|
|
2386
|
+
} catch {
|
|
2387
|
+
return [];
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
return {
|
|
2391
|
+
scopePath,
|
|
2392
|
+
excludedVersionPrefixes: [...new Set(excludedVersionPrefixes)],
|
|
2393
|
+
sidebar
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
function docusaurusRouteIdForUrl(url, scopePath) {
|
|
2397
|
+
let path = url.pathname.replace(/\/+$/, "");
|
|
2398
|
+
if (scopePath === "/") path = path.replace(/^\/+/, "");
|
|
2399
|
+
else if (path === scopePath) path = "";
|
|
2400
|
+
else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
|
|
2401
|
+
else return null;
|
|
2402
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
2403
|
+
let decoded = segment;
|
|
2404
|
+
try {
|
|
2405
|
+
decoded = decodeURIComponent(segment);
|
|
2406
|
+
} catch {
|
|
2407
|
+
}
|
|
2408
|
+
return decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
|
|
2409
|
+
}).filter(Boolean);
|
|
2410
|
+
return segments.join("/") || "introduction";
|
|
2411
|
+
}
|
|
2412
|
+
function storageIdForDocusaurusRoute(routeId) {
|
|
2413
|
+
return /(?:^|\/)(?:index|readme)$/i.test(routeId) ? `${routeId}/index` : routeId;
|
|
2414
|
+
}
|
|
2415
|
+
function docusaurusNavigationFromSnapshots(snapshots, pages, scopePath) {
|
|
2416
|
+
const imported = new Set(pages.map((page) => page.navigationId));
|
|
2417
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
2418
|
+
const uniqueSnapshots = [...new Map(snapshots.map((snapshot) => [
|
|
2419
|
+
JSON.stringify(snapshot.items),
|
|
2420
|
+
snapshot
|
|
2421
|
+
])).values()];
|
|
2422
|
+
const mergedSnapshots = [];
|
|
2423
|
+
function mergeItems(target, incoming) {
|
|
2424
|
+
for (const item of incoming) {
|
|
2425
|
+
const existing = target.find((candidate) => candidate.label === item.label && (candidate.items || item.items)) ?? target.find((candidate) => candidate.label === item.label && candidate.href === item.href);
|
|
2426
|
+
if (!existing) {
|
|
2427
|
+
target.push(structuredClone(item));
|
|
2428
|
+
continue;
|
|
2429
|
+
}
|
|
2430
|
+
if (!existing.href && item.href) existing.href = item.href;
|
|
2431
|
+
if (item.items) {
|
|
2432
|
+
existing.items ??= [];
|
|
2433
|
+
mergeItems(existing.items, item.items);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
for (const snapshot of uniqueSnapshots) {
|
|
2438
|
+
const incomingLabels = new Set(snapshot.items.map((item) => item.label));
|
|
2439
|
+
const cluster = mergedSnapshots.find((candidate) => {
|
|
2440
|
+
const candidateLabels = new Set(candidate.items.map((item) => item.label));
|
|
2441
|
+
const overlap = [...incomingLabels].filter((label) => candidateLabels.has(label)).length;
|
|
2442
|
+
return overlap >= Math.max(2, Math.ceil(Math.min(incomingLabels.size, candidateLabels.size) * 0.5));
|
|
2443
|
+
});
|
|
2444
|
+
if (cluster) mergeItems(cluster.items, snapshot.items);
|
|
2445
|
+
else mergedSnapshots.push(structuredClone(snapshot));
|
|
2446
|
+
}
|
|
2447
|
+
const pageIdsFor = (snapshot) => docusaurusSidebarHrefs(snapshot).flatMap((href) => {
|
|
2448
|
+
const id = docusaurusRouteIdForUrl(new URL(href), scopePath);
|
|
2449
|
+
return id && imported.has(id) ? [id] : [];
|
|
2450
|
+
});
|
|
2451
|
+
mergedSnapshots.sort((left, right) => pageIdsFor(right).length - pageIdsFor(left).length);
|
|
2452
|
+
const tabs = mergedSnapshots.flatMap((snapshot, snapshotIndex) => {
|
|
2453
|
+
function convert(items) {
|
|
2454
|
+
const converted2 = [];
|
|
2455
|
+
for (const item of items) {
|
|
2456
|
+
const routeId = item.href ? docusaurusRouteIdForUrl(new URL(item.href), scopePath) : null;
|
|
2457
|
+
const page = routeId && imported.has(routeId) && !claimed.has(routeId) ? routeId : null;
|
|
2458
|
+
if (page) claimed.add(page);
|
|
2459
|
+
const children = item.items ? convert(item.items) : [];
|
|
2460
|
+
if (children.length > 0 || item.isCategory) {
|
|
2461
|
+
converted2.push({ group: item.label, pages: page ? [page, ...children] : children });
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
if (page) converted2.push(page);
|
|
2465
|
+
}
|
|
2466
|
+
return converted2;
|
|
2467
|
+
}
|
|
2468
|
+
const converted = convert(snapshot.items);
|
|
2469
|
+
const loosePages = converted.filter((item) => typeof item === "string");
|
|
2470
|
+
const groups = converted.filter((item) => typeof item !== "string");
|
|
2471
|
+
if (loosePages.length > 0) groups.unshift({ group: "Overview", pages: loosePages });
|
|
2472
|
+
if (groups.length === 0) return [];
|
|
2473
|
+
const snapshotPageIds = pageIdsFor(snapshot);
|
|
2474
|
+
const firstSegments = new Set(snapshotPageIds.map((id) => id.split("/", 1)[0]));
|
|
2475
|
+
const tab = snapshotIndex > 0 && firstSegments.size === 1 ? normalizedLinkLabel([...firstSegments][0].replace(/[-_]/g, " ")).replace(/\b\w/g, (letter) => letter.toUpperCase()) : "Documentation";
|
|
2476
|
+
return [{ tab, groups }];
|
|
2477
|
+
});
|
|
2478
|
+
const additional = pages.map((page) => page.navigationId).filter((id) => !claimed.has(id));
|
|
2479
|
+
if (additional.length > 0) {
|
|
2480
|
+
if (tabs.length === 0) return buildNavigationFromPages(pages);
|
|
2481
|
+
tabs[0].groups?.push({ group: "Additional", pages: additional });
|
|
2482
|
+
}
|
|
2483
|
+
return { tabs };
|
|
2484
|
+
}
|
|
1523
2485
|
function htmlTopLevelNavigation(document, source, scopePath) {
|
|
1524
2486
|
const $ = load(document.body);
|
|
1525
2487
|
const collectCandidates = (selector) => {
|
|
@@ -1598,24 +2560,54 @@ async function migrateUrl(options) {
|
|
|
1598
2560
|
}
|
|
1599
2561
|
const htmlProbe = /html/i.test(initial.contentType) ? initial : await safeFetch(fetcher, source, "text/html,application/xhtml+xml");
|
|
1600
2562
|
const scopedHtmlProbe = htmlProbe && isInScope(htmlProbe.finalUrl, source, submittedScopePath) ? htmlProbe : null;
|
|
1601
|
-
const platform = detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
1602
|
-
|
|
2563
|
+
const platform = options.platform ?? detectUrlPlatform(scopedHtmlProbe ?? initial);
|
|
2564
|
+
let docusaurusHtmlProbe = platform === "docusaurus" ? scopedHtmlProbe : null;
|
|
2565
|
+
if (docusaurusHtmlProbe && !readDocusaurusSidebar(docusaurusHtmlProbe)) {
|
|
2566
|
+
const $ = load(docusaurusHtmlProbe.body);
|
|
2567
|
+
const docsHref = $("nav a[href]").toArray().map((element) => ({
|
|
2568
|
+
href: $(element).attr("href"),
|
|
2569
|
+
label: normalizedLinkLabel($(element).text())
|
|
2570
|
+
})).find((link) => /^docs(?:umentation)?$/i.test(link.label))?.href;
|
|
2571
|
+
if (docsHref) {
|
|
2572
|
+
try {
|
|
2573
|
+
const docsUrl = new URL(docsHref, docusaurusHtmlProbe.finalUrl);
|
|
2574
|
+
if (docsUrl.origin === source.origin && isInScope(docsUrl, source, submittedScopePath)) {
|
|
2575
|
+
const probe = await safeFetch(fetcher, docsUrl, "text/html,application/xhtml+xml");
|
|
2576
|
+
if (probe && probe.finalUrl.origin === source.origin && /html/i.test(probe.contentType)) {
|
|
2577
|
+
docusaurusHtmlProbe = probe;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
} catch {
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
|
|
2585
|
+
const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : docusaurusDiscovery?.scopePath ?? submittedScopePath;
|
|
1603
2586
|
const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
|
|
1604
2587
|
const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
|
|
1605
2588
|
const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
|
|
2589
|
+
if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
|
|
1606
2590
|
const queue = [source];
|
|
1607
2591
|
const queued = /* @__PURE__ */ new Set([candidateIdentity(source)]);
|
|
1608
2592
|
const warnings = [];
|
|
1609
2593
|
const fetchedSitemaps = /* @__PURE__ */ new Set();
|
|
2594
|
+
const docusaurusSidebars = docusaurusDiscovery?.sidebar ? [docusaurusDiscovery.sidebar] : [];
|
|
2595
|
+
const docusaurusRedirects = [];
|
|
1610
2596
|
function enqueue(value, base) {
|
|
1611
2597
|
if (queued.size >= MAX_DISCOVERED_URLS) return;
|
|
1612
2598
|
const url = normalizeCandidate(value, base, source, scopePath);
|
|
1613
2599
|
if (!url || queued.has(candidateIdentity(url))) return;
|
|
2600
|
+
if (platform === "docusaurus" && url.pathname.replace(/\/+$/, "") === `${scopePath}/search`) return;
|
|
2601
|
+
if (docusaurusDiscovery?.excludedVersionPrefixes.some((prefix) => {
|
|
2602
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
2603
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
2604
|
+
})) return;
|
|
1614
2605
|
queued.add(candidateIdentity(url));
|
|
1615
2606
|
queue.push(url);
|
|
1616
2607
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
2608
|
+
const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
|
|
2609
|
+
if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
|
|
2610
|
+
for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
|
|
1619
2611
|
}
|
|
1620
2612
|
async function discoverDocumentLinks(document, depth = 0) {
|
|
1621
2613
|
const links = sitemapLinks(document.body);
|
|
@@ -1734,11 +2726,13 @@ async function migrateUrl(options) {
|
|
|
1734
2726
|
return { candidate, page: null, links: [], failure: false, budgetExceeded: true };
|
|
1735
2727
|
}
|
|
1736
2728
|
importedBytes += responseBytes;
|
|
1737
|
-
const
|
|
1738
|
-
const
|
|
1739
|
-
if (!
|
|
2729
|
+
const discoveredNavigationId = platform === "docusaurus" ? docusaurusRouteIdForUrl(document.finalUrl, scopePath) ?? docusaurusRouteIdForUrl(candidate, scopePath) : pageIdForUrl(document.finalUrl, scopePath) ?? pageIdForUrl(candidate, scopePath);
|
|
2730
|
+
const navigationId = discoveredNavigationId && canonicalLocalizedPageId(discoveredNavigationId) === sourceHomePageId ? "introduction" : discoveredNavigationId;
|
|
2731
|
+
if (!navigationId) return { candidate, page: null, links: [], failure: false };
|
|
2732
|
+
const id = platform === "docusaurus" ? storageIdForDocusaurusRoute(navigationId) : navigationId;
|
|
1740
2733
|
if (/(?:markdown|text\/plain)/i.test(document.contentType) || /\.mdx?$/i.test(document.finalUrl.pathname)) {
|
|
1741
2734
|
const markdown = markdownPage(document, id);
|
|
2735
|
+
if (markdown.page) markdown.page.navigationId = navigationId;
|
|
1742
2736
|
return {
|
|
1743
2737
|
candidate,
|
|
1744
2738
|
page: markdown.page,
|
|
@@ -1749,7 +2743,9 @@ async function migrateUrl(options) {
|
|
|
1749
2743
|
};
|
|
1750
2744
|
}
|
|
1751
2745
|
const extracted = htmlPage(document, id);
|
|
1752
|
-
|
|
2746
|
+
if (extracted.page) extracted.page.navigationId = navigationId;
|
|
2747
|
+
const sidebar = platform === "docusaurus" ? readDocusaurusSidebar(document) : null;
|
|
2748
|
+
return { candidate, page: extracted.page, links: extracted.links, base: document.finalUrl, sidebar, failure: false };
|
|
1753
2749
|
}));
|
|
1754
2750
|
for (const result of results) {
|
|
1755
2751
|
if (result.failure) {
|
|
@@ -1757,7 +2753,10 @@ async function migrateUrl(options) {
|
|
|
1757
2753
|
continue;
|
|
1758
2754
|
}
|
|
1759
2755
|
if (result.budgetExceeded) continue;
|
|
1760
|
-
if (result.page
|
|
2756
|
+
if (result.page && platform !== "docusaurus") {
|
|
2757
|
+
result.page.id = canonicalLocalizedPageId(result.page.id);
|
|
2758
|
+
result.page.navigationId = canonicalLocalizedPageId(result.page.navigationId);
|
|
2759
|
+
}
|
|
1761
2760
|
const sourceIdentity = result.page ? candidateIdentity(new URL(result.page.source)) : null;
|
|
1762
2761
|
if (result.page && sourceIdentity && !seenSources.has(sourceIdentity) && !seenIds.has(result.page.id) && pages.length < maxPages) {
|
|
1763
2762
|
pages.push(result.page);
|
|
@@ -1767,6 +2766,16 @@ async function migrateUrl(options) {
|
|
|
1767
2766
|
seenIds.add(result.page.id);
|
|
1768
2767
|
seenSources.add(sourceIdentity);
|
|
1769
2768
|
}
|
|
2769
|
+
if (platform === "docusaurus" && result.page) {
|
|
2770
|
+
const candidateRoute = docusaurusRouteIdForUrl(result.candidate, scopePath);
|
|
2771
|
+
if (candidateRoute && candidateRoute !== result.page.navigationId) {
|
|
2772
|
+
docusaurusRedirects.push({
|
|
2773
|
+
source: candidateRoute === "introduction" ? "/" : `/${candidateRoute}`,
|
|
2774
|
+
destination: result.page.navigationId === "introduction" ? "/" : `/${result.page.navigationId}`
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if ("sidebar" in result && result.sidebar) docusaurusSidebars.push(result.sidebar);
|
|
1770
2779
|
for (const link of result.links) enqueue(link, result.base ?? result.candidate);
|
|
1771
2780
|
}
|
|
1772
2781
|
}
|
|
@@ -1779,7 +2788,7 @@ async function migrateUrl(options) {
|
|
|
1779
2788
|
page.navigationId = navigationSegments.join("/") || "introduction";
|
|
1780
2789
|
}
|
|
1781
2790
|
}
|
|
1782
|
-
const importedIds = new Set(pages.map((page) => page.
|
|
2791
|
+
const importedIds = new Set(pages.map((page) => page.navigationId));
|
|
1783
2792
|
for (const page of pages) {
|
|
1784
2793
|
page.body = rewriteInternalLinks(
|
|
1785
2794
|
page.body,
|
|
@@ -1799,10 +2808,16 @@ async function migrateUrl(options) {
|
|
|
1799
2808
|
...entry,
|
|
1800
2809
|
pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
|
|
1801
2810
|
}));
|
|
1802
|
-
const docsConfig = buildNavigationFromPages(pages, {
|
|
2811
|
+
const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
|
|
1803
2812
|
topLevelTabs: platform === "mintlify",
|
|
1804
2813
|
topLevelNavigation
|
|
1805
2814
|
});
|
|
2815
|
+
if (docusaurusRedirects.length > 0) {
|
|
2816
|
+
docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
|
|
2817
|
+
`${redirect.source}:${redirect.destination}`,
|
|
2818
|
+
redirect
|
|
2819
|
+
])).values()];
|
|
2820
|
+
}
|
|
1806
2821
|
const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
|
|
1807
2822
|
if (openApiAsset) {
|
|
1808
2823
|
const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
|