@thallylabs/migrate 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 existsSync2,
5
- lstatSync,
6
- readFileSync as readFileSync2,
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 extname2, relative as relative3 } from "path";
9
+ import { basename, dirname as dirname2, extname as extname3, relative as relative3, resolve as resolvePath } from "path";
10
10
 
11
- // src/mdx.ts
12
- import matter from "gray-matter";
13
- function titleFromId(id) {
14
- const value = id.split("/").at(-1) ?? id;
15
- if (value === "introduction") return "Introduction";
16
- return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
17
- }
18
- function firstParagraph(content) {
19
- for (const block of content.split(/\n\s*\n/)) {
20
- const value = block.replace(/\s+/g, " ").trim();
21
- if (!value || /^(?:#|```|:::|<|import\s|export\s)/.test(value)) continue;
22
- return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").slice(0, 240);
23
- }
24
- return "";
25
- }
26
- function normalizeMdx(body) {
27
- return body.replace(/<!--([\s\S]*?)-->/g, (_match, content) => `{/*${content}*/}`).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
28
- }
29
- function parseMarkdownPage(input) {
30
- const parsed = matter(input.raw);
31
- const body = normalizeMdx(parsed.content).trim();
32
- if (parsed.data.openapi && !body) return null;
33
- const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
34
- const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(input.navigationId ?? input.id);
35
- const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
36
- return {
37
- id: input.id,
38
- navigationId: input.navigationId ?? input.id,
39
- locale: input.locale,
40
- title,
41
- description,
42
- keywords,
43
- body,
44
- source: input.source
45
- };
46
- }
47
-
48
- // src/navigation.ts
49
- import { existsSync, readFileSync } from "fs";
50
- import { dirname, extname, relative as relative2 } from "path";
11
+ // src/docusaurus.ts
12
+ import { existsSync, lstatSync, readFileSync } from "fs";
13
+ import { extname, posix } from "path";
14
+ import JSON5 from "json5";
15
+ import { parse as parseYaml } from "yaml";
51
16
 
52
17
  // src/path.ts
53
18
  import { isAbsolute, relative, resolve, sep } from "path";
@@ -92,7 +57,553 @@ function normalizeAssetPath(value) {
92
57
  return segments.join("/");
93
58
  }
94
59
 
60
+ // src/docusaurus.ts
61
+ var MAX_CONFIG_BYTES = 1e6;
62
+ var CATEGORY_FILENAMES = ["_category_.json", "_category_.yml", "_category_.yaml"];
63
+ var SIDEBAR_FILENAMES = [
64
+ "sidebars.json",
65
+ "sidebars.js",
66
+ "sidebars.cjs",
67
+ "sidebars.mjs",
68
+ "sidebars.ts"
69
+ ];
70
+ function objectValue(value) {
71
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
72
+ }
73
+ function stripNumberPrefix(value) {
74
+ return value.replace(/^\d+[-_]+/, "");
75
+ }
76
+ function normalizeDocId(value) {
77
+ return value.split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").split("/").filter(Boolean).map(stripNumberPrefix).join("/");
78
+ }
79
+ function docusaurusSlugifySegment(value) {
80
+ let decoded = value;
81
+ try {
82
+ decoded = decodeURIComponent(value);
83
+ } catch {
84
+ }
85
+ return decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
86
+ }
87
+ function docusaurusRouteId(value) {
88
+ const segments = normalizeDocId(value).split("/").map(docusaurusSlugifySegment).filter(Boolean);
89
+ return segments.join("/") || "introduction";
90
+ }
91
+ function defaultDocusaurusRouteId(value) {
92
+ const segments = normalizeDocId(value).split("/").filter(Boolean);
93
+ if (/^(?:index|readme)$/i.test(segments.at(-1) ?? "")) segments.pop();
94
+ const normalized = segments.map(docusaurusSlugifySegment).filter(Boolean);
95
+ return normalized.join("/") || "introduction";
96
+ }
97
+ function storageIdForRoute(routeId) {
98
+ return /(?:^|\/)(?:index|readme)$/i.test(routeId) ? `${routeId}/index` : routeId;
99
+ }
100
+ function titleCase(value) {
101
+ return stripNumberPrefix(value).replace(/sidebar$/i, "").split(/[-_]/).filter(Boolean).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
102
+ }
103
+ function routeFromSlug(slug, fallback, sourceDirectory) {
104
+ if (slug === "/") return "introduction";
105
+ if (slug.startsWith("/")) return docusaurusRouteId(slug);
106
+ const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" : posix.dirname(fallback));
107
+ const resolved = posix.normalize(posix.join(fallbackDirectory, slug));
108
+ if (resolved === ".." || resolved.startsWith("../")) return null;
109
+ return docusaurusRouteId(resolved);
110
+ }
111
+ function resolveDocusaurusPageIdentity(sourcePath, frontmatter, fallback) {
112
+ const sourceDocId = normalizeDocId(sourcePath);
113
+ const sourceDirectory = posix.dirname(sourceDocId);
114
+ const configuredId = typeof frontmatter.id === "string" && frontmatter.id.trim() ? stripNumberPrefix(frontmatter.id.trim()) : null;
115
+ const docId = configuredId ? normalizeDocId(sourceDirectory === "." ? configuredId : posix.join(sourceDirectory, configuredId)) : sourceDocId;
116
+ const defaultNavigationId = defaultDocusaurusRouteId(docId) ?? fallback.navigationId;
117
+ const configuredSlug = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
118
+ const navigationId = configuredSlug ? routeFromSlug(
119
+ configuredSlug,
120
+ defaultNavigationId,
121
+ sourceDirectory === "." ? "" : sourceDirectory
122
+ ) ?? defaultNavigationId : defaultNavigationId;
123
+ const position = typeof frontmatter.sidebar_position === "number" && Number.isFinite(frontmatter.sidebar_position) ? frontmatter.sidebar_position : void 0;
124
+ return {
125
+ identity: { id: storageIdForRoute(navigationId), navigationId },
126
+ descriptor: {
127
+ sourcePath: sourcePath.replace(/\\/g, "/"),
128
+ docId,
129
+ navigationId,
130
+ ...position === void 0 ? {} : { sidebarPosition: position }
131
+ }
132
+ };
133
+ }
134
+ function sourceReferenceKey(value) {
135
+ return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.(?:mdx?)$/i, "").replace(/\/$/, "");
136
+ }
137
+ function rewriteDocusaurusLinks(body, current, descriptors) {
138
+ const routes = /* @__PURE__ */ new Map();
139
+ for (const descriptor of descriptors) {
140
+ const sourceKey = sourceReferenceKey(descriptor.sourcePath);
141
+ routes.set(sourceKey, descriptor.navigationId);
142
+ routes.set(sourceReferenceKey(descriptor.docId), descriptor.navigationId);
143
+ routes.set(sourceReferenceKey(descriptor.navigationId), descriptor.navigationId);
144
+ const sourceRoute = pageIdFromReference(sourceKey);
145
+ if (sourceRoute) routes.set(sourceRoute, descriptor.navigationId);
146
+ }
147
+ const currentDirectory = posix.dirname(sourceReferenceKey(current.sourcePath));
148
+ function rewriteTarget(target) {
149
+ if (!target || target.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
150
+ const suffixIndex = target.search(/[?#]/);
151
+ const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
152
+ const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
153
+ const candidates = path.startsWith("/") ? [sourceReferenceKey(path), sourceReferenceKey(path).replace(/^docs\//, "")] : [
154
+ sourceReferenceKey(posix.normalize(posix.join(currentDirectory, path))),
155
+ sourceReferenceKey(path)
156
+ ];
157
+ const route = candidates.map((candidate) => routes.get(candidate)).find(Boolean);
158
+ if (!route) return target;
159
+ return `${route === "introduction" ? "/" : `/${route}`}${suffix}`;
160
+ }
161
+ let codeFence = null;
162
+ return body.split("\n").map((line) => {
163
+ const fence = line.match(/^\s*(`{3,}|~{3,})/);
164
+ if (fence) {
165
+ if (!codeFence) codeFence = fence[1][0];
166
+ else if (fence[1][0] === codeFence) codeFence = null;
167
+ return line;
168
+ }
169
+ if (codeFence) return line;
170
+ return line.replace(/(\]\()([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(target)}`).replace(/(\bhref=")([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(target)}${suffix}`);
171
+ }).join("\n");
172
+ }
173
+ function readBoundedText(path) {
174
+ if (lstatSync(path).size > MAX_CONFIG_BYTES) {
175
+ throw new Error("Docusaurus sidebar config exceeded the 1 MB static-parser limit.");
176
+ }
177
+ return readFileSync(path, "utf8");
178
+ }
179
+ function matchingObjectLiteral(source, start) {
180
+ let depth = 0;
181
+ let quote = null;
182
+ let isEscaped = false;
183
+ let lineComment = false;
184
+ let blockComment = false;
185
+ let objectStart = -1;
186
+ for (let index = start; index < source.length; index++) {
187
+ const char = source[index];
188
+ const next = source[index + 1];
189
+ if (lineComment) {
190
+ if (char === "\n") lineComment = false;
191
+ continue;
192
+ }
193
+ if (blockComment) {
194
+ if (char === "*" && next === "/") {
195
+ blockComment = false;
196
+ index++;
197
+ }
198
+ continue;
199
+ }
200
+ if (quote) {
201
+ if (isEscaped) isEscaped = false;
202
+ else if (char === "\\") isEscaped = true;
203
+ else if (char === quote) quote = null;
204
+ continue;
205
+ }
206
+ if (char === "/" && next === "/") {
207
+ lineComment = true;
208
+ index++;
209
+ continue;
210
+ }
211
+ if (char === "/" && next === "*") {
212
+ blockComment = true;
213
+ index++;
214
+ continue;
215
+ }
216
+ if (char === '"' || char === "'" || char === "`") {
217
+ quote = char;
218
+ continue;
219
+ }
220
+ if (char === "{") {
221
+ if (objectStart < 0) objectStart = index;
222
+ depth++;
223
+ } else if (char === "}" && objectStart >= 0) {
224
+ depth--;
225
+ if (depth === 0) return source.slice(objectStart, index + 1);
226
+ } else if (objectStart < 0 && !/\s/.test(char)) {
227
+ return null;
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+ function parseStaticSidebarModule(source) {
233
+ const bindings = /* @__PURE__ */ new Map();
234
+ const normalizedSource = replaceExternalFbContent(source);
235
+ const parseFailures = [];
236
+ const assignmentPatterns = [
237
+ /\bmodule\.exports\s*=\s*/g,
238
+ /\bexport\s+default\s*/g,
239
+ /\b(?:const|let|var)\s+[A-Za-z_$][\w$]*(?:\s*:\s*[^=;]+)?\s*=\s*/g
240
+ ];
241
+ const candidates = assignmentPatterns.flatMap((pattern) => [...normalizedSource.matchAll(pattern)]).sort((left, right) => (left.index ?? 0) - (right.index ?? 0));
242
+ for (const candidate of candidates) {
243
+ const literal = matchingObjectLiteral(normalizedSource, (candidate.index ?? 0) + candidate[0].length);
244
+ if (!literal) continue;
245
+ try {
246
+ const substituted = [...bindings].reduce(
247
+ (value, [name, binding]) => value.replace(
248
+ new RegExp(`(:\\s*)${name}\\b`, "g"),
249
+ (_match, prefix) => `${prefix}${JSON.stringify(binding)}`
250
+ ),
251
+ literal
252
+ );
253
+ const parsed = JSON5.parse(substituted);
254
+ const object = objectValue(parsed);
255
+ if (!object) continue;
256
+ const bindingName = candidate[0].match(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/)?.[1];
257
+ if (bindingName) {
258
+ bindings.set(bindingName, object);
259
+ continue;
260
+ }
261
+ return object;
262
+ } catch (error) {
263
+ const message = error instanceof Error ? error.message : String(error);
264
+ const line = Number(message.match(/at (\d+):/)?.[1] ?? 0);
265
+ const context = line > 0 ? literal.split("\n").slice(Math.max(0, line - 2), line + 1).join(" ") : "";
266
+ parseFailures.push(`${message}${context ? ` near ${context}` : ""}`);
267
+ }
268
+ }
269
+ const exportedBinding = normalizedSource.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\b/)?.[1] ?? normalizedSource.match(/\bmodule\.exports\s*=\s*([A-Za-z_$][\w$]*)\b/)?.[1];
270
+ if (exportedBinding && bindings.has(exportedBinding)) return bindings.get(exportedBinding);
271
+ throw new Error(`Sidebar config is executable or outside the supported data-only syntax.${parseFailures[0] ? ` ${parseFailures[0]}` : ""}`);
272
+ }
273
+ function replaceExternalFbContent(source) {
274
+ const marker = "...fbContent(";
275
+ let result = source;
276
+ let searchFrom = 0;
277
+ while (true) {
278
+ const start = result.indexOf(marker, searchFrom);
279
+ if (start < 0) return result.replace(/,\s*,/g, ",");
280
+ const objectStart = result.indexOf("{", start + marker.length);
281
+ if (objectStart < 0) return result;
282
+ const objectLiteral = matchingObjectLiteral(result, objectStart);
283
+ if (!objectLiteral) {
284
+ searchFrom = start + marker.length;
285
+ continue;
286
+ }
287
+ const objectEnd = objectStart + objectLiteral.length;
288
+ const close = result.indexOf(")", objectEnd);
289
+ if (close < 0) return result;
290
+ const externalMatch = /\bexternal\s*:\s*/g.exec(objectLiteral);
291
+ let replacement = "";
292
+ if (externalMatch) {
293
+ const arrayStart = objectLiteral.indexOf("[", externalMatch.index + externalMatch[0].length);
294
+ if (arrayStart >= 0) {
295
+ const array = matchingArrayLiteral(objectLiteral, arrayStart);
296
+ if (array) replacement = array.slice(1, -1);
297
+ }
298
+ }
299
+ let replaceEnd = close + 1;
300
+ if (!replacement) {
301
+ const trailingComma = result.slice(replaceEnd).match(/^\s*,/);
302
+ if (trailingComma) replaceEnd += trailingComma[0].length;
303
+ }
304
+ result = `${result.slice(0, start)}${replacement}${result.slice(replaceEnd)}`;
305
+ searchFrom = start + replacement.length;
306
+ }
307
+ }
308
+ function matchingArrayLiteral(source, start) {
309
+ let depth = 0;
310
+ let quote = "";
311
+ let isEscaped = false;
312
+ for (let index = start; index < source.length; index++) {
313
+ const character = source[index];
314
+ if (quote) {
315
+ if (isEscaped) isEscaped = false;
316
+ else if (character === "\\") isEscaped = true;
317
+ else if (character === quote) quote = "";
318
+ continue;
319
+ }
320
+ if (character === '"' || character === "'" || character === "`") {
321
+ quote = character;
322
+ continue;
323
+ }
324
+ if (character === "[") depth++;
325
+ if (character === "]" && --depth === 0) return source.slice(start, index + 1);
326
+ }
327
+ return null;
328
+ }
329
+ function configuredSidebarPath(repositoryRoot) {
330
+ for (const filename of ["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"]) {
331
+ const path = resolveWithin(repositoryRoot, filename);
332
+ if (!existsSync(path)) continue;
333
+ const source = readBoundedText(path);
334
+ const match = source.match(/\bsidebarPath\s*:\s*(?:require\.resolve\(\s*)?(['"])([^'"]+)\1/);
335
+ if (!match) continue;
336
+ const candidate = match[2].replace(/^\.\//, "");
337
+ const resolved = resolveWithin(repositoryRoot, candidate);
338
+ if (existsSync(resolved) && lstatSync(resolved).isFile()) return candidate;
339
+ }
340
+ return null;
341
+ }
342
+ function readDocusaurusSidebars(repositoryRoot) {
343
+ const configured = configuredSidebarPath(repositoryRoot);
344
+ const sourcePath = [configured, ...SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) => existsSync(resolveWithin(repositoryRoot, candidate)));
345
+ if (!sourcePath) return null;
346
+ const absolutePath = resolveWithin(repositoryRoot, sourcePath);
347
+ const source = readBoundedText(absolutePath);
348
+ const parsed = extname(sourcePath).toLowerCase() === ".json" ? objectValue(JSON5.parse(source)) : parseStaticSidebarModule(source);
349
+ if (!parsed) throw new Error("Docusaurus sidebar config must export an object.");
350
+ return { config: parsed, sourcePath };
351
+ }
352
+ function readCategoryMetadata(contentRoot, directory) {
353
+ for (const filename of CATEGORY_FILENAMES) {
354
+ const path = resolveWithin(contentRoot, posix.join(directory, filename));
355
+ if (!existsSync(path) || !lstatSync(path).isFile()) continue;
356
+ try {
357
+ const raw = readBoundedText(path);
358
+ const parsed = filename.endsWith(".json") ? JSON5.parse(raw) : parseYaml(raw);
359
+ const object = objectValue(parsed);
360
+ if (!object) return {};
361
+ return {
362
+ ...typeof object.label === "string" ? { label: object.label } : {},
363
+ ...typeof object.position === "number" ? { position: object.position } : {},
364
+ ..."link" in object ? { link: object.link } : {}
365
+ };
366
+ } catch {
367
+ return {};
368
+ }
369
+ }
370
+ return {};
371
+ }
372
+ function descriptorSort(left, right) {
373
+ const leftPosition = left.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
374
+ const rightPosition = right.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
375
+ if (leftPosition !== rightPosition) return leftPosition - rightPosition;
376
+ return left.sourcePath.localeCompare(right.sourcePath, void 0, { numeric: true });
377
+ }
378
+ function registerDoc(docId, context) {
379
+ const key = normalizeDocId(docId);
380
+ const descriptor = context.descriptorByDocId.get(key) ?? context.descriptors.find((page) => page.navigationId === docusaurusRouteId(key));
381
+ if (!descriptor) {
382
+ context.warnings.push({
383
+ code: "missing-page",
384
+ message: "A Docusaurus sidebar document did not resolve to an imported page.",
385
+ source: docId
386
+ });
387
+ return null;
388
+ }
389
+ context.referencedNavigationIds.add(descriptor.navigationId);
390
+ return descriptor.navigationId;
391
+ }
392
+ function generatedIndexPage(label, linkValue, context) {
393
+ const link = objectValue(linkValue);
394
+ if (!link) return null;
395
+ if (link.type === "doc" && typeof link.id === "string") return registerDoc(link.id, context);
396
+ if (link.type !== "generated-index") return null;
397
+ const fallback = `category/${slugifySegment(label)}`;
398
+ const unprefixedNavigationId = typeof link.slug === "string" ? routeFromSlug(link.slug, fallback) ?? fallback : fallback;
399
+ const navigationId = context.routePrefix && unprefixedNavigationId !== context.routePrefix && !unprefixedNavigationId.startsWith(`${context.routePrefix}/`) ? posix.join(context.routePrefix, unprefixedNavigationId) : unprefixedNavigationId;
400
+ if (!context.generatedIds.has(navigationId) && !context.descriptors.some((page) => page.navigationId === navigationId)) {
401
+ const description = typeof link.description === "string" ? link.description : `Browse the ${label} documentation.`;
402
+ context.generatedPages.push({
403
+ id: storageIdForRoute(navigationId),
404
+ navigationId,
405
+ title: typeof link.title === "string" ? link.title : label,
406
+ description,
407
+ keywords: Array.isArray(link.keywords) ? link.keywords.filter((value) => typeof value === "string") : [],
408
+ body: description,
409
+ source: `${context.sourceUrl}#${context.sidebarSource}`
410
+ });
411
+ context.generatedIds.add(navigationId);
412
+ }
413
+ context.referencedNavigationIds.add(navigationId);
414
+ return navigationId;
415
+ }
416
+ function autogeneratedItems(dirName, context) {
417
+ const normalizedDir = dirName === "." ? "" : normalizeDocId(dirName);
418
+ const inDirectory = context.descriptors.filter((descriptor) => {
419
+ const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
420
+ return !normalizedDir || source === normalizedDir || source.startsWith(`${normalizedDir}/`);
421
+ });
422
+ const directPages = [];
423
+ const childDirectories = /* @__PURE__ */ new Set();
424
+ for (const descriptor of inDirectory) {
425
+ const source = descriptor.sourcePath.replace(/\.(?:mdx?)$/i, "");
426
+ const relativePath = normalizedDir ? posix.relative(normalizedDir, source) : source;
427
+ const segments = relativePath.split("/").filter(Boolean);
428
+ if (segments.length <= 1) directPages.push(descriptor);
429
+ else childDirectories.add(segments[0]);
430
+ }
431
+ const items = directPages.sort(descriptorSort).map((descriptor) => {
432
+ context.referencedNavigationIds.add(descriptor.navigationId);
433
+ return descriptor.navigationId;
434
+ });
435
+ const groups = [...childDirectories].map((segment) => {
436
+ const directory = normalizedDir ? posix.join(normalizedDir, segment) : segment;
437
+ const metadata = readCategoryMetadata(context.contentRoot, directory);
438
+ const pages = autogeneratedItems(directory, context);
439
+ const landing = generatedIndexPage(metadata.label ?? titleCase(segment), metadata.link, context);
440
+ if (landing && !pages.includes(landing)) pages.unshift(landing);
441
+ return {
442
+ position: metadata.position ?? Number.MAX_SAFE_INTEGER,
443
+ segment,
444
+ group: {
445
+ group: metadata.label ?? titleCase(segment),
446
+ pages
447
+ }
448
+ };
449
+ }).sort((left, right) => left.position - right.position || left.segment.localeCompare(right.segment, void 0, { numeric: true }));
450
+ items.push(...groups.map((entry) => entry.group));
451
+ return items;
452
+ }
453
+ function convertItems(value, context) {
454
+ if (typeof value === "string") {
455
+ const page = registerDoc(value, context);
456
+ return page ? [page] : [];
457
+ }
458
+ if (Array.isArray(value)) return value.flatMap((entry) => convertItems(entry, context));
459
+ const object = objectValue(value);
460
+ if (!object) return [];
461
+ if (object.type === "doc" || object.type === "ref") {
462
+ const page = typeof object.id === "string" ? registerDoc(object.id, context) : null;
463
+ return page ? [page] : [];
464
+ }
465
+ if (object.type === "autogenerated") {
466
+ return autogeneratedItems(typeof object.dirName === "string" ? object.dirName : ".", context);
467
+ }
468
+ if (object.type === "category") {
469
+ const label = typeof object.label === "string" ? object.label : "Documentation";
470
+ const pages = convertItems(object.items, context);
471
+ const landing = generatedIndexPage(label, object.link, context);
472
+ if (landing && !pages.includes(landing)) pages.unshift(landing);
473
+ return pages.length > 0 ? [{ group: label, pages }] : [];
474
+ }
475
+ if (object.type === "link" || object.type === "html") return [];
476
+ return Object.entries(object).flatMap(([label, items]) => {
477
+ const pages = convertItems(items, context);
478
+ return pages.length > 0 ? [{ group: label, pages }] : [];
479
+ });
480
+ }
481
+ function projectDocusaurusNavigation(input) {
482
+ const warnings = [];
483
+ const descriptorByDocId = new Map(input.descriptors.map((page) => [page.docId, page]));
484
+ const context = {
485
+ contentRoot: input.contentRoot,
486
+ descriptors: input.descriptors,
487
+ descriptorByDocId,
488
+ generatedPages: [],
489
+ generatedIds: /* @__PURE__ */ new Set(),
490
+ referencedNavigationIds: /* @__PURE__ */ new Set(),
491
+ sourceUrl: input.sourceUrl,
492
+ routePrefix: input.routePrefix ?? "",
493
+ sidebarSource: input.sidebars?.sourcePath ?? "autogenerated sidebar",
494
+ warnings
495
+ };
496
+ const sidebarEntries = input.sidebars ? Object.entries(input.sidebars.config) : [["docs", [
497
+ { type: "autogenerated", dirName: "." }
498
+ ]]];
499
+ const tabs = sidebarEntries.flatMap(([sidebarId, value]) => {
500
+ const converted = convertItems(value, context);
501
+ const loosePages = converted.filter((entry) => typeof entry === "string");
502
+ const groups = converted.filter((entry) => typeof entry !== "string");
503
+ if (loosePages.length > 0) groups.unshift({ group: "Overview", pages: loosePages });
504
+ if (groups.length === 0) return [];
505
+ return [{
506
+ tab: sidebarEntries.length === 1 ? "Documentation" : titleCase(sidebarId),
507
+ groups
508
+ }];
509
+ });
510
+ const unreferenced = input.descriptors.filter((page) => !context.referencedNavigationIds.has(page.navigationId)).sort(descriptorSort).map((page) => page.navigationId);
511
+ if (unreferenced.length > 0) {
512
+ if (tabs.length === 0) tabs.push({ tab: "Documentation", groups: [] });
513
+ tabs[0].groups?.push({ group: "Additional", pages: unreferenced });
514
+ }
515
+ return {
516
+ docsConfig: { tabs },
517
+ generatedPages: context.generatedPages,
518
+ referencedNavigationIds: context.referencedNavigationIds,
519
+ warnings
520
+ };
521
+ }
522
+
523
+ // src/mdx.ts
524
+ import matter from "gray-matter";
525
+ function titleFromId(id) {
526
+ const value = id.split("/").at(-1) ?? id;
527
+ if (value === "introduction") return "Introduction";
528
+ return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
529
+ }
530
+ function firstParagraph(content) {
531
+ for (const block of content.split(/\n\s*\n/)) {
532
+ const value = block.replace(/\s+/g, " ").trim();
533
+ if (!value || /^(?:#|```|:::|<|import\s|export\s)/.test(value)) continue;
534
+ return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").slice(0, 240);
535
+ }
536
+ return "";
537
+ }
538
+ function docusaurusAdmonitionTag(kind) {
539
+ if (kind === "danger") return "Error";
540
+ if (kind === "info") return "Info";
541
+ if (kind === "caution" || kind === "warning") return "Warning";
542
+ return "Note";
543
+ }
544
+ function normalizeDocusaurusAdmonitions(body) {
545
+ const lines = body.split("\n");
546
+ const openAdmonitions = [];
547
+ let codeFence = null;
548
+ return lines.map((line) => {
549
+ const codeMatch = line.match(/^\s*(`{3,}|~{3,})/);
550
+ if (codeMatch) {
551
+ if (!codeFence) codeFence = codeMatch[1][0];
552
+ else if (codeMatch[1][0] === codeFence) codeFence = null;
553
+ return line;
554
+ }
555
+ if (codeFence) return line;
556
+ const opening = line.match(/^\s*(:{3,})(note|tip|info|warning|caution|danger)(?:\[([^\]]+)\]|\s+(.+))?\s*$/i);
557
+ if (opening) {
558
+ const tag = docusaurusAdmonitionTag(opening[2].toLowerCase());
559
+ openAdmonitions.push({ delimiter: opening[1], tag });
560
+ const title = (opening[3] ?? opening[4])?.trim();
561
+ return title ? `<${tag}>
562
+ **${title}**` : `<${tag}>`;
563
+ }
564
+ const closing = line.match(/^\s*(:{3,})\s*$/);
565
+ const current = openAdmonitions.at(-1);
566
+ if (closing && current?.delimiter === closing[1]) {
567
+ openAdmonitions.pop();
568
+ return `</${current.tag}>`;
569
+ }
570
+ return line;
571
+ }).join("\n");
572
+ }
573
+ function normalizeMdx(body) {
574
+ return normalizeDocusaurusAdmonitions(body).replace(/^import\s+(?:Tabs|TabItem|Link|DocCardList|TOCInline)\s+from\s+['"]@(?:theme|docusaurus)\/[^'"]+['"]\s*;?\s*$/gm, "").replace(/<TabItem\b([^>]*)>/g, (_match, attributes) => {
575
+ const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
576
+ return `<Tab title="${title.replace(/"/g, "&quot;")}">`;
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 objectValue(value) {
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 = objectValue(current);
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 = objectValue(value);
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(readFileSync(referencedFile, "utf8"));
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(existsSync);
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(readFileSync(configPath, "utf8"));
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 = objectValue(value);
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 = objectValue(containerValue);
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 = objectValue(value);
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 = objectValue(config.navigation) ?? config;
256
- const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue).filter((value) => Boolean(value)) : [];
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 = objectValue(value);
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 titleCase(value) {
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] ?? titleCase(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 : titleCase(segment),
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" : titleCase(segment),
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(extname(filename).toLowerCase());
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 = 1e8;
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 (existsSync2(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
1093
+ if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
518
1094
  const docsJson = resolveWithin(repositoryDir, "docs.json");
519
- if (existsSync2(docsJson)) {
1095
+ if (existsSync3(docsJson)) {
520
1096
  try {
521
- const config = JSON.parse(readFileSync2(docsJson, "utf8"));
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) => existsSync2(resolveWithin(repositoryDir, name)))) return "docusaurus";
528
- if (existsSync2(resolveWithin(repositoryDir, "SUMMARY.md"))) return "gitbook";
529
- if (existsSync2(resolveWithin(repositoryDir, ".vitepress"))) return "vitepress";
530
- if (["astro.config.mjs", "astro.config.ts"].some((name) => existsSync2(resolveWithin(repositoryDir, name)))) return "starlight";
531
- if (["pages/_meta.json", "_meta.json"].some((name) => existsSync2(resolveWithin(repositoryDir, name)))) return "nextra";
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(extname2(entry.name).toLowerCase())) return true;
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 (existsSync2(path) && lstatSync(path).isDirectory() && containsMarkdown(path)) return candidate;
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*;?\s*$/gm,
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 candidate = sourcePath.startsWith("/") ? resolveWithin(repositoryRoot, sourcePath.replace(/^\/+/, "")) : resolveWithin(dirname2(currentFile), sourcePath);
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 (!existsSync2(candidate) || !lstatSync(candidate).isFile()) throw new Error("file not found");
1162
+ if (!existsSync3(candidate) || !lstatSync2(candidate).isFile()) throw new Error("file not found");
585
1163
  const nested = inlineMdxSnippets(
586
- withoutFrontmatter(readFileSync2(candidate, "utf8")),
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
- const configuredDocsDir = options.docsDir ?? (platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir));
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 (!existsSync2(contentRoot) || !lstatSync(contentRoot).isDirectory()) {
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
- if (file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()))) {
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(extname2(file.relativePath).toLowerCase())) {
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 = lstatSync(file.absolutePath).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
- readFileSync2(file.absolutePath, "utf8"),
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.add(id);
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(extname2(file.relativePath).toLowerCase())) continue;
740
- const size = lstatSync(file.absolutePath).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 assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : file.relativePath);
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: readFileSync2(file.absolutePath) });
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: readFileSync2(openApi.absolutePath) });
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: files.length, imported: pages.length, skipped }
1472
+ stats: { discovered, imported: pages.length, skipped }
772
1473
  };
773
1474
  }
774
1475
 
@@ -782,6 +1483,7 @@ function renderPage(bundle, page) {
782
1483
  `title: ${yamlString(page.title)}`,
783
1484
  `description: ${yamlString(page.description)}`,
784
1485
  page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
1486
+ page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
785
1487
  bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
786
1488
  "---",
787
1489
  "",
@@ -860,6 +1562,7 @@ function renderMigrationFiles(bundle, options = {}) {
860
1562
  // src/url.ts
861
1563
  import { load } from "cheerio";
862
1564
  import TurndownService from "turndown";
1565
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
863
1566
  var DEFAULT_MAX_PAGES = 1e3;
864
1567
  var DEFAULT_MAX_TOTAL_BYTES = 1e8;
865
1568
  var MAX_DISCOVERED_URLS = 5e3;
@@ -1041,7 +1744,7 @@ function normalizeCandidate(value, base, source, scopePath) {
1041
1744
  if (!isInScope(url, source, scopePath)) return null;
1042
1745
  if (url.pathname.startsWith("/cdn-cgi/")) return null;
1043
1746
  if (/\/(?:llms(?:-full)?|robots)\.txt$/i.test(url.pathname) || /\/sitemap\.xml$/i.test(url.pathname)) return null;
1044
- if (/\.(?:avif|bmp|gif|ico|jpe?g|mp[34]|pdf|png|svg|webm|webp|zip)$/i.test(url.pathname)) return null;
1747
+ if (/\.(?:avif|bmp|gif|ico|jpe?g|mp[34]|pdf|png|svg|ya?ml|webm|webp|zip)$/i.test(url.pathname) || /\/(?:openapi|swagger|asyncapi)[^/]*\.json$/i.test(url.pathname)) return null;
1045
1748
  return url;
1046
1749
  } catch {
1047
1750
  return null;
@@ -1166,10 +1869,57 @@ function hasUnsafePortableUrlProp(value) {
1166
1869
  }
1167
1870
  return false;
1168
1871
  }
1872
+ function portableHtmlToMarkdown(value) {
1873
+ return value.replace(/<span\b[^>]*>|<\/span>/gi, "").replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_original, attributes, label) => {
1874
+ const href = attributes.match(/\bhref\s*=\s*(['"])(.*?)\1/i)?.[2];
1875
+ const plainLabel = label.replace(/<[^>]+>/g, "").trim();
1876
+ if (!href || hasUnsafeUrlScheme(href)) return plainLabel;
1877
+ return `[${plainLabel.replace(/]/g, "\\]")}](${href})`;
1878
+ }).replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_original, level, content) => {
1879
+ return `
1880
+ ${"#".repeat(Number(level))} ${content.trim()}
1881
+ `;
1882
+ }).replace(/<p\b[^>]*>([\s\S]*?)<\/p>/gi, (_original, content) => `
1883
+ ${content.trim()}
1884
+ `).replace(/<br\s*\/?\s*>/gi, "\n").replace(/<\/?(?:article|div|main|section)\b[^>]*>/gi, "");
1885
+ }
1886
+ function transformMarkdownProse(body, transform) {
1887
+ const output = [];
1888
+ let prose = [];
1889
+ let fenceCharacter = "";
1890
+ let fenceLength = 0;
1891
+ const flushProse = () => {
1892
+ if (prose.length === 0) return;
1893
+ output.push(transform(prose.join("\n")));
1894
+ prose = [];
1895
+ };
1896
+ for (const line of body.split("\n")) {
1897
+ const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
1898
+ if (fence && !fenceCharacter) {
1899
+ flushProse();
1900
+ fenceCharacter = fence[1][0];
1901
+ fenceLength = fence[1].length;
1902
+ output.push(line);
1903
+ continue;
1904
+ }
1905
+ if (fenceCharacter) {
1906
+ output.push(line);
1907
+ if (fence && fence[1][0] === fenceCharacter && fence[1].length >= fenceLength && fence[2].trim() === "") {
1908
+ fenceCharacter = "";
1909
+ fenceLength = 0;
1910
+ }
1911
+ continue;
1912
+ }
1913
+ prose.push(line);
1914
+ }
1915
+ flushProse();
1916
+ return output.join("\n");
1917
+ }
1169
1918
  function sanitizeRemoteMarkdown(body) {
1170
1919
  let fenceCharacter = "";
1171
1920
  let fenceLength = 0;
1172
- const sanitized = body.split("\n").map((line) => {
1921
+ const portableBody = transformMarkdownProse(body, portableHtmlToMarkdown);
1922
+ const sanitized = portableBody.split("\n").map((line) => {
1173
1923
  const fence = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
1174
1924
  if (fence && !fenceCharacter) {
1175
1925
  fenceCharacter = fence[1][0];
@@ -1182,14 +1932,14 @@ function sanitizeRemoteMarkdown(body) {
1182
1932
  return line;
1183
1933
  }
1184
1934
  if (fenceCharacter) return line;
1185
- return stripEventHandlerProps(line.replace(/!\[([^\]]*)\]\(\s*([^)]+)\)/g, (original, alternative, destination) => {
1935
+ return portableHtmlToMarkdown(stripEventHandlerProps(line.replace(/!\[([^\]]*)\]\(\s*([^)]+)\)/g, (original, alternative, destination) => {
1186
1936
  return hasUnsafeUrlScheme(destination.replace(/^<|>$/g, "")) ? alternative : original;
1187
- }).replace(/<\/?div\b[^>]*>/gi, "").replace(/<img\s+([^>]*?)\/?\s*>/gi, (original, attributes) => {
1937
+ }).replace(/<img\s+([^>]*?)\/?\s*>/gi, (original, attributes) => {
1188
1938
  const source = attributes.match(/\bsrc\s*=\s*(['"])(.*?)\1/i)?.[2];
1189
1939
  const alternative = attributes.match(/\balt\s*=\s*(['"])(.*?)\1/i)?.[2] ?? "";
1190
1940
  if (!source || hasUnsafeUrlScheme(source)) return "";
1191
1941
  return `![${alternative.replace(/]/g, "\\]")}](${source})`;
1192
- }));
1942
+ })));
1193
1943
  }).join("\n");
1194
1944
  const executable = withoutFencedCode(sanitized).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<(?:https?:\/\/|mailto:)[^>]+>/gi, "").replace(/(`+)[\s\S]*?\1/g, "");
1195
1945
  if (/^\s*(?:import|export)\s/m.test(executable)) return null;
@@ -1226,8 +1976,12 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
1226
1976
  const id = pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
1227
1977
  const localizedId = id ? canonicalLocalizedPageId(id) : id;
1228
1978
  const destinationId = localizedId === sourceHomePageId ? "introduction" : localizedId;
1229
- if (destinationId && importedIds.has(destinationId)) {
1230
- const destination = destinationId === "introduction" ? "/" : `/${destinationId}`;
1979
+ let importedDestinationId = destinationId && importedIds.has(destinationId) ? destinationId : void 0;
1980
+ if (destinationId && !importedDestinationId) {
1981
+ importedDestinationId = [`${destinationId}/overview`, `${destinationId}/introduction`].find((candidate) => importedIds.has(candidate)) ?? [...importedIds].find((candidate) => candidate.startsWith(`${destinationId}/`));
1982
+ }
1983
+ if (importedDestinationId) {
1984
+ const destination = importedDestinationId === "introduction" ? "/" : `/${importedDestinationId}`;
1231
1985
  return `${destination}${target.search}${target.hash}`;
1232
1986
  }
1233
1987
  return target.toString();
@@ -1278,6 +2032,36 @@ function stripMintlifyDocumentationIndex(body) {
1278
2032
  while (index < lines.length && lines[index].trim() === "") index += 1;
1279
2033
  return lines.slice(index).join("\n");
1280
2034
  }
2035
+ function extractEmbeddedOpenApi(content) {
2036
+ const fencePattern = /^\s{0,3}(`{3,}|~{3,})(?:yaml|yml|json)\s+(\S+\.(?:yaml|yml|json))\s+(delete|get|head|options|patch|post|put|trace)\s+(\/\S+)\s*\r?\n([\s\S]*?)^\s{0,3}\1\s*$/im;
2037
+ const match = fencePattern.exec(content);
2038
+ if (!match) return { content };
2039
+ try {
2040
+ const parsed = parseYaml2(match[5], { maxAliasCount: 50 });
2041
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
2042
+ const document = parsed;
2043
+ if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
2044
+ const method = match[3].toUpperCase();
2045
+ const path = match[4];
2046
+ const paths = document.paths;
2047
+ if (!paths || typeof paths !== "object" || Array.isArray(paths)) return { content };
2048
+ const pathItem = paths[path];
2049
+ if (!pathItem || typeof pathItem !== "object" || Array.isArray(pathItem) || !(match[3].toLowerCase() in pathItem)) return { content };
2050
+ const before = content.slice(0, match.index).replace(/(?:^|\n)##\s+OpenAPI\s*\n\s*$/i, "\n");
2051
+ const after = content.slice(match.index + match[0].length);
2052
+ return {
2053
+ content: `${before}${after}`.trim(),
2054
+ openapi: `${method} ${path}`,
2055
+ fragment: {
2056
+ method,
2057
+ path,
2058
+ document
2059
+ }
2060
+ };
2061
+ } catch {
2062
+ return { content };
2063
+ }
2064
+ }
1281
2065
  function markdownPage(document, id) {
1282
2066
  const parsed = remoteMarkdownMetadata(document.body);
1283
2067
  let content = stripMintlifyDocumentationIndex(parsed.content).trimStart();
@@ -1292,15 +2076,74 @@ function markdownPage(document, id) {
1292
2076
  content = content.slice(quoteMatch[0].length).trimStart();
1293
2077
  }
1294
2078
  }
2079
+ const embeddedOpenApi = extractEmbeddedOpenApi(content);
1295
2080
  const raw = [
1296
2081
  "---",
1297
2082
  `title: ${JSON.stringify(title)}`,
1298
2083
  ...description ? [`description: ${JSON.stringify(description)}`] : [],
2084
+ ...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
1299
2085
  "---",
1300
2086
  "",
1301
- content
2087
+ embeddedOpenApi.content
1302
2088
  ].join("\n");
1303
- return parseMarkdownPage({ id, raw, source: document.finalUrl.toString() });
2089
+ return {
2090
+ page: parseMarkdownPage({ id, raw, source: document.finalUrl.toString() }),
2091
+ openApiFragment: embeddedOpenApi.fragment
2092
+ };
2093
+ }
2094
+ function mergeRecord(target, source) {
2095
+ if (!source || typeof source !== "object" || Array.isArray(source)) return;
2096
+ for (const [key, value] of Object.entries(source)) {
2097
+ if (["__proto__", "constructor", "prototype"].includes(key)) continue;
2098
+ if (!Object.prototype.hasOwnProperty.call(target, key)) {
2099
+ target[key] = value;
2100
+ continue;
2101
+ }
2102
+ const current = target[key];
2103
+ if (current && value && typeof current === "object" && typeof value === "object" && !Array.isArray(current) && !Array.isArray(value)) {
2104
+ mergeRecord(current, value);
2105
+ }
2106
+ }
2107
+ }
2108
+ function mergeEmbeddedOpenApi(fragments) {
2109
+ const first = fragments[0];
2110
+ if (!first) return null;
2111
+ const combined = {
2112
+ ...first.document,
2113
+ paths: {},
2114
+ components: {}
2115
+ };
2116
+ const combinedPaths = combined.paths;
2117
+ const combinedComponents = combined.components;
2118
+ const tags = /* @__PURE__ */ new Map();
2119
+ for (const fragment of fragments) {
2120
+ const sourcePaths = fragment.document.paths;
2121
+ const sourcePathItem = sourcePaths?.[fragment.path];
2122
+ if (!sourcePathItem || typeof sourcePathItem !== "object" || Array.isArray(sourcePathItem)) continue;
2123
+ const sourceOperation = sourcePathItem[fragment.method.toLowerCase()];
2124
+ if (!sourceOperation || typeof sourceOperation !== "object" || Array.isArray(sourceOperation)) continue;
2125
+ const operation = { ...sourceOperation };
2126
+ if (!operation.servers && Array.isArray(fragment.document.servers)) operation.servers = fragment.document.servers;
2127
+ if (!operation.security && Array.isArray(fragment.document.security)) operation.security = fragment.document.security;
2128
+ const existingPath = combinedPaths[fragment.path];
2129
+ const pathItem = existingPath && typeof existingPath === "object" && !Array.isArray(existingPath) ? existingPath : {};
2130
+ for (const sharedKey of ["parameters", "servers", "summary", "description"]) {
2131
+ const sharedValue = sourcePathItem[sharedKey];
2132
+ if (sharedValue !== void 0 && pathItem[sharedKey] === void 0) pathItem[sharedKey] = sharedValue;
2133
+ }
2134
+ pathItem[fragment.method.toLowerCase()] = operation;
2135
+ combinedPaths[fragment.path] = pathItem;
2136
+ mergeRecord(combinedComponents, fragment.document.components);
2137
+ for (const tag of Array.isArray(fragment.document.tags) ? fragment.document.tags : []) {
2138
+ if (!tag || typeof tag !== "object" || Array.isArray(tag)) continue;
2139
+ const name = tag.name;
2140
+ if (typeof name === "string" && !tags.has(name)) tags.set(name, tag);
2141
+ }
2142
+ }
2143
+ if (Object.keys(combinedPaths).length === 0) return null;
2144
+ if (Object.keys(combinedComponents).length === 0) delete combined.components;
2145
+ if (tags.size > 0) combined.tags = [...tags.values()];
2146
+ return new TextEncoder().encode(stringifyYaml(combined));
1304
2147
  }
1305
2148
  function htmlPage(document, id) {
1306
2149
  const $ = load(document.body);
@@ -1310,6 +2153,7 @@ function htmlPage(document, id) {
1310
2153
  const content = $('main article, article, main, [role="main"]').first();
1311
2154
  const root = content.length ? content : $("body");
1312
2155
  root.find("script,style,noscript,nav,header,footer,aside,form,button,svg,iframe,object,embed,link,meta").remove();
2156
+ root.find(".theme-doc-version-badge,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
1313
2157
  root.find("a[href]").each((_index, element) => {
1314
2158
  const href = $(element).attr("href");
1315
2159
  if (!href) return;
@@ -1340,12 +2184,78 @@ function htmlPage(document, id) {
1340
2184
  });
1341
2185
  const turndown = new TurndownService({ codeBlockStyle: "fenced", emDelimiter: "_", headingStyle: "atx" });
1342
2186
  turndown.remove(["script", "style", "noscript"]);
2187
+ turndown.addRule("docusaurus-admonition", {
2188
+ filter: (node) => node.nodeName === "DIV" && node.classList.contains("theme-admonition"),
2189
+ replacement: (_content, node) => {
2190
+ const element = node;
2191
+ const className = element.className;
2192
+ const tag = /admonition-(?:danger)/.test(className) ? "Error" : /admonition-(?:caution|warning)/.test(className) ? "Warning" : /admonition-info/.test(className) ? "Info" : "Note";
2193
+ const fragment = load(element.outerHTML);
2194
+ const clone = fragment(".theme-admonition").first();
2195
+ const title2 = clone.find('[class*="admonitionHeading"],.admonition-heading').first();
2196
+ const titleText = title2.text().trim();
2197
+ title2.remove();
2198
+ const markdown = turndown.turndown(clone.html() ?? "").trim();
2199
+ return `
2200
+
2201
+ <${tag}>
2202
+ ${titleText ? `**${titleText}**
2203
+
2204
+ ` : ""}${markdown}
2205
+ </${tag}>
2206
+
2207
+ `;
2208
+ }
2209
+ });
2210
+ turndown.addRule("docusaurus-tabs", {
2211
+ filter: (node) => node.nodeName === "DIV" && node.classList.contains("tabs-container"),
2212
+ replacement: (_content, node) => {
2213
+ const element = node;
2214
+ const fragment = load(element.outerHTML);
2215
+ const container = fragment(".tabs-container").first();
2216
+ const labels = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
2217
+ const panels = container.children('div[role="tabpanel"]').toArray();
2218
+ if (panels.length === 0) return _content;
2219
+ const tabs = panels.map((panel, index) => {
2220
+ const title2 = (labels[index] ?? `Tab ${index + 1}`).replace(/"/g, "&quot;");
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, "&lt;").replace(/>/g, "&gt;").replace(/\{/g, "&#123;").replace(/\}/g, "&#125;").trim())).filter((row) => row.length > 0);
2239
+ if (rows.length === 0) return "";
2240
+ const width = Math.max(...rows.map((row) => row.length));
2241
+ const normalized = rows.map((row) => [...row, ...Array(width - row.length).fill("")]);
2242
+ return `
2243
+
2244
+ | ${normalized[0].join(" | ")} |
2245
+ | ${Array(width).fill("---").join(" | ")} |
2246
+ ${normalized.slice(1).map((row) => `| ${row.join(" | ")} |`).join("\n")}
2247
+
2248
+ `;
2249
+ }
2250
+ });
1343
2251
  turndown.addRule("migration-safe-fenced-code", {
1344
2252
  filter: (node) => node.nodeName === "PRE",
1345
2253
  replacement: (_content, node) => {
1346
2254
  const element = node;
1347
- const code = element.textContent?.replace(/\n$/, "") ?? "";
1348
- const language = element.querySelector("code")?.className.match(/(?:language-|lang-)([A-Za-z0-9_+-]+)/)?.[1] ?? "";
2255
+ const fragment = load(element.outerHTML);
2256
+ const tokenLines = fragment(".token-line").toArray();
2257
+ const code = (tokenLines.length > 0 ? tokenLines.map((line) => fragment(line).text()).join("\n") : fragment("pre").text()).replace(/\n$/, "");
2258
+ const language = fragment("code").attr("class")?.match(/(?:language-|lang-)([A-Za-z0-9_+-]+)/)?.[1] ?? "";
1349
2259
  const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
1350
2260
  const fence = "`".repeat(Math.max(3, longestFence + 1));
1351
2261
  return `
@@ -1360,7 +2270,7 @@ ${fence}
1360
2270
  const escape = turndown.escape.bind(turndown);
1361
2271
  turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
1362
2272
  const body = turndown.turndown(root.html() ?? "").trim();
1363
- if (!title || body.length < 40) return { page: null, links };
2273
+ if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
1364
2274
  return {
1365
2275
  page: {
1366
2276
  id,
@@ -1378,6 +2288,176 @@ function htmlNavigationLinks(document) {
1378
2288
  const $ = load(document.body);
1379
2289
  return $('.nav-tabs a[href], nav a[href], aside a[href], [role="navigation"] a[href]').map((_index, element) => $(element).attr("href") ?? "").get().filter(Boolean);
1380
2290
  }
2291
+ function normalizedLinkLabel(value) {
2292
+ return value.replace(/\s+/g, " ").trim();
2293
+ }
2294
+ function readDocusaurusSidebar(document) {
2295
+ if (!/html/i.test(document.contentType)) return null;
2296
+ const $ = load(document.body);
2297
+ const sidebar = $("ul.theme-doc-sidebar-menu").first();
2298
+ if (sidebar.length === 0) return null;
2299
+ function readList(list) {
2300
+ return $(list).children("li").toArray().flatMap((item) => {
2301
+ const directLink = $(item).children("a.menu__link").first();
2302
+ const collapsibleLink = $(item).children(".menu__list-item-collapsible").children("a.menu__link").first();
2303
+ const link = directLink.length > 0 ? directLink : collapsibleLink;
2304
+ const label = normalizedLinkLabel(link.text());
2305
+ const href = link.attr("href");
2306
+ const nested = $(item).children("ul.menu__list").first();
2307
+ const children = nested.length > 0 ? readList(nested.get(0)) : [];
2308
+ const isCategory = $(item).hasClass("theme-doc-sidebar-item-category") || link.hasClass("menu__link--sublist");
2309
+ if (!label && children.length === 0) return [];
2310
+ return [{
2311
+ label: label || "Documentation",
2312
+ ...href ? { href: new URL(href, document.finalUrl).toString() } : {},
2313
+ ...children.length > 0 ? { items: children } : {},
2314
+ ...isCategory ? { isCategory: true } : {}
2315
+ }];
2316
+ });
2317
+ }
2318
+ return { items: readList(sidebar.get(0)) };
2319
+ }
2320
+ function docusaurusSidebarHrefs(snapshot) {
2321
+ const hrefs = [];
2322
+ const visit = (items) => {
2323
+ for (const item of items) {
2324
+ if (item.href) hrefs.push(item.href);
2325
+ if (item.items) visit(item.items);
2326
+ }
2327
+ };
2328
+ if (snapshot) visit(snapshot.items);
2329
+ return hrefs;
2330
+ }
2331
+ function deriveDocusaurusScope(source, document, submittedScopePath) {
2332
+ const $ = load(document.body);
2333
+ const sidebar = readDocusaurusSidebar(document);
2334
+ const internalPaths = docusaurusSidebarHrefs(sidebar).flatMap((href) => {
2335
+ try {
2336
+ const url = new URL(href);
2337
+ return url.origin === source.origin ? [url.pathname.replace(/\/+$/, "") || "/"] : [];
2338
+ } catch {
2339
+ return [];
2340
+ }
2341
+ });
2342
+ const counts = /* @__PURE__ */ new Map();
2343
+ for (const path of internalPaths) {
2344
+ const segments = path.split("/").filter(Boolean);
2345
+ for (let length = 1; length < segments.length; length++) {
2346
+ const prefix = `/${segments.slice(0, length).join("/")}`;
2347
+ counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
2348
+ }
2349
+ }
2350
+ const threshold = Math.max(2, Math.ceil(internalPaths.length * 0.6));
2351
+ const scopePath = [...counts].filter(([, count]) => count >= threshold).sort((left, right) => right[0].split("/").length - left[0].split("/").length || right[1] - left[1])[0]?.[0] ?? submittedScopePath;
2352
+ const excludedVersionPrefixes = $(".dropdown__menu a[href]").toArray().flatMap((element) => {
2353
+ const href = $(element).attr("href");
2354
+ if (!href) return [];
2355
+ try {
2356
+ const target = new URL(href, document.finalUrl);
2357
+ if (target.origin !== source.origin || !isInScope(target, source, scopePath)) return [];
2358
+ const relativePath = target.pathname.slice(scopePath.length).replace(/^\/+/, "");
2359
+ const version = relativePath.split("/", 1)[0];
2360
+ if (!/^(?:legacy|next|v\d[\w.-]*)$/i.test(version)) return [];
2361
+ return [`${scopePath}/${version}`];
2362
+ } catch {
2363
+ return [];
2364
+ }
2365
+ });
2366
+ return {
2367
+ scopePath,
2368
+ excludedVersionPrefixes: [...new Set(excludedVersionPrefixes)],
2369
+ sidebar
2370
+ };
2371
+ }
2372
+ function docusaurusRouteIdForUrl(url, scopePath) {
2373
+ let path = url.pathname.replace(/\/+$/, "");
2374
+ if (scopePath === "/") path = path.replace(/^\/+/, "");
2375
+ else if (path === scopePath) path = "";
2376
+ else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
2377
+ else return null;
2378
+ const segments = path.split("/").filter(Boolean).map((segment) => {
2379
+ let decoded = segment;
2380
+ try {
2381
+ decoded = decodeURIComponent(segment);
2382
+ } catch {
2383
+ }
2384
+ return decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
2385
+ }).filter(Boolean);
2386
+ return segments.join("/") || "introduction";
2387
+ }
2388
+ function storageIdForDocusaurusRoute(routeId) {
2389
+ return /(?:^|\/)(?:index|readme)$/i.test(routeId) ? `${routeId}/index` : routeId;
2390
+ }
2391
+ function docusaurusNavigationFromSnapshots(snapshots, pages, scopePath) {
2392
+ const imported = new Set(pages.map((page) => page.navigationId));
2393
+ const claimed = /* @__PURE__ */ new Set();
2394
+ const uniqueSnapshots = [...new Map(snapshots.map((snapshot) => [
2395
+ JSON.stringify(snapshot.items),
2396
+ snapshot
2397
+ ])).values()];
2398
+ const mergedSnapshots = [];
2399
+ function mergeItems(target, incoming) {
2400
+ for (const item of incoming) {
2401
+ const existing = target.find((candidate) => candidate.label === item.label && (candidate.items || item.items)) ?? target.find((candidate) => candidate.label === item.label && candidate.href === item.href);
2402
+ if (!existing) {
2403
+ target.push(structuredClone(item));
2404
+ continue;
2405
+ }
2406
+ if (!existing.href && item.href) existing.href = item.href;
2407
+ if (item.items) {
2408
+ existing.items ??= [];
2409
+ mergeItems(existing.items, item.items);
2410
+ }
2411
+ }
2412
+ }
2413
+ for (const snapshot of uniqueSnapshots) {
2414
+ const incomingLabels = new Set(snapshot.items.map((item) => item.label));
2415
+ const cluster = mergedSnapshots.find((candidate) => {
2416
+ const candidateLabels = new Set(candidate.items.map((item) => item.label));
2417
+ const overlap = [...incomingLabels].filter((label) => candidateLabels.has(label)).length;
2418
+ return overlap >= Math.max(2, Math.ceil(Math.min(incomingLabels.size, candidateLabels.size) * 0.5));
2419
+ });
2420
+ if (cluster) mergeItems(cluster.items, snapshot.items);
2421
+ else mergedSnapshots.push(structuredClone(snapshot));
2422
+ }
2423
+ const pageIdsFor = (snapshot) => docusaurusSidebarHrefs(snapshot).flatMap((href) => {
2424
+ const id = docusaurusRouteIdForUrl(new URL(href), scopePath);
2425
+ return id && imported.has(id) ? [id] : [];
2426
+ });
2427
+ mergedSnapshots.sort((left, right) => pageIdsFor(right).length - pageIdsFor(left).length);
2428
+ const tabs = mergedSnapshots.flatMap((snapshot, snapshotIndex) => {
2429
+ function convert(items) {
2430
+ const converted2 = [];
2431
+ for (const item of items) {
2432
+ const routeId = item.href ? docusaurusRouteIdForUrl(new URL(item.href), scopePath) : null;
2433
+ const page = routeId && imported.has(routeId) && !claimed.has(routeId) ? routeId : null;
2434
+ if (page) claimed.add(page);
2435
+ const children = item.items ? convert(item.items) : [];
2436
+ if (children.length > 0 || item.isCategory) {
2437
+ converted2.push({ group: item.label, pages: page ? [page, ...children] : children });
2438
+ continue;
2439
+ }
2440
+ if (page) converted2.push(page);
2441
+ }
2442
+ return converted2;
2443
+ }
2444
+ const converted = convert(snapshot.items);
2445
+ const loosePages = converted.filter((item) => typeof item === "string");
2446
+ const groups = converted.filter((item) => typeof item !== "string");
2447
+ if (loosePages.length > 0) groups.unshift({ group: "Overview", pages: loosePages });
2448
+ if (groups.length === 0) return [];
2449
+ const snapshotPageIds = pageIdsFor(snapshot);
2450
+ const firstSegments = new Set(snapshotPageIds.map((id) => id.split("/", 1)[0]));
2451
+ const tab = snapshotIndex > 0 && firstSegments.size === 1 ? normalizedLinkLabel([...firstSegments][0].replace(/[-_]/g, " ")).replace(/\b\w/g, (letter) => letter.toUpperCase()) : "Documentation";
2452
+ return [{ tab, groups }];
2453
+ });
2454
+ const additional = pages.map((page) => page.navigationId).filter((id) => !claimed.has(id));
2455
+ if (additional.length > 0) {
2456
+ if (tabs.length === 0) return buildNavigationFromPages(pages);
2457
+ tabs[0].groups?.push({ group: "Additional", pages: additional });
2458
+ }
2459
+ return { tabs };
2460
+ }
1381
2461
  function htmlTopLevelNavigation(document, source, scopePath) {
1382
2462
  const $ = load(document.body);
1383
2463
  const collectCandidates = (selector) => {
@@ -1456,24 +2536,54 @@ async function migrateUrl(options) {
1456
2536
  }
1457
2537
  const htmlProbe = /html/i.test(initial.contentType) ? initial : await safeFetch(fetcher, source, "text/html,application/xhtml+xml");
1458
2538
  const scopedHtmlProbe = htmlProbe && isInScope(htmlProbe.finalUrl, source, submittedScopePath) ? htmlProbe : null;
1459
- const platform = detectUrlPlatform(scopedHtmlProbe ?? initial);
1460
- const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : submittedScopePath;
2539
+ const platform = options.platform ?? detectUrlPlatform(scopedHtmlProbe ?? initial);
2540
+ let docusaurusHtmlProbe = platform === "docusaurus" ? scopedHtmlProbe : null;
2541
+ if (docusaurusHtmlProbe && !readDocusaurusSidebar(docusaurusHtmlProbe)) {
2542
+ const $ = load(docusaurusHtmlProbe.body);
2543
+ const docsHref = $("nav a[href]").toArray().map((element) => ({
2544
+ href: $(element).attr("href"),
2545
+ label: normalizedLinkLabel($(element).text())
2546
+ })).find((link) => /^docs(?:umentation)?$/i.test(link.label))?.href;
2547
+ if (docsHref) {
2548
+ try {
2549
+ const docsUrl = new URL(docsHref, docusaurusHtmlProbe.finalUrl);
2550
+ if (docsUrl.origin === source.origin && isInScope(docsUrl, source, submittedScopePath)) {
2551
+ const probe = await safeFetch(fetcher, docsUrl, "text/html,application/xhtml+xml");
2552
+ if (probe && probe.finalUrl.origin === source.origin && /html/i.test(probe.contentType)) {
2553
+ docusaurusHtmlProbe = probe;
2554
+ }
2555
+ }
2556
+ } catch {
2557
+ }
2558
+ }
2559
+ }
2560
+ const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
2561
+ const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : docusaurusDiscovery?.scopePath ?? submittedScopePath;
1461
2562
  const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
1462
2563
  const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
1463
2564
  const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
2565
+ if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
1464
2566
  const queue = [source];
1465
2567
  const queued = /* @__PURE__ */ new Set([candidateIdentity(source)]);
1466
2568
  const warnings = [];
1467
2569
  const fetchedSitemaps = /* @__PURE__ */ new Set();
2570
+ const docusaurusSidebars = docusaurusDiscovery?.sidebar ? [docusaurusDiscovery.sidebar] : [];
2571
+ const docusaurusRedirects = [];
1468
2572
  function enqueue(value, base) {
1469
2573
  if (queued.size >= MAX_DISCOVERED_URLS) return;
1470
2574
  const url = normalizeCandidate(value, base, source, scopePath);
1471
2575
  if (!url || queued.has(candidateIdentity(url))) return;
2576
+ if (platform === "docusaurus" && url.pathname.replace(/\/+$/, "") === `${scopePath}/search`) return;
2577
+ if (docusaurusDiscovery?.excludedVersionPrefixes.some((prefix) => {
2578
+ const path = url.pathname.replace(/\/+$/, "");
2579
+ return path === prefix || path.startsWith(`${prefix}/`);
2580
+ })) return;
1472
2581
  queued.add(candidateIdentity(url));
1473
2582
  queue.push(url);
1474
2583
  }
1475
- if (scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType)) {
1476
- for (const link of htmlNavigationLinks(scopedHtmlProbe)) enqueue(link, scopedHtmlProbe.finalUrl);
2584
+ const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
2585
+ if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
2586
+ for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
1477
2587
  }
1478
2588
  async function discoverDocumentLinks(document, depth = 0) {
1479
2589
  const links = sitemapLinks(document.body);
@@ -1540,6 +2650,7 @@ async function migrateUrl(options) {
1540
2650
  }
1541
2651
  }
1542
2652
  const pages = [];
2653
+ const openApiFragments = [];
1543
2654
  const seenIds = /* @__PURE__ */ new Set();
1544
2655
  const seenSources = /* @__PURE__ */ new Set();
1545
2656
  const visited = /* @__PURE__ */ new Set();
@@ -1591,20 +2702,26 @@ async function migrateUrl(options) {
1591
2702
  return { candidate, page: null, links: [], failure: false, budgetExceeded: true };
1592
2703
  }
1593
2704
  importedBytes += responseBytes;
1594
- const discoveredId = pageIdForUrl(document.finalUrl, scopePath) ?? pageIdForUrl(candidate, scopePath);
1595
- const id = discoveredId && canonicalLocalizedPageId(discoveredId) === sourceHomePageId ? "introduction" : discoveredId;
1596
- if (!id) return { candidate, page: null, links: [], failure: false };
2705
+ const discoveredNavigationId = platform === "docusaurus" ? docusaurusRouteIdForUrl(document.finalUrl, scopePath) ?? docusaurusRouteIdForUrl(candidate, scopePath) : pageIdForUrl(document.finalUrl, scopePath) ?? pageIdForUrl(candidate, scopePath);
2706
+ const navigationId = discoveredNavigationId && canonicalLocalizedPageId(discoveredNavigationId) === sourceHomePageId ? "introduction" : discoveredNavigationId;
2707
+ if (!navigationId) return { candidate, page: null, links: [], failure: false };
2708
+ const id = platform === "docusaurus" ? storageIdForDocusaurusRoute(navigationId) : navigationId;
1597
2709
  if (/(?:markdown|text\/plain)/i.test(document.contentType) || /\.mdx?$/i.test(document.finalUrl.pathname)) {
2710
+ const markdown = markdownPage(document, id);
2711
+ if (markdown.page) markdown.page.navigationId = navigationId;
1598
2712
  return {
1599
2713
  candidate,
1600
- page: markdownPage(document, id),
2714
+ page: markdown.page,
2715
+ openApiFragment: markdown.openApiFragment,
1601
2716
  links: markdownLinks(document.body),
1602
2717
  base: document.finalUrl,
1603
2718
  failure: false
1604
2719
  };
1605
2720
  }
1606
2721
  const extracted = htmlPage(document, id);
1607
- return { candidate, page: extracted.page, links: extracted.links, base: document.finalUrl, failure: false };
2722
+ if (extracted.page) extracted.page.navigationId = navigationId;
2723
+ const sidebar = platform === "docusaurus" ? readDocusaurusSidebar(document) : null;
2724
+ return { candidate, page: extracted.page, links: extracted.links, base: document.finalUrl, sidebar, failure: false };
1608
2725
  }));
1609
2726
  for (const result of results) {
1610
2727
  if (result.failure) {
@@ -1612,13 +2729,29 @@ async function migrateUrl(options) {
1612
2729
  continue;
1613
2730
  }
1614
2731
  if (result.budgetExceeded) continue;
1615
- if (result.page) result.page.id = canonicalLocalizedPageId(result.page.id);
2732
+ if (result.page && platform !== "docusaurus") {
2733
+ result.page.id = canonicalLocalizedPageId(result.page.id);
2734
+ result.page.navigationId = canonicalLocalizedPageId(result.page.navigationId);
2735
+ }
1616
2736
  const sourceIdentity = result.page ? candidateIdentity(new URL(result.page.source)) : null;
1617
2737
  if (result.page && sourceIdentity && !seenSources.has(sourceIdentity) && !seenIds.has(result.page.id) && pages.length < maxPages) {
1618
2738
  pages.push(result.page);
2739
+ if ("openApiFragment" in result && result.openApiFragment) {
2740
+ openApiFragments.push(result.openApiFragment);
2741
+ }
1619
2742
  seenIds.add(result.page.id);
1620
2743
  seenSources.add(sourceIdentity);
1621
2744
  }
2745
+ if (platform === "docusaurus" && result.page) {
2746
+ const candidateRoute = docusaurusRouteIdForUrl(result.candidate, scopePath);
2747
+ if (candidateRoute && candidateRoute !== result.page.navigationId) {
2748
+ docusaurusRedirects.push({
2749
+ source: candidateRoute === "introduction" ? "/" : `/${candidateRoute}`,
2750
+ destination: result.page.navigationId === "introduction" ? "/" : `/${result.page.navigationId}`
2751
+ });
2752
+ }
2753
+ }
2754
+ if ("sidebar" in result && result.sidebar) docusaurusSidebars.push(result.sidebar);
1622
2755
  for (const link of result.links) enqueue(link, result.base ?? result.candidate);
1623
2756
  }
1624
2757
  }
@@ -1631,7 +2764,7 @@ async function migrateUrl(options) {
1631
2764
  page.navigationId = navigationSegments.join("/") || "introduction";
1632
2765
  }
1633
2766
  }
1634
- const importedIds = new Set(pages.map((page) => page.id));
2767
+ const importedIds = new Set(pages.map((page) => page.navigationId));
1635
2768
  for (const page of pages) {
1636
2769
  page.body = rewriteInternalLinks(
1637
2770
  page.body,
@@ -1651,10 +2784,29 @@ async function migrateUrl(options) {
1651
2784
  ...entry,
1652
2785
  pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
1653
2786
  }));
1654
- const docsConfig = buildNavigationFromPages(pages, {
2787
+ const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
1655
2788
  topLevelTabs: platform === "mintlify",
1656
2789
  topLevelNavigation
1657
2790
  });
2791
+ if (docusaurusRedirects.length > 0) {
2792
+ docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
2793
+ `${redirect.source}:${redirect.destination}`,
2794
+ redirect
2795
+ ])).values()];
2796
+ }
2797
+ const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
2798
+ if (openApiAsset) {
2799
+ const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
2800
+ const apiTab = docsConfig.tabs.find((tab) => tab.groups?.some((group) => {
2801
+ const containsOperation = (pages2) => pages2.some((page) => {
2802
+ return typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages);
2803
+ });
2804
+ return containsOperation(group.pages);
2805
+ })) ?? docsConfig.tabs[0];
2806
+ if (apiTab) {
2807
+ apiTab.api = { source: "/openapi.yaml", navigation: false };
2808
+ }
2809
+ }
1658
2810
  const locales = [...new Set(pages.map((page) => page.locale).filter((value) => Boolean(value)))];
1659
2811
  if (locales.length > 0) {
1660
2812
  docsConfig.i18n = {
@@ -1670,7 +2822,7 @@ async function migrateUrl(options) {
1670
2822
  sourceKind: "url",
1671
2823
  platform,
1672
2824
  pages,
1673
- assets: [],
2825
+ assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
1674
2826
  docsConfig,
1675
2827
  warnings,
1676
2828
  stats: {