@thallylabs/migrate 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { spawn } from "child_process";
3
3
  import {
4
4
  existsSync as existsSync3,
5
- lstatSync as lstatSync2,
5
+ lstatSync as lstatSync3,
6
6
  readFileSync as readFileSync3,
7
7
  readdirSync
8
8
  } from "fs";
@@ -16,16 +16,29 @@ import { parse as parseYaml } from "yaml";
16
16
 
17
17
  // src/path.ts
18
18
  import { isAbsolute, relative, resolve, sep } from "path";
19
- var SAFE_SEGMENT = /[^a-z0-9._-]+/g;
20
- function slugifySegment(value) {
19
+ var SAFE_SEGMENT = /[^a-z0-9._-]+/gi;
20
+ function trimTrailingSlashes(value) {
21
+ let end = value.length;
22
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
23
+ return value.slice(0, end);
24
+ }
25
+ function trimEdgeSlashes(value) {
26
+ let start = 0;
27
+ let end = value.length;
28
+ while (start < end && value.charCodeAt(start) === 47) start++;
29
+ while (end > start && value.charCodeAt(end - 1) === 47) end--;
30
+ return value.slice(start, end);
31
+ }
32
+ function slugifySegment(value, preserveCase = false) {
21
33
  let decoded = value;
22
34
  try {
23
35
  decoded = decodeURIComponent(value);
24
36
  } catch {
25
37
  }
26
- return decoded.toLowerCase().replace(/\.(?:html?|mdx?)$/i, "").replace(SAFE_SEGMENT, "-").replace(/(^-|-$)/g, "");
38
+ const normalized = preserveCase ? decoded : decoded.toLowerCase();
39
+ return normalized.replace(/\.(?:html?|mdx?)$/i, "").replace(SAFE_SEGMENT, "-").replace(/(^-|-$)/g, "");
27
40
  }
28
- function pageIdFromReference(value) {
41
+ function pageIdFromReference(value, preserveCase = false) {
29
42
  const withoutQuery = value.split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^\/+/, "");
30
43
  if (!withoutQuery || withoutQuery.includes("\0")) return null;
31
44
  const rawSegments = withoutQuery.split("/").filter(Boolean);
@@ -35,7 +48,7 @@ function pageIdFromReference(value) {
35
48
  if (!/^(?:index|readme)$/i.test(last)) {
36
49
  baseSegments[baseSegments.length - 1] = last;
37
50
  }
38
- const segments = baseSegments.map(slugifySegment).filter(Boolean);
51
+ const segments = baseSegments.map((segment) => slugifySegment(segment, preserveCase)).filter(Boolean);
39
52
  return segments.join("/") || "introduction";
40
53
  }
41
54
  function resolveWithin(root, candidate) {
@@ -520,8 +533,19 @@ function projectDocusaurusNavigation(input) {
520
533
  };
521
534
  }
522
535
 
523
- // src/mdx.ts
536
+ // src/frontmatter.ts
524
537
  import matter from "gray-matter";
538
+ var FRONTMATTER_OPTIONS = {
539
+ engines: {
540
+ javascript: () => ({}),
541
+ js: () => ({})
542
+ }
543
+ };
544
+ function parseFrontmatter(raw) {
545
+ return matter(raw, FRONTMATTER_OPTIONS);
546
+ }
547
+
548
+ // src/mdx.ts
525
549
  function titleFromId(id) {
526
550
  const value = id.split("/").at(-1) ?? id;
527
551
  if (value === "introduction") return "Introduction";
@@ -574,10 +598,10 @@ function normalizeMdx(body) {
574
598
  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
599
  const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
576
600
  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>");
601
+ }).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(/<Warn(\s[^>]*)?>/g, "<Warning$1>").replace(/<\/Warn>/g, "</Warning>").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
602
  }
579
603
  function parseMarkdownPage(input) {
580
- const parsed = matter(input.raw);
604
+ const parsed = parseFrontmatter(input.raw);
581
605
  const fallbackIdentity = {
582
606
  id: input.id,
583
607
  navigationId: input.navigationId ?? input.id,
@@ -587,14 +611,23 @@ function parseMarkdownPage(input) {
587
611
  const body = normalizeMdx(parsed.content).trim();
588
612
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
589
613
  const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : titleFromId(identity.navigationId);
614
+ const navTitle = typeof parsed.data.sidebarTitle === "string" && parsed.data.sidebarTitle.trim() ? parsed.data.sidebarTitle.trim() : typeof parsed.data.navTitle === "string" && parsed.data.navTitle.trim() ? parsed.data.navTitle.trim() : void 0;
615
+ const badge = typeof parsed.data.tag === "string" && parsed.data.tag.trim() ? parsed.data.tag.trim() : typeof parsed.data.badge === "string" && parsed.data.badge.trim() ? parsed.data.badge.trim() : void 0;
616
+ const sourceMode = typeof parsed.data.mode === "string" ? parsed.data.mode : void 0;
617
+ const mode = sourceMode === "frame" ? "wide" : ["default", "wide", "custom", "center", "home"].includes(sourceMode ?? "") ? sourceMode : void 0;
590
618
  const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
591
619
  return {
592
620
  id: identity.id,
593
621
  navigationId: identity.navigationId,
594
622
  locale: identity.locale,
595
623
  title,
624
+ navTitle,
596
625
  description,
626
+ badge,
597
627
  keywords,
628
+ mode,
629
+ hidden: parsed.data.hidden === true ? true : void 0,
630
+ noindex: parsed.data.noindex === true || parsed.data.noindex === "true" ? true : void 0,
598
631
  openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
599
632
  body,
600
633
  source: input.source
@@ -602,8 +635,24 @@ function parseMarkdownPage(input) {
602
635
  }
603
636
 
604
637
  // src/navigation.ts
605
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
638
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
606
639
  import { dirname, extname as extname2, relative as relative2 } from "path";
640
+ var MAX_MINTLIFY_CONFIG_BYTES = 2e6;
641
+ function addMintlifyDirectoryRedirects(config, pages) {
642
+ const pageIds = new Set(pages.filter((page) => !page.locale).map((page) => page.navigationId));
643
+ const redirects = [...config.redirects ?? []];
644
+ const redirectSources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
645
+ for (const page of pages) {
646
+ if (page.locale || !/(?:^|\/)(?:overview|introduction)$/.test(page.navigationId)) continue;
647
+ const parent = page.navigationId.replace(/\/(?:overview|introduction)$/, "");
648
+ if (!parent || pageIds.has(parent)) continue;
649
+ const source = `/${parent}`;
650
+ if (redirectSources.has(source)) continue;
651
+ redirects.push({ source, destination: `/${page.navigationId}`, permanent: false });
652
+ redirectSources.add(source);
653
+ }
654
+ return redirects.length > 0 ? { ...config, redirects } : config;
655
+ }
607
656
  var LANGUAGE_LABELS = {
608
657
  ar: "Arabic",
609
658
  de: "German",
@@ -625,6 +674,15 @@ var LANGUAGE_LABELS = {
625
674
  function objectValue2(value) {
626
675
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
627
676
  }
677
+ function projectedHref(value) {
678
+ if (typeof value !== "string") return void 0;
679
+ const href = value.trim();
680
+ if (!href || /[\u0000-\u001f\u007f]/.test(href)) return void 0;
681
+ if (/^(?:javascript|data|vbscript|file):/i.test(href)) return void 0;
682
+ const scheme = href.match(/^([a-z][a-z0-9+.-]*):/i)?.[1].toLowerCase();
683
+ if (scheme && !["http", "https", "mailto", "tel"].includes(scheme)) return void 0;
684
+ return href;
685
+ }
628
686
  function labelFor(value, fallback) {
629
687
  for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "label", "name", "title"]) {
630
688
  if (typeof value[key] === "string" && value[key]) return String(value[key]);
@@ -638,11 +696,27 @@ function jsonPointer(root, pointer) {
638
696
  if (!pointer || pointer === "#") return root;
639
697
  const tokens = pointer.replace(/^#\/?/, "").split("/").filter(Boolean);
640
698
  return tokens.reduce((current, token) => {
699
+ if (Array.isArray(current)) {
700
+ const index = Number(token);
701
+ return Number.isSafeInteger(index) && index >= 0 ? current[index] : void 0;
702
+ }
641
703
  const object = objectValue2(current);
642
704
  if (!object) return void 0;
643
705
  return object[token.replace(/~1/g, "/").replace(/~0/g, "~")];
644
706
  }, root);
645
707
  }
708
+ function readMintlifyJson(path, repositoryRoot) {
709
+ if (!existsSync2(path) || !lstatSync2(path).isFile()) {
710
+ throw new Error(`Mintlify config reference is not a regular file: ${relative2(repositoryRoot, path)}`);
711
+ }
712
+ const realPath = realpathSync(path);
713
+ const realRoot = realpathSync(repositoryRoot);
714
+ resolveWithin(realRoot, relative2(realRoot, realPath));
715
+ if (lstatSync2(realPath).size > MAX_MINTLIFY_CONFIG_BYTES) {
716
+ throw new Error(`Mintlify config reference exceeded ${MAX_MINTLIFY_CONFIG_BYTES / 1e6} MB: ${relative2(repositoryRoot, path)}`);
717
+ }
718
+ return JSON.parse(readFileSync2(realPath, "utf8"));
719
+ }
646
720
  function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
647
721
  if (Array.isArray(value)) {
648
722
  return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
@@ -657,11 +731,17 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
657
731
  const stackKey = `${referencedFile}#${pointer}`;
658
732
  if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
659
733
  stack.add(stackKey);
660
- const referencedRoot = JSON.parse(readFileSync2(referencedFile, "utf8"));
734
+ const referencedRoot = readMintlifyJson(referencedFile, repositoryRoot);
661
735
  const referenced = jsonPointer(referencedRoot, pointer ? `#${pointer}` : "");
662
736
  const resolved = resolveJsonReferences(referenced, referencedFile, repositoryRoot, stack);
663
737
  stack.delete(stackKey);
664
- return resolved;
738
+ const siblings = Object.fromEntries(
739
+ Object.entries(object).filter(([key]) => key !== "$ref").map(([key, entry]) => [
740
+ key,
741
+ resolveJsonReferences(entry, currentFile, repositoryRoot, stack)
742
+ ])
743
+ );
744
+ return objectValue2(resolved) ? { ...resolved, ...siblings } : resolved;
665
745
  }
666
746
  return Object.fromEntries(
667
747
  Object.entries(object).map(([key, entry]) => [
@@ -673,18 +753,33 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
673
753
  function readMintlifyConfig(repositoryRoot) {
674
754
  const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(existsSync2);
675
755
  if (!configPath) return null;
676
- const raw = JSON.parse(readFileSync2(configPath, "utf8"));
756
+ const raw = readMintlifyJson(configPath, repositoryRoot);
677
757
  return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
678
758
  }
679
- function normalizePageRef(value) {
759
+ function normalizePageRef(value, pathPrefix = "") {
680
760
  if (/^(?:https?:)?\/\//i.test(value) || value.startsWith("#")) return null;
681
- const ref = localReference(value).replace(/^\/+/, "").replace(/\.(?:mdx?|rst|txt)$/i, "");
682
- return pageIdFromReference(ref);
761
+ let ref = localReference(value).split("?", 1)[0].replace(/^\/+/, "");
762
+ const prefix = trimEdgeSlashes(pathPrefix);
763
+ if (prefix && (ref === prefix || ref.startsWith(`${prefix}/`))) {
764
+ ref = ref === prefix ? "introduction" : ref.slice(prefix.length + 1);
765
+ }
766
+ ref = ref.replace(/\.(?:mdx?|rst|txt)$/i, "");
767
+ return pageIdFromReference(ref, true);
768
+ }
769
+ function warnOnce(context, key, message) {
770
+ if (context.warningKeys.has(key)) return;
771
+ context.warningKeys.add(key);
772
+ context.warnings.push({ code: "unsupported-config", message });
773
+ }
774
+ function iconName(value) {
775
+ if (typeof value === "string" && value) return value;
776
+ const icon = objectValue2(value);
777
+ return typeof icon?.name === "string" && icon.name ? icon.name : void 0;
683
778
  }
684
779
  function registerReference(value, context) {
685
780
  const localePrefix = context.locale ? `${context.locale}/` : "";
686
781
  const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
687
- const navigationId = normalizePageRef(localizedValue);
782
+ const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
688
783
  if (!navigationId) return null;
689
784
  const key = `${context.locale ?? ""}:${value}`;
690
785
  if (!context.seenReferences.has(key)) {
@@ -698,6 +793,17 @@ function convertPage(value, context) {
698
793
  const object = objectValue2(value);
699
794
  if (!object) return null;
700
795
  if (typeof object.page === "string") return registerReference(object.page, context);
796
+ const href = projectedHref(object.href);
797
+ if (href && !object.pages && !object.groups) {
798
+ const page = registerReference(href, context);
799
+ if (page) return page;
800
+ warnOnce(
801
+ context,
802
+ "external-page-link",
803
+ "External links nested inside a Mintlify sidebar cannot be represented as Thally pages and were omitted."
804
+ );
805
+ return null;
806
+ }
701
807
  const pages = Array.isArray(object.pages) ? object.pages : [];
702
808
  if ("group" in object || pages.length > 0) {
703
809
  const children = [];
@@ -712,7 +818,8 @@ function convertPage(value, context) {
712
818
  if (children.length === 0) return null;
713
819
  return {
714
820
  group: labelFor(object, "Documentation"),
715
- ...typeof object.icon === "string" ? { icon: object.icon } : {},
821
+ ...iconName(object.icon) ? { icon: iconName(object.icon) } : {},
822
+ ...object.hidden === true ? { hidden: true } : {},
716
823
  pages: children
717
824
  };
718
825
  }
@@ -739,12 +846,20 @@ function convertContainerToTabs(containerValue, context, fallbackTab) {
739
846
  const object = objectValue2(value);
740
847
  if (!object) return [];
741
848
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
742
- if (typeof object.href === "string" && !object.pages && !object.groups) {
743
- return [{ tab, href: object.href }];
849
+ const href = projectedHref(object.href);
850
+ if (href && !object.pages && !object.groups) {
851
+ return [{ tab, href, ...object.hidden === true ? { hidden: true } : {} }];
744
852
  }
745
853
  const nested = convertContainerToTabs(object, context, tab);
746
854
  if (nested.length > 0) {
747
- if (nested.length === 1) return [{ ...nested[0], tab }];
855
+ if (nested.length === 1) {
856
+ return [{
857
+ ...nested[0],
858
+ tab,
859
+ ...href ? { href } : {},
860
+ ...object.hidden === true ? { hidden: true } : {}
861
+ }];
862
+ }
748
863
  return nested.map((item) => ({ ...item, tab: `${tab}: ${item.tab}` }));
749
864
  }
750
865
  return [];
@@ -757,38 +872,168 @@ function convertContainerToTabs(containerValue, context, fallbackTab) {
757
872
  ];
758
873
  if (typeof container.root === "string") rawPages.unshift(container.root);
759
874
  const groups = convertGroups(rawPages, context, "Overview");
760
- return groups.length > 0 ? [{ tab: fallbackTab, groups }] : [];
875
+ return groups.length > 0 ? [{
876
+ tab: fallbackTab,
877
+ groups,
878
+ ...projectedHref(container.href) ? { href: projectedHref(container.href) } : {},
879
+ ...container.hidden === true ? { hidden: true } : {}
880
+ }] : [];
881
+ }
882
+ function projectedTheme(value) {
883
+ if (value === "maple") return "maple";
884
+ if (["aspen", "luma", "sequoia"].includes(String(value))) return "sharp";
885
+ if (["almond", "palm"].includes(String(value))) return "minimal";
886
+ return typeof value === "string" ? "default" : void 0;
887
+ }
888
+ function projectedNavbar(value) {
889
+ const navbar = objectValue2(value);
890
+ if (!navbar) return void 0;
891
+ const links = Array.isArray(navbar.links) ? navbar.links.flatMap((entry) => {
892
+ const link = objectValue2(entry);
893
+ const href = projectedHref(link?.href);
894
+ if (!link || !href) return [];
895
+ const label = typeof link.label === "string" ? link.label : typeof link.name === "string" ? link.name : "";
896
+ if (!label) return [];
897
+ return [{ label, href, ...link.type === "github" ? { type: "github" } : {} }];
898
+ }) : [];
899
+ const primaryValue = objectValue2(navbar.primary);
900
+ const primaryHref = projectedHref(primaryValue?.href);
901
+ const primary = primaryValue && primaryHref ? {
902
+ label: typeof primaryValue.label === "string" ? primaryValue.label : primaryValue.type === "github" ? "GitHub" : "Get started",
903
+ href: primaryHref
904
+ } : void 0;
905
+ return links.length > 0 || primary ? { ...links.length > 0 ? { links } : {}, ...primary ? { primary } : {} } : void 0;
761
906
  }
762
- function projectMintlifyNavigation(config) {
907
+ function projectedGlobalNavigationLinks(value) {
908
+ const global = objectValue2(value);
909
+ if (!global) return [];
910
+ const links = [];
911
+ const visit = (entry) => {
912
+ if (Array.isArray(entry)) {
913
+ entry.forEach(visit);
914
+ return;
915
+ }
916
+ const item = objectValue2(entry);
917
+ if (!item) return;
918
+ const href = projectedHref(item.href);
919
+ if (href) {
920
+ const label = labelFor(item, "");
921
+ if (label) links.push({ label, href });
922
+ }
923
+ for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus"]) {
924
+ if (Array.isArray(item[key])) visit(item[key]);
925
+ }
926
+ };
927
+ visit(global);
928
+ return links;
929
+ }
930
+ function projectedFooter(value) {
931
+ const footer = objectValue2(value);
932
+ if (!footer) return void 0;
933
+ const socials = objectValue2(footer.socials);
934
+ const socialLinks = socials ? Object.fromEntries(Object.entries(socials).flatMap(([key, value2]) => {
935
+ const href = projectedHref(value2);
936
+ return href ? [[key, href]] : [];
937
+ })) : void 0;
938
+ const links = Array.isArray(footer.links) ? footer.links.flatMap((entry) => {
939
+ const column = objectValue2(entry);
940
+ if (!column || !Array.isArray(column.items)) return [];
941
+ const heading = typeof column.heading === "string" ? column.heading : typeof column.header === "string" ? column.header : "";
942
+ const items = column.items.flatMap((item) => {
943
+ const link = objectValue2(item);
944
+ const href = projectedHref(link?.href);
945
+ return link && typeof link.label === "string" && href ? [{ label: link.label, href }] : [];
946
+ });
947
+ return heading && items.length > 0 ? [{ heading, items }] : [];
948
+ }) : [];
949
+ return socialLinks && Object.keys(socialLinks).length > 0 || links.length > 0 ? { ...socialLinks && Object.keys(socialLinks).length > 0 ? { socials: socialLinks } : {}, ...links.length > 0 ? { links } : {} } : void 0;
950
+ }
951
+ function projectedFont(value) {
952
+ if (typeof value === "string" && value) return { family: value };
953
+ const font = objectValue2(value);
954
+ if (!font) return void 0;
955
+ const family = typeof font.family === "string" ? font.family : typeof font.name === "string" ? font.name : void 0;
956
+ if (!family) return void 0;
957
+ const weight = Array.isArray(font.weight) ? font.weight.map(String) : Array.isArray(font.weights) ? font.weights.map(String) : void 0;
958
+ return { family, ...weight && weight.length > 0 ? { weight } : {} };
959
+ }
960
+ function projectedCompatibleConfig(config) {
961
+ const banner = objectValue2(config.banner);
962
+ const bannerContent = banner && (typeof banner.content === "string" || objectValue2(banner.content)) ? banner.content : void 0;
963
+ const bannerType = banner && ["info", "warning", "critical"].includes(String(banner.type ?? banner.variant)) ? String(banner.type ?? banner.variant) : void 0;
964
+ const bannerColor = objectValue2(banner?.color);
965
+ const bodyFont = projectedFont(objectValue2(config.fonts)?.body ?? config.fonts);
966
+ const headingFont = projectedFont(objectValue2(config.fonts)?.heading);
967
+ const feedback = objectValue2(config.feedback);
968
+ const seo = objectValue2(config.seo);
969
+ const navbar = projectedNavbar(config.navbar);
970
+ const globalLinks = projectedGlobalNavigationLinks(objectValue2(config.navigation)?.global);
971
+ const navbarLinks = [...new Map(
972
+ [...navbar?.links ?? [], ...globalLinks].map((link) => [`${link.label}:${link.href}`, link])
973
+ ).values()];
974
+ const projectedNavigation = navbarLinks.length > 0 || navbar?.primary ? { ...navbarLinks.length > 0 ? { links: navbarLinks } : {}, ...navbar?.primary ? { primary: navbar.primary } : {} } : void 0;
975
+ return {
976
+ ...projectedTheme(config.theme) ? { theme: projectedTheme(config.theme) } : {},
977
+ ...bannerContent ? {
978
+ banner: {
979
+ content: bannerContent,
980
+ ...banner?.dismissible === false ? { dismissible: false } : {},
981
+ ...typeof banner?.id === "string" ? { id: banner.id } : {},
982
+ ...typeof banner?.revision === "string" ? { revision: banner.revision } : {},
983
+ ...bannerType ? { type: bannerType } : {},
984
+ ...bannerColor ? { color: {
985
+ ...typeof bannerColor.light === "string" ? { light: bannerColor.light } : {},
986
+ ...typeof bannerColor.dark === "string" ? { dark: bannerColor.dark } : {}
987
+ } } : {}
988
+ }
989
+ } : {},
990
+ ...projectedNavigation ? { navbar: projectedNavigation } : {},
991
+ ...projectedFooter(config.footer) ? { footer: projectedFooter(config.footer) } : {},
992
+ ...bodyFont || headingFont ? { fonts: { ...bodyFont ? { body: bodyFont } : {}, ...headingFont ? { heading: headingFont } : {} } } : {},
993
+ ...seo?.indexing === "all" ? { seo: { indexing: "all" } } : {},
994
+ ...typeof feedback?.thumbsRating === "boolean" ? { feedback: { thumbsRating: feedback.thumbsRating } } : {}
995
+ };
996
+ }
997
+ function projectMintlifyNavigation(config, options = {}) {
763
998
  const warnings = [];
764
999
  const references = [];
765
1000
  const seenReferences = /* @__PURE__ */ new Set();
1001
+ const warningKeys = /* @__PURE__ */ new Set();
766
1002
  const navigation = objectValue2(config.navigation) ?? config;
767
1003
  const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
768
1004
  let tabs = [];
769
1005
  let i18n;
770
1006
  if (languages.length > 0) {
771
- const defaultLanguage = languages.find((entry) => entry.language === "en") ?? languages[0];
1007
+ const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
772
1008
  const defaultLocale = String(defaultLanguage.language ?? "en");
773
1009
  const locales = languages.map((entry) => {
774
1010
  const code = String(entry.language ?? entry.locale ?? "en");
775
- return { code, label: LANGUAGE_LABELS[code] ?? code.toUpperCase() };
1011
+ let label = LANGUAGE_LABELS[code];
1012
+ try {
1013
+ label ??= new Intl.DisplayNames(["en"], { type: "language" }).of(code) ?? void 0;
1014
+ } catch {
1015
+ }
1016
+ return { code, label: label ?? code.toUpperCase() };
776
1017
  });
777
1018
  i18n = { defaultLocale, locales };
778
1019
  for (const language of languages) {
779
1020
  const locale = String(language.language ?? language.locale ?? defaultLocale);
780
1021
  const context = {
781
1022
  locale,
1023
+ pathPrefix: options.pathPrefix,
782
1024
  references,
783
- seenReferences
1025
+ seenReferences,
1026
+ warnings,
1027
+ warningKeys
784
1028
  };
785
1029
  const languageTabs = convertContainerToTabs(language, context, "Documentation");
786
1030
  if (language === defaultLanguage) tabs = languageTabs;
787
1031
  }
788
1032
  } else {
789
- tabs = convertContainerToTabs(navigation, { references, seenReferences }, "Documentation");
1033
+ const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
1034
+ tabs = convertContainerToTabs(navigation, context, "Documentation");
790
1035
  if (tabs.length === 0 && Array.isArray(config.navigation)) {
791
- const groups = convertGroups(config.navigation, { references, seenReferences }, "Overview");
1036
+ const groups = convertGroups(config.navigation, context, "Overview");
792
1037
  if (groups.length > 0) tabs = [{ tab: "Documentation", groups }];
793
1038
  }
794
1039
  }
@@ -798,20 +1043,25 @@ function projectMintlifyNavigation(config) {
798
1043
  message: "Mintlify navigation could not be projected; generated navigation will be used."
799
1044
  });
800
1045
  }
801
- if (!tabs.some((tab) => tab.tab.toLowerCase() === "changelog")) {
802
- tabs.push({ tab: "Changelog", href: "/changelog" });
803
- }
804
1046
  const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
805
1047
  const redirect = objectValue2(value);
806
1048
  if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
1049
+ const source = redirect.source.trim();
1050
+ const destination = redirect.destination.trim();
1051
+ if (!source.startsWith("/") || !destination.startsWith("/") || source.startsWith("//") || destination.startsWith("//")) return [];
807
1052
  return [{
808
- source: redirect.source,
809
- destination: redirect.destination,
1053
+ source,
1054
+ destination,
810
1055
  ...typeof redirect.permanent === "boolean" ? { permanent: redirect.permanent } : {}
811
1056
  }];
812
1057
  }) : [];
813
1058
  return {
814
- docsConfig: { tabs, ...i18n ? { i18n } : {}, ...redirects.length > 0 ? { redirects } : {} },
1059
+ docsConfig: {
1060
+ tabs,
1061
+ ...projectedCompatibleConfig(config),
1062
+ ...i18n ? { i18n } : {},
1063
+ ...redirects.length > 0 ? { redirects } : {}
1064
+ },
815
1065
  pageReferences: references,
816
1066
  warnings
817
1067
  };
@@ -980,12 +1230,50 @@ var DOCUSAURUS_CONFIG_FILENAMES = [
980
1230
  function hasDocusaurusConfig(directory) {
981
1231
  return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
982
1232
  const path = resolveWithin(directory, filename);
983
- return existsSync3(path) && lstatSync2(path).isFile();
1233
+ return existsSync3(path) && lstatSync3(path).isFile();
984
1234
  });
985
1235
  }
1236
+ function hasMintlifyConfig(directory) {
1237
+ return ["docs.json", "mint.json"].some((filename) => {
1238
+ const path = resolveWithin(directory, filename);
1239
+ if (!existsSync3(path) || !lstatSync3(path).isFile()) return false;
1240
+ if (filename === "mint.json") return true;
1241
+ try {
1242
+ const config = JSON.parse(readFileSync3(path, "utf8"));
1243
+ return String(config.$schema ?? "").includes("mintlify") || "navigation" in config;
1244
+ } catch {
1245
+ return false;
1246
+ }
1247
+ });
1248
+ }
1249
+ function findMintlifyProjectRoot(repositoryDir, docsDir) {
1250
+ if (docsDir !== void 0) {
1251
+ let candidate = trimTrailingSlashes(docsDir);
1252
+ while (true) {
1253
+ const path = resolveWithin(repositoryDir, candidate || ".");
1254
+ if (hasMintlifyConfig(path)) return path;
1255
+ if (!candidate) break;
1256
+ const parent = dirname2(candidate);
1257
+ candidate = parent === "." ? "" : parent;
1258
+ }
1259
+ }
1260
+ const queue = [{ directory: repositoryDir, depth: 0 }];
1261
+ let visited = 0;
1262
+ while (queue.length > 0 && visited < 500) {
1263
+ const current = queue.shift();
1264
+ visited++;
1265
+ if (hasMintlifyConfig(current.directory)) return current.directory;
1266
+ if (current.depth >= 4) continue;
1267
+ for (const entry of readdirSync(current.directory, { withFileTypes: true })) {
1268
+ if (!entry.isDirectory() || entry.isSymbolicLink() || IGNORED_DIRECTORIES.has(entry.name)) continue;
1269
+ queue.push({ directory: resolveWithin(current.directory, entry.name), depth: current.depth + 1 });
1270
+ }
1271
+ }
1272
+ return null;
1273
+ }
986
1274
  function findDocusaurusProjectRoot(repositoryDir, docsDir) {
987
1275
  if (docsDir !== void 0) {
988
- let candidate = docsDir.replace(/\/+$/, "");
1276
+ let candidate = trimTrailingSlashes(docsDir);
989
1277
  while (true) {
990
1278
  const path = resolveWithin(repositoryDir, candidate || ".");
991
1279
  if (hasDocusaurusConfig(path)) return path;
@@ -1012,14 +1300,14 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
1012
1300
  return null;
1013
1301
  }
1014
1302
  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 "";
1303
+ const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync3(candidate) && lstatSync3(candidate).isFile());
1304
+ if (!path || lstatSync3(path).size > 1e6) return "";
1017
1305
  return readFileSync3(path, "utf8");
1018
1306
  }
1019
1307
  function primaryDocusaurusDocsDirectory(projectRoot) {
1020
1308
  const source = readDocusaurusConfigSource(projectRoot);
1021
1309
  const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
1022
- return configured?.replace(/^\.\//, "").replace(/\/+$/, "") || "docs";
1310
+ return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
1023
1311
  }
1024
1312
  function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1025
1313
  const source = readDocusaurusConfigSource(projectRoot);
@@ -1027,12 +1315,12 @@ function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1027
1315
  const plugins = [];
1028
1316
  const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
1029
1317
  for (const match of source.matchAll(matcher)) {
1030
- const localPath = match[2].replace(/^\.\//, "").replace(/\/+$/, "");
1031
- const routePrefix = match[4].replace(/^\/+|\/+$/g, "");
1318
+ const localPath = trimTrailingSlashes(match[2].replace(/^\.\//, ""));
1319
+ const routePrefix = trimEdgeSlashes(match[4]);
1032
1320
  if (!localPath || !routePrefix) continue;
1033
1321
  const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
1034
1322
  const absolute = resolveWithin(repositoryDir, docsDir);
1035
- if (!existsSync3(absolute) || !lstatSync2(absolute).isDirectory()) continue;
1323
+ if (!existsSync3(absolute) || !lstatSync3(absolute).isDirectory()) continue;
1036
1324
  plugins.push({ docsDir, routePrefix });
1037
1325
  }
1038
1326
  return plugins;
@@ -1089,13 +1377,13 @@ async function cloneGitHubRepository(source, targetDir) {
1089
1377
  });
1090
1378
  });
1091
1379
  }
1092
- function detectRepositoryPlatform(repositoryDir) {
1093
- if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
1094
- const docsJson = resolveWithin(repositoryDir, "docs.json");
1380
+ function detectRepositoryPlatform(repositoryDir, docsDir) {
1381
+ if (findMintlifyProjectRoot(repositoryDir, docsDir)) return "mintlify";
1382
+ const selectedRoot = docsDir === void 0 ? repositoryDir : resolveWithin(repositoryDir, docsDir || ".");
1383
+ const docsJson = resolveWithin(selectedRoot, "docs.json");
1095
1384
  if (existsSync3(docsJson)) {
1096
1385
  try {
1097
1386
  const config = JSON.parse(readFileSync3(docsJson, "utf8"));
1098
- if (String(config.$schema ?? "").includes("mintlify") || "navigation" in config) return "mintlify";
1099
1387
  if (Array.isArray(config.tabs)) return "thally";
1100
1388
  } catch {
1101
1389
  }
@@ -1121,7 +1409,7 @@ function containsMarkdown(directory, depth = 0) {
1121
1409
  function detectRepositoryDocsDir(repositoryDir) {
1122
1410
  for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
1123
1411
  const path = resolveWithin(repositoryDir, candidate);
1124
- if (existsSync3(path) && lstatSync2(path).isDirectory() && containsMarkdown(path)) return candidate;
1412
+ if (existsSync3(path) && lstatSync3(path).isDirectory() && containsMarkdown(path)) return candidate;
1125
1413
  }
1126
1414
  return "";
1127
1415
  }
@@ -1143,30 +1431,135 @@ function scanFiles(root) {
1143
1431
  function normalizedReferenceKey(value) {
1144
1432
  return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "").replace(/\/(?:index|readme)$/i, "").replace(/^(?:index|readme)$/i, "") || "introduction";
1145
1433
  }
1434
+ function exactReferenceKey(value) {
1435
+ return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
1436
+ }
1146
1437
  function findOpenApi(files) {
1147
1438
  return files.find((file) => OPENAPI_FILENAMES.has(basename(file.relativePath).toLowerCase())) ?? null;
1148
1439
  }
1440
+ function findConfiguredMintlifyOpenApi(config, files) {
1441
+ const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
1442
+ const configured = typeof api?.openapi === "string" ? [api.openapi] : Array.isArray(api?.openapi) ? api.openapi.filter((value) => typeof value === "string") : [];
1443
+ for (const reference of configured) {
1444
+ if (/^(?:https?:)?\/\//i.test(reference)) continue;
1445
+ const key = reference.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/");
1446
+ const match = files.find((file) => file.relativePath === key);
1447
+ if (match) return match;
1448
+ }
1449
+ return null;
1450
+ }
1149
1451
  function withoutFrontmatter(value) {
1150
1452
  return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
1151
1453
  }
1152
- function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot) {
1454
+ function staticSnippetProperties(attributes) {
1455
+ const properties = /* @__PURE__ */ new Map();
1456
+ const matcher = /\b([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*"([^"]*)"\s*\}|\{\s*'([^']*)'\s*\})/g;
1457
+ for (const match of attributes.matchAll(matcher)) {
1458
+ properties.set(match[1], match[2] ?? match[3] ?? match[4] ?? match[5] ?? "");
1459
+ }
1460
+ return properties;
1461
+ }
1462
+ function interpolateSnippet(snippet, attributes) {
1463
+ const properties = staticSnippetProperties(attributes);
1464
+ if (properties.size === 0) return snippet;
1465
+ let fence = "";
1466
+ return snippet.split("\n").map((line) => {
1467
+ const marker = line.match(/^\s*(`{3,}|~{3,})/)?.[1];
1468
+ if (marker) {
1469
+ if (!fence) fence = marker[0];
1470
+ else if (marker[0] === fence) fence = "";
1471
+ return line;
1472
+ }
1473
+ if (fence) return line;
1474
+ return line.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (original, name) => {
1475
+ return properties.get(name) ?? original;
1476
+ });
1477
+ }).join("\n");
1478
+ }
1479
+ function resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot) {
1480
+ const repositoryRelative = sourcePath.startsWith("@site/") ? relative3(repositoryRoot, resolveWithin(siteRoot, sourcePath.slice("@site/".length))).replace(/\\/g, "/") : sourcePath.startsWith("/") ? relative3(repositoryRoot, resolveWithin(siteRoot, sourcePath.replace(/^\/+/, ""))).replace(/\\/g, "/") : relative3(repositoryRoot, resolvePath(dirname2(currentFile), sourcePath)).replace(/\\/g, "/");
1481
+ const candidate = resolveWithin(repositoryRoot, repositoryRelative);
1482
+ resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
1483
+ return candidate;
1484
+ }
1485
+ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
1486
+ const aliases = /* @__PURE__ */ new Map();
1487
+ const matcher = /^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm;
1488
+ for (const file of files) {
1489
+ const segments = file.relativePath.split("/");
1490
+ if (!segments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()))) continue;
1491
+ const basenameWithoutExtension = basename(file.relativePath).replace(/\.mdx?$/i, "");
1492
+ const conventionalAlias = basenameWithoutExtension.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1493
+ if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
1494
+ }
1495
+ for (const file of files) {
1496
+ if (![".md", ".mdx"].includes(extname3(file.relativePath).toLowerCase())) continue;
1497
+ const raw = readFileSync3(file.absolutePath, "utf8");
1498
+ for (const match of raw.matchAll(matcher)) {
1499
+ try {
1500
+ const candidate = resolveSnippetPath(match[2], file.absolutePath, repositoryRoot, siteRoot);
1501
+ if (existsSync3(candidate) && lstatSync3(candidate).isFile()) {
1502
+ aliases.set(match[1], candidate);
1503
+ }
1504
+ } catch {
1505
+ }
1506
+ }
1507
+ }
1508
+ return aliases;
1509
+ }
1510
+ function repositoryAssetHref(value, currentFile, siteRoot) {
1511
+ const isBracketed = value.startsWith("<") && value.endsWith(">");
1512
+ const raw = isBracketed ? value.slice(1, -1) : value;
1513
+ if (!raw || raw.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(raw)) return null;
1514
+ const suffixIndex = raw.search(/[?#]/);
1515
+ const pathname = suffixIndex >= 0 ? raw.slice(0, suffixIndex) : raw;
1516
+ const suffix = suffixIndex >= 0 ? raw.slice(suffixIndex) : "";
1517
+ let decodedPath;
1518
+ try {
1519
+ decodedPath = decodeURIComponent(pathname);
1520
+ } catch {
1521
+ return null;
1522
+ }
1523
+ if (!ASSET_EXTENSIONS.has(extname3(decodedPath).toLowerCase())) return null;
1524
+ try {
1525
+ const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(dirname2(currentFile), decodedPath);
1526
+ const siteRelative = relative3(siteRoot, candidate).replace(/\\/g, "/");
1527
+ resolveWithin(siteRoot, siteRelative);
1528
+ if (!existsSync3(candidate) || !lstatSync3(candidate).isFile()) return null;
1529
+ const normalized = normalizeAssetPath(siteRelative);
1530
+ if (!normalized) return null;
1531
+ const rewritten = `/${normalized}${suffix}`;
1532
+ return isBracketed ? `<${rewritten}>` : rewritten;
1533
+ } catch {
1534
+ return null;
1535
+ }
1536
+ }
1537
+ function rewriteRepositoryAssetLinks(body, currentFile, siteRoot) {
1538
+ return body.replace(/(!?\[[^\]]*\]\()(<[^>]+>|[^)\s]+)([^)]*\))/g, (original, opening, destination, closing) => {
1539
+ const rewritten = repositoryAssetHref(destination, currentFile, siteRoot);
1540
+ return rewritten ? `${opening}${rewritten}${closing}` : original;
1541
+ }).replace(/\b(src|img|image|href)=(['"])([^'"]+)\2/g, (original, property, quote, destination) => {
1542
+ const rewritten = repositoryAssetHref(destination, currentFile, siteRoot);
1543
+ return rewritten ? `${property}=${quote}${rewritten}${quote}` : original;
1544
+ });
1545
+ }
1546
+ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map()) {
1153
1547
  if (depth >= 8) return raw;
1154
1548
  const snippets = /* @__PURE__ */ new Map();
1155
1549
  const withoutImports = raw.replace(
1156
1550
  /^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm,
1157
1551
  (_statement, componentName, sourcePath) => {
1158
1552
  try {
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);
1161
- resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
1162
- if (!existsSync3(candidate) || !lstatSync2(candidate).isFile()) throw new Error("file not found");
1553
+ const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
1554
+ if (!existsSync3(candidate) || !lstatSync3(candidate).isFile()) throw new Error("file not found");
1163
1555
  const nested = inlineMdxSnippets(
1164
1556
  withoutFrontmatter(readFileSync3(candidate, "utf8")),
1165
1557
  candidate,
1166
1558
  repositoryRoot,
1167
1559
  warnings,
1168
1560
  depth + 1,
1169
- siteRoot
1561
+ siteRoot,
1562
+ globalAliases
1170
1563
  );
1171
1564
  snippets.set(componentName, nested);
1172
1565
  return "";
@@ -1181,26 +1574,40 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
1181
1574
  }
1182
1575
  }
1183
1576
  );
1577
+ for (const [componentName, candidate] of globalAliases) {
1578
+ if (snippets.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
1579
+ const nested = inlineMdxSnippets(
1580
+ withoutFrontmatter(readFileSync3(candidate, "utf8")),
1581
+ candidate,
1582
+ repositoryRoot,
1583
+ warnings,
1584
+ depth + 1,
1585
+ siteRoot,
1586
+ globalAliases
1587
+ );
1588
+ snippets.set(componentName, nested);
1589
+ }
1184
1590
  let result = withoutImports;
1185
1591
  for (const [componentName, snippet] of snippets) {
1186
- result = result.replace(new RegExp(`<${componentName}(?:\\s[^>]*)?\\s*/>`, "g"), snippet).replace(new RegExp(`<${componentName}(?:\\s[^>]*)?>(?:[\\s\\S]*?)<\\/${componentName}>`, "g"), snippet);
1592
+ result = result.replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)\\s*/>`, "g"), (_tag, attributes) => {
1593
+ return interpolateSnippet(snippet, attributes);
1594
+ }).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>(?:[\\s\\S]*?)<\\/${componentName}>`, "g"), (_tag, attributes) => {
1595
+ return interpolateSnippet(snippet, attributes);
1596
+ });
1187
1597
  }
1188
1598
  return result;
1189
1599
  }
1190
1600
  function injectOpenApi(config, filename) {
1191
- const tabs = config.tabs.filter((tab) => !tab.tab.toLowerCase().includes("api"));
1192
- const apiTab = {
1193
- tab: "API Reference",
1194
- api: { source: `/${filename}` }
1195
- };
1196
- const changelog = tabs.findIndex((tab) => tab.tab.toLowerCase() === "changelog");
1197
- if (changelog >= 0) tabs.splice(changelog, 0, apiTab);
1198
- else tabs.push(apiTab);
1601
+ const tabs = config.tabs.map((tab) => ({ ...tab }));
1602
+ const apiTab = tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
1603
+ if (apiTab) apiTab.api = { source: `/${filename}`, navigation: false };
1604
+ else tabs.push({ tab: "API Reference", api: { source: `/${filename}` } });
1199
1605
  return { ...config, tabs };
1200
1606
  }
1201
1607
  function migrateRepository(options) {
1202
1608
  const repositoryDir = options.repositoryDir;
1203
- const platform = options.platform ?? detectRepositoryPlatform(repositoryDir);
1609
+ const platform = options.platform ?? detectRepositoryPlatform(repositoryDir, options.docsDir);
1610
+ const mintlifyProjectRoot = platform === "mintlify" ? findMintlifyProjectRoot(repositoryDir, options.docsDir) : null;
1204
1611
  const docusaurusProjectRoot = platform === "docusaurus" ? findDocusaurusProjectRoot(repositoryDir, options.docsDir) : null;
1205
1612
  const configuredDocsDir = (() => {
1206
1613
  if (options.docsDir !== void 0) {
@@ -1215,23 +1622,30 @@ function migrateRepository(options) {
1215
1622
  const projectRelative = relative3(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
1216
1623
  return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
1217
1624
  }
1625
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1626
+ return relative3(repositoryDir, mintlifyProjectRoot).replace(/\\/g, "/");
1627
+ }
1218
1628
  return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
1219
1629
  })();
1220
1630
  const warnings = [];
1221
1631
  let docsConfig = { tabs: [] };
1222
1632
  const referenceMap = /* @__PURE__ */ new Map();
1633
+ const exactReferenceMap = /* @__PURE__ */ new Map();
1223
1634
  const referenceOrder = /* @__PURE__ */ new Map();
1224
1635
  let docusaurusSidebars = null;
1636
+ let mintlifyConfig = null;
1225
1637
  if (platform === "mintlify") {
1226
1638
  try {
1227
- const config = readMintlifyConfig(repositoryDir);
1639
+ const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
1228
1640
  if (config) {
1641
+ mintlifyConfig = config;
1229
1642
  const projected = projectMintlifyNavigation(config);
1230
1643
  docsConfig = projected.docsConfig;
1231
1644
  warnings.push(...projected.warnings);
1232
1645
  for (const [index, reference] of projected.pageReferences.entries()) {
1233
1646
  const key = normalizedReferenceKey(reference.ref);
1234
1647
  referenceMap.set(key, reference);
1648
+ exactReferenceMap.set(exactReferenceKey(reference.ref), reference);
1235
1649
  referenceOrder.set(key, index);
1236
1650
  }
1237
1651
  }
@@ -1253,7 +1667,7 @@ function migrateRepository(options) {
1253
1667
  }
1254
1668
  }
1255
1669
  const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
1256
- if (!existsSync3(contentRoot) || !lstatSync2(contentRoot).isDirectory()) {
1670
+ if (!existsSync3(contentRoot) || !lstatSync3(contentRoot).isDirectory()) {
1257
1671
  throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
1258
1672
  }
1259
1673
  const files = scanFiles(contentRoot);
@@ -1269,6 +1683,7 @@ function migrateRepository(options) {
1269
1683
  const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
1270
1684
  return leftOrder - rightOrder || left.relativePath.localeCompare(right.relativePath);
1271
1685
  });
1686
+ const snippetAliases = platform === "mintlify" && mintlifyProjectRoot ? globalSnippetAliases(files, repositoryDir, mintlifyProjectRoot) : /* @__PURE__ */ new Map();
1272
1687
  for (const file of pageFiles) {
1273
1688
  if (!isDocumentationExtension(file.relativePath)) continue;
1274
1689
  const sourceSegments = file.relativePath.split("/");
@@ -1277,7 +1692,7 @@ function migrateRepository(options) {
1277
1692
  continue;
1278
1693
  }
1279
1694
  const rootFilename = !file.relativePath.includes("/") ? basename(file.relativePath).toLowerCase() : "";
1280
- if (!configuredDocsDir && REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
1695
+ if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename) && !exactReferenceMap.has(exactReferenceKey(file.relativePath))) {
1281
1696
  skipped++;
1282
1697
  continue;
1283
1698
  }
@@ -1286,21 +1701,21 @@ function migrateRepository(options) {
1286
1701
  warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
1287
1702
  continue;
1288
1703
  }
1289
- const size = lstatSync2(file.absolutePath).size;
1704
+ const size = lstatSync3(file.absolutePath).size;
1290
1705
  if (size > MAX_PAGE_BYTES) {
1291
1706
  skipped++;
1292
1707
  warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
1293
1708
  continue;
1294
1709
  }
1295
1710
  const key = normalizedReferenceKey(file.relativePath);
1296
- const referenced = referenceMap.get(key);
1711
+ const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
1297
1712
  let locale = referenced?.locale;
1298
- let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath);
1713
+ let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
1299
1714
  if (!referenced && localeConfig) {
1300
1715
  const prefix = file.relativePath.split("/", 1)[0];
1301
1716
  if (localeConfig.locales.some((entry) => entry.code === prefix)) {
1302
1717
  locale = prefix;
1303
- navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1));
1718
+ navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1), platform === "mintlify");
1304
1719
  }
1305
1720
  }
1306
1721
  if (!navigationId) {
@@ -1316,7 +1731,8 @@ function migrateRepository(options) {
1316
1731
  repositoryDir,
1317
1732
  warnings,
1318
1733
  0,
1319
- docusaurusProjectRoot ?? repositoryDir
1734
+ mintlifyProjectRoot ?? docusaurusProjectRoot ?? repositoryDir,
1735
+ snippetAliases
1320
1736
  );
1321
1737
  let docusaurusDescriptor;
1322
1738
  const page = parseMarkdownPage({
@@ -1324,11 +1740,11 @@ function migrateRepository(options) {
1324
1740
  navigationId,
1325
1741
  ...locale ? { locale } : {},
1326
1742
  raw,
1327
- source: `${options.sourceUrl}#${file.relativePath}`,
1743
+ source: `${options.sourceUrl}#${relative3(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
1328
1744
  ...platform === "docusaurus" ? {
1329
1745
  resolveIdentity: (frontmatter, fallback) => {
1330
1746
  const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
1331
- const routePrefix = options.docusaurusRoutePrefix?.replace(/^\/+|\/+$/g, "");
1747
+ const routePrefix = options.docusaurusRoutePrefix === void 0 ? void 0 : trimEdgeSlashes(options.docusaurusRoutePrefix);
1332
1748
  if (!routePrefix) {
1333
1749
  docusaurusDescriptor = resolved.descriptor;
1334
1750
  return resolved.identity;
@@ -1351,6 +1767,9 @@ function migrateRepository(options) {
1351
1767
  skipped++;
1352
1768
  continue;
1353
1769
  }
1770
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1771
+ page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot);
1772
+ }
1354
1773
  if (seenPageIds.has(page.id)) {
1355
1774
  skipped++;
1356
1775
  warnings.push({ code: "collision", message: `Multiple source files map to ${page.id}; the first file was kept.`, source: file.relativePath });
@@ -1371,7 +1790,7 @@ function migrateRepository(options) {
1371
1790
  const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
1372
1791
  const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
1373
1792
  const root = resolveWithin(docusaurusAssetRoot, directory);
1374
- if (!existsSync3(root) || !lstatSync2(root).isDirectory()) return [];
1793
+ if (!existsSync3(root) || !lstatSync3(root).isDirectory()) return [];
1375
1794
  return scanFiles(root).map((file) => ({
1376
1795
  ...file,
1377
1796
  relativePath: `${directory}/${file.relativePath}`
@@ -1380,8 +1799,9 @@ function migrateRepository(options) {
1380
1799
  let totalAssetBytes = 0;
1381
1800
  for (const file of [...files, ...repositoryAssets]) {
1382
1801
  const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
1383
- if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
1384
- const size = lstatSync2(file.absolutePath).size;
1802
+ if (!ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
1803
+ if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
1804
+ const size = lstatSync3(file.absolutePath).size;
1385
1805
  if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
1386
1806
  warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
1387
1807
  continue;
@@ -1419,7 +1839,7 @@ function migrateRepository(options) {
1419
1839
  }
1420
1840
  }
1421
1841
  if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
1422
- const openApi = findOpenApi(files);
1842
+ const openApi = findConfiguredMintlifyOpenApi(mintlifyConfig, files) ?? findOpenApi(files);
1423
1843
  if (openApi) {
1424
1844
  const filename = basename(openApi.relativePath);
1425
1845
  if (!assets.some((asset) => asset.path === filename)) {
@@ -1427,6 +1847,7 @@ function migrateRepository(options) {
1427
1847
  }
1428
1848
  docsConfig = injectOpenApi(docsConfig, filename);
1429
1849
  }
1850
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
1430
1851
  if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
1431
1852
  for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
1432
1853
  if (plugin.docsDir === configuredDocsDir) continue;
@@ -1468,6 +1889,12 @@ function migrateRepository(options) {
1468
1889
  pages,
1469
1890
  assets,
1470
1891
  docsConfig,
1892
+ ...mintlifyConfig ? {
1893
+ site: {
1894
+ ...typeof mintlifyConfig.name === "string" ? { name: mintlifyConfig.name } : {},
1895
+ ...typeof mintlifyConfig.description === "string" ? { description: mintlifyConfig.description } : {}
1896
+ }
1897
+ } : {},
1471
1898
  warnings,
1472
1899
  stats: { discovered, imported: pages.length, skipped }
1473
1900
  };
@@ -1481,8 +1908,13 @@ function renderPage(bundle, page) {
1481
1908
  return [
1482
1909
  "---",
1483
1910
  `title: ${yamlString(page.title)}`,
1911
+ page.navTitle ? `navTitle: ${yamlString(page.navTitle)}` : null,
1484
1912
  `description: ${yamlString(page.description)}`,
1913
+ page.badge ? `badge: ${yamlString(page.badge)}` : null,
1485
1914
  page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
1915
+ page.mode ? `mode: ${yamlString(page.mode)}` : null,
1916
+ page.hidden ? "hidden: true" : null,
1917
+ page.noindex ? "noindex: true" : null,
1486
1918
  page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
1487
1919
  bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
1488
1920
  "---",
@@ -1580,16 +2012,28 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
1580
2012
  "Color",
1581
2013
  "Column",
1582
2014
  "Columns",
2015
+ "ContentPanel",
1583
2016
  "Danger",
1584
2017
  "Error",
1585
2018
  "Expandable",
2019
+ "Agent",
2020
+ "AgentPrompt",
2021
+ "BannerPreview",
2022
+ "Embed",
1586
2023
  "File",
1587
2024
  "Folder",
1588
2025
  "Frame",
2026
+ "GitHub",
2027
+ "Github",
1589
2028
  "Hero",
2029
+ "Human",
1590
2030
  "Icon",
1591
2031
  "Info",
2032
+ "InlinePanel",
2033
+ "InlineRequestExample",
2034
+ "InlineResponseExample",
1592
2035
  "Latex",
2036
+ "LegacyView",
1593
2037
  "Mermaid",
1594
2038
  "Note",
1595
2039
  "Panel",
@@ -1604,6 +2048,9 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
1604
2048
  "Steps",
1605
2049
  "Tab",
1606
2050
  "Tabs",
2051
+ "Terminal",
2052
+ "TerminalInput",
2053
+ "TerminalOutput",
1607
2054
  "Tile",
1608
2055
  "TileGroup",
1609
2056
  "Tip",
@@ -1611,6 +2058,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
1611
2058
  "Tree",
1612
2059
  "Update",
1613
2060
  "View",
2061
+ "Visibility",
1614
2062
  "Warning"
1615
2063
  ]);
1616
2064
  var LOCALE_CODES = /* @__PURE__ */ new Set([
@@ -1761,7 +2209,7 @@ function pageIdForUrl(url, scopePath) {
1761
2209
  else if (path === scopePath) path = "";
1762
2210
  else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
1763
2211
  }
1764
- return pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
2212
+ return pageIdFromReference(path.replace(/^\/+/, "") || "introduction", true);
1765
2213
  }
1766
2214
  function canonicalLocalizedPageId(id) {
1767
2215
  const [segment, ...rest] = id.split("/");
@@ -1783,6 +2231,72 @@ function detectUrlPlatform(document) {
1783
2231
  if (value.includes("starlight")) return "starlight";
1784
2232
  return "generic";
1785
2233
  }
2234
+ function jsonObjectAfterKey(value, key) {
2235
+ let cursor = 0;
2236
+ const marker = `"${key}":`;
2237
+ while (cursor < value.length) {
2238
+ const markerIndex = value.indexOf(marker, cursor);
2239
+ if (markerIndex < 0) return null;
2240
+ let start = markerIndex + marker.length;
2241
+ while (/\s/.test(value[start] ?? "")) start += 1;
2242
+ if (value[start] !== "{") {
2243
+ cursor = start + 1;
2244
+ continue;
2245
+ }
2246
+ let depth = 0;
2247
+ let isQuoted = false;
2248
+ let isEscaped = false;
2249
+ for (let index = start; index < value.length; index += 1) {
2250
+ const character = value[index];
2251
+ if (isQuoted) {
2252
+ if (isEscaped) isEscaped = false;
2253
+ else if (character === "\\") isEscaped = true;
2254
+ else if (character === '"') isQuoted = false;
2255
+ continue;
2256
+ }
2257
+ if (character === '"') {
2258
+ isQuoted = true;
2259
+ continue;
2260
+ }
2261
+ if (character === "{") depth += 1;
2262
+ if (character === "}") {
2263
+ depth -= 1;
2264
+ if (depth !== 0) continue;
2265
+ try {
2266
+ const parsed = JSON.parse(value.slice(start, index + 1));
2267
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2268
+ } catch {
2269
+ break;
2270
+ }
2271
+ }
2272
+ }
2273
+ cursor = start + 1;
2274
+ }
2275
+ return null;
2276
+ }
2277
+ function embeddedMintlifyConfig(body) {
2278
+ const $ = load(body);
2279
+ const flightPayloads = [];
2280
+ $("script").each((_index, element) => {
2281
+ const script = $(element).text();
2282
+ for (const match of script.matchAll(/self\.__next_f\.push\(\[1,("(?:\\.|[^"\\])*")\]\)/g)) {
2283
+ try {
2284
+ const payload2 = JSON.parse(match[1]);
2285
+ if (typeof payload2 === "string") flightPayloads.push(payload2);
2286
+ } catch {
2287
+ }
2288
+ }
2289
+ });
2290
+ if (flightPayloads.length === 0) return null;
2291
+ const payload = flightPayloads.join("\n");
2292
+ const docsConfig = jsonObjectAfterKey(payload, "docsConfig");
2293
+ const scopedNavigation = jsonObjectAfterKey(payload, "scopedNav");
2294
+ if (!docsConfig && !scopedNavigation) return null;
2295
+ return {
2296
+ ...docsConfig ?? {},
2297
+ ...scopedNavigation ? { navigation: scopedNavigation } : {}
2298
+ };
2299
+ }
1786
2300
  function plainDescription(value) {
1787
2301
  return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_>#-]/g, "").replace(/\s+/g, " ").trim().slice(0, 240);
1788
2302
  }
@@ -2021,7 +2535,12 @@ function remoteMarkdownMetadata(body) {
2021
2535
  return {
2022
2536
  content: body.slice(contentStart),
2023
2537
  title: scalar("title"),
2024
- description: scalar("description")
2538
+ description: scalar("description"),
2539
+ sidebarTitle: scalar("sidebarTitle"),
2540
+ tag: scalar("tag"),
2541
+ mode: scalar("mode"),
2542
+ hidden: scalar("hidden") === "true" ? true : void 0,
2543
+ noindex: scalar("noindex") === "true" ? true : void 0
2025
2544
  };
2026
2545
  }
2027
2546
  function stripMintlifyDocumentationIndex(body) {
@@ -2081,6 +2600,11 @@ function markdownPage(document, id) {
2081
2600
  "---",
2082
2601
  `title: ${JSON.stringify(title)}`,
2083
2602
  ...description ? [`description: ${JSON.stringify(description)}`] : [],
2603
+ ...parsed.sidebarTitle ? [`sidebarTitle: ${JSON.stringify(parsed.sidebarTitle)}`] : [],
2604
+ ...parsed.tag ? [`tag: ${JSON.stringify(parsed.tag)}`] : [],
2605
+ ...parsed.mode ? [`mode: ${JSON.stringify(parsed.mode)}`] : [],
2606
+ ...parsed.hidden ? ["hidden: true"] : [],
2607
+ ...parsed.noindex ? ["noindex: true"] : [],
2084
2608
  ...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
2085
2609
  "---",
2086
2610
  "",
@@ -2463,7 +2987,7 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2463
2987
  const collectCandidates = (selector) => {
2464
2988
  const candidates2 = [];
2465
2989
  $(selector).each((_containerIndex, container) => {
2466
- const entries = [];
2990
+ const entries2 = [];
2467
2991
  const seenSections = /* @__PURE__ */ new Set();
2468
2992
  let anchorCount = 0;
2469
2993
  $(container).find("a[href]").each((_index, element) => {
@@ -2479,22 +3003,40 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2479
3003
  const section = targetId.split("/", 1)[0] || "introduction";
2480
3004
  if (!pageId || seenSections.has(section)) return;
2481
3005
  seenSections.add(section);
2482
- entries.push({ section, label, pageId });
3006
+ entries2.push({ section, label, pageId });
2483
3007
  } catch {
2484
3008
  }
2485
3009
  });
2486
- if (entries.length > 1) {
2487
- candidates2.push({ entries, anchorCount });
3010
+ if (entries2.length > 1) {
3011
+ candidates2.push({ entries: entries2, anchorCount });
2488
3012
  }
2489
3013
  });
2490
3014
  return candidates2;
2491
3015
  };
2492
3016
  const tabCandidates = collectCandidates(".nav-tabs");
2493
3017
  const candidates = tabCandidates.length > 0 ? tabCandidates : collectCandidates('nav, [role="navigation"]');
2494
- return candidates.sort((left, right) => {
3018
+ const entries = candidates.sort((left, right) => {
2495
3019
  const densityDifference = right.entries.length / right.anchorCount - left.entries.length / left.anchorCount;
2496
3020
  return densityDifference || right.entries.length - left.entries.length;
2497
3021
  })[0]?.entries ?? [];
3022
+ const normalizedLabels = entries.map((entry) => entry.label.trim().toLowerCase());
3023
+ if (entries.length > 12 || new Set(normalizedLabels).size !== normalizedLabels.length) return [];
3024
+ return entries;
3025
+ }
3026
+ function remapNavigationHome(config, sourceHomePageId) {
3027
+ if (!sourceHomePageId || sourceHomePageId === "introduction") return config;
3028
+ const remapGroups = (groups) => groups.map((group) => ({
3029
+ ...group,
3030
+ pages: group.pages.map((page) => typeof page === "string" ? page === sourceHomePageId ? "introduction" : page : remapGroups([page])[0])
3031
+ }));
3032
+ return {
3033
+ ...config,
3034
+ tabs: config.tabs.map((tab) => ({
3035
+ ...tab,
3036
+ ...tab.href === `/${sourceHomePageId}` ? { href: "/" } : {},
3037
+ ...tab.groups ? { groups: remapGroups(tab.groups) } : {}
3038
+ }))
3039
+ };
2498
3040
  }
2499
3041
  function markdownLinks(body) {
2500
3042
  return [...body.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s+['"][^'"]*['"])?\)/g)].map((match) => match[1]);
@@ -2559,8 +3101,13 @@ async function migrateUrl(options) {
2559
3101
  }
2560
3102
  const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
2561
3103
  const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : docusaurusDiscovery?.scopePath ?? submittedScopePath;
2562
- const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
2563
- const sourceHomePageId = sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
3104
+ const sourceMintlifyConfig = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? embeddedMintlifyConfig(scopedHtmlProbe.body) : null;
3105
+ const sourceMintlifyProjection = sourceMintlifyConfig ? projectMintlifyNavigation(sourceMintlifyConfig, { pathPrefix: scopePath }) : null;
3106
+ const hasStructuredMintlifyNavigation = Boolean(
3107
+ sourceMintlifyProjection?.docsConfig.tabs.length && sourceMintlifyProjection.pageReferences.length
3108
+ );
3109
+ const sourceTopLevelNavigation = platform === "mintlify" && !hasStructuredMintlifyNavigation && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
3110
+ const sourceHomePageId = sourceMintlifyProjection?.pageReferences[0]?.navigationId ?? sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
2564
3111
  const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
2565
3112
  if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
2566
3113
  const queue = [source];
@@ -2581,6 +3128,13 @@ async function migrateUrl(options) {
2581
3128
  queued.add(candidateIdentity(url));
2582
3129
  queue.push(url);
2583
3130
  }
3131
+ for (const reference of sourceMintlifyProjection?.pageReferences ?? []) {
3132
+ let href = reference.ref;
3133
+ if (scopePath !== "/" && href.startsWith("/") && href !== scopePath && !href.startsWith(`${scopePath}/`)) {
3134
+ href = `${scopePath}${href}`;
3135
+ }
3136
+ enqueue(href, source);
3137
+ }
2584
3138
  const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
2585
3139
  if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
2586
3140
  for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
@@ -2784,10 +3338,11 @@ async function migrateUrl(options) {
2784
3338
  ...entry,
2785
3339
  pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
2786
3340
  }));
2787
- const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
3341
+ let docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : hasStructuredMintlifyNavigation && sourceMintlifyProjection ? remapNavigationHome(sourceMintlifyProjection.docsConfig, sourceHomePageId) : buildNavigationFromPages(pages, {
2788
3342
  topLevelTabs: platform === "mintlify",
2789
3343
  topLevelNavigation
2790
3344
  });
3345
+ if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
2791
3346
  if (docusaurusRedirects.length > 0) {
2792
3347
  docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
2793
3348
  `${redirect.source}:${redirect.destination}`,
@@ -2817,6 +3372,7 @@ async function migrateUrl(options) {
2817
3372
  ]
2818
3373
  };
2819
3374
  }
3375
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
2820
3376
  return {
2821
3377
  sourceUrl: source.toString(),
2822
3378
  sourceKind: "url",
@@ -2824,6 +3380,12 @@ async function migrateUrl(options) {
2824
3380
  pages,
2825
3381
  assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
2826
3382
  docsConfig,
3383
+ ...sourceMintlifyConfig ? {
3384
+ site: {
3385
+ ...typeof sourceMintlifyConfig.name === "string" ? { name: sourceMintlifyConfig.name } : {},
3386
+ ...typeof sourceMintlifyConfig.description === "string" ? { description: sourceMintlifyConfig.description } : {}
3387
+ }
3388
+ } : {},
2827
3389
  warnings,
2828
3390
  stats: {
2829
3391
  discovered: visited.size,
@@ -2833,6 +3395,7 @@ async function migrateUrl(options) {
2833
3395
  };
2834
3396
  }
2835
3397
  export {
3398
+ addMintlifyDirectoryRedirects,
2836
3399
  buildNavigationFromPages,
2837
3400
  cloneGitHubRepository,
2838
3401
  defaultMigrationFetcher,