@thallylabs/migrate 0.2.1 → 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) {
@@ -585,7 +598,7 @@ function normalizeMdx(body) {
585
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) => {
586
599
  const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
587
600
  return `<Tab title="${title.replace(/"/g, "&quot;")}">`;
588
- }).replace(/<\/TabItem>/g, "</Tab>").replace(/<Link\b([^>]*)\bto=(?:"([^"]*)"|'([^']*)')([^>]*)>/g, (_match, before, doubleQuoted, singleQuoted, after) => `<a${before}href="${doubleQuoted ?? singleQuoted}"${after}>`).replace(/<\/Link>/g, "</a>").replace(/<(?:DocCardList|TOCInline)\b[^>]*\/>/g, "").replace(/<!--([\s\S]*?)-->/g, (_match, content) => `{/*${content}*/}`).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
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>");
589
602
  }
590
603
  function parseMarkdownPage(input) {
591
604
  const parsed = parseFrontmatter(input.raw);
@@ -598,14 +611,23 @@ function parseMarkdownPage(input) {
598
611
  const body = normalizeMdx(parsed.content).trim();
599
612
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
600
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;
601
618
  const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : firstParagraph(body);
602
619
  return {
603
620
  id: identity.id,
604
621
  navigationId: identity.navigationId,
605
622
  locale: identity.locale,
606
623
  title,
624
+ navTitle,
607
625
  description,
626
+ badge,
608
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,
609
631
  openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
610
632
  body,
611
633
  source: input.source
@@ -613,8 +635,24 @@ function parseMarkdownPage(input) {
613
635
  }
614
636
 
615
637
  // src/navigation.ts
616
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
638
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
617
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
+ }
618
656
  var LANGUAGE_LABELS = {
619
657
  ar: "Arabic",
620
658
  de: "German",
@@ -636,6 +674,15 @@ var LANGUAGE_LABELS = {
636
674
  function objectValue2(value) {
637
675
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
638
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
+ }
639
686
  function labelFor(value, fallback) {
640
687
  for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "label", "name", "title"]) {
641
688
  if (typeof value[key] === "string" && value[key]) return String(value[key]);
@@ -649,11 +696,27 @@ function jsonPointer(root, pointer) {
649
696
  if (!pointer || pointer === "#") return root;
650
697
  const tokens = pointer.replace(/^#\/?/, "").split("/").filter(Boolean);
651
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
+ }
652
703
  const object = objectValue2(current);
653
704
  if (!object) return void 0;
654
705
  return object[token.replace(/~1/g, "/").replace(/~0/g, "~")];
655
706
  }, root);
656
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
+ }
657
720
  function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
658
721
  if (Array.isArray(value)) {
659
722
  return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
@@ -668,11 +731,17 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
668
731
  const stackKey = `${referencedFile}#${pointer}`;
669
732
  if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
670
733
  stack.add(stackKey);
671
- const referencedRoot = JSON.parse(readFileSync2(referencedFile, "utf8"));
734
+ const referencedRoot = readMintlifyJson(referencedFile, repositoryRoot);
672
735
  const referenced = jsonPointer(referencedRoot, pointer ? `#${pointer}` : "");
673
736
  const resolved = resolveJsonReferences(referenced, referencedFile, repositoryRoot, stack);
674
737
  stack.delete(stackKey);
675
- 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;
676
745
  }
677
746
  return Object.fromEntries(
678
747
  Object.entries(object).map(([key, entry]) => [
@@ -684,18 +753,33 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
684
753
  function readMintlifyConfig(repositoryRoot) {
685
754
  const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(existsSync2);
686
755
  if (!configPath) return null;
687
- const raw = JSON.parse(readFileSync2(configPath, "utf8"));
756
+ const raw = readMintlifyJson(configPath, repositoryRoot);
688
757
  return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
689
758
  }
690
- function normalizePageRef(value) {
759
+ function normalizePageRef(value, pathPrefix = "") {
691
760
  if (/^(?:https?:)?\/\//i.test(value) || value.startsWith("#")) return null;
692
- const ref = localReference(value).replace(/^\/+/, "").replace(/\.(?:mdx?|rst|txt)$/i, "");
693
- 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;
694
778
  }
695
779
  function registerReference(value, context) {
696
780
  const localePrefix = context.locale ? `${context.locale}/` : "";
697
781
  const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
698
- const navigationId = normalizePageRef(localizedValue);
782
+ const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
699
783
  if (!navigationId) return null;
700
784
  const key = `${context.locale ?? ""}:${value}`;
701
785
  if (!context.seenReferences.has(key)) {
@@ -709,6 +793,17 @@ function convertPage(value, context) {
709
793
  const object = objectValue2(value);
710
794
  if (!object) return null;
711
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
+ }
712
807
  const pages = Array.isArray(object.pages) ? object.pages : [];
713
808
  if ("group" in object || pages.length > 0) {
714
809
  const children = [];
@@ -723,7 +818,8 @@ function convertPage(value, context) {
723
818
  if (children.length === 0) return null;
724
819
  return {
725
820
  group: labelFor(object, "Documentation"),
726
- ...typeof object.icon === "string" ? { icon: object.icon } : {},
821
+ ...iconName(object.icon) ? { icon: iconName(object.icon) } : {},
822
+ ...object.hidden === true ? { hidden: true } : {},
727
823
  pages: children
728
824
  };
729
825
  }
@@ -750,12 +846,20 @@ function convertContainerToTabs(containerValue, context, fallbackTab) {
750
846
  const object = objectValue2(value);
751
847
  if (!object) return [];
752
848
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
753
- if (typeof object.href === "string" && !object.pages && !object.groups) {
754
- 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 } : {} }];
755
852
  }
756
853
  const nested = convertContainerToTabs(object, context, tab);
757
854
  if (nested.length > 0) {
758
- 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
+ }
759
863
  return nested.map((item) => ({ ...item, tab: `${tab}: ${item.tab}` }));
760
864
  }
761
865
  return [];
@@ -768,38 +872,168 @@ function convertContainerToTabs(containerValue, context, fallbackTab) {
768
872
  ];
769
873
  if (typeof container.root === "string") rawPages.unshift(container.root);
770
874
  const groups = convertGroups(rawPages, context, "Overview");
771
- 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;
906
+ }
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
+ };
772
996
  }
773
- function projectMintlifyNavigation(config) {
997
+ function projectMintlifyNavigation(config, options = {}) {
774
998
  const warnings = [];
775
999
  const references = [];
776
1000
  const seenReferences = /* @__PURE__ */ new Set();
1001
+ const warningKeys = /* @__PURE__ */ new Set();
777
1002
  const navigation = objectValue2(config.navigation) ?? config;
778
1003
  const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
779
1004
  let tabs = [];
780
1005
  let i18n;
781
1006
  if (languages.length > 0) {
782
- const defaultLanguage = languages.find((entry) => entry.language === "en") ?? languages[0];
1007
+ const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
783
1008
  const defaultLocale = String(defaultLanguage.language ?? "en");
784
1009
  const locales = languages.map((entry) => {
785
1010
  const code = String(entry.language ?? entry.locale ?? "en");
786
- 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() };
787
1017
  });
788
1018
  i18n = { defaultLocale, locales };
789
1019
  for (const language of languages) {
790
1020
  const locale = String(language.language ?? language.locale ?? defaultLocale);
791
1021
  const context = {
792
1022
  locale,
1023
+ pathPrefix: options.pathPrefix,
793
1024
  references,
794
- seenReferences
1025
+ seenReferences,
1026
+ warnings,
1027
+ warningKeys
795
1028
  };
796
1029
  const languageTabs = convertContainerToTabs(language, context, "Documentation");
797
1030
  if (language === defaultLanguage) tabs = languageTabs;
798
1031
  }
799
1032
  } else {
800
- tabs = convertContainerToTabs(navigation, { references, seenReferences }, "Documentation");
1033
+ const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
1034
+ tabs = convertContainerToTabs(navigation, context, "Documentation");
801
1035
  if (tabs.length === 0 && Array.isArray(config.navigation)) {
802
- const groups = convertGroups(config.navigation, { references, seenReferences }, "Overview");
1036
+ const groups = convertGroups(config.navigation, context, "Overview");
803
1037
  if (groups.length > 0) tabs = [{ tab: "Documentation", groups }];
804
1038
  }
805
1039
  }
@@ -809,20 +1043,25 @@ function projectMintlifyNavigation(config) {
809
1043
  message: "Mintlify navigation could not be projected; generated navigation will be used."
810
1044
  });
811
1045
  }
812
- if (!tabs.some((tab) => tab.tab.toLowerCase() === "changelog")) {
813
- tabs.push({ tab: "Changelog", href: "/changelog" });
814
- }
815
1046
  const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
816
1047
  const redirect = objectValue2(value);
817
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 [];
818
1052
  return [{
819
- source: redirect.source,
820
- destination: redirect.destination,
1053
+ source,
1054
+ destination,
821
1055
  ...typeof redirect.permanent === "boolean" ? { permanent: redirect.permanent } : {}
822
1056
  }];
823
1057
  }) : [];
824
1058
  return {
825
- docsConfig: { tabs, ...i18n ? { i18n } : {}, ...redirects.length > 0 ? { redirects } : {} },
1059
+ docsConfig: {
1060
+ tabs,
1061
+ ...projectedCompatibleConfig(config),
1062
+ ...i18n ? { i18n } : {},
1063
+ ...redirects.length > 0 ? { redirects } : {}
1064
+ },
826
1065
  pageReferences: references,
827
1066
  warnings
828
1067
  };
@@ -991,12 +1230,50 @@ var DOCUSAURUS_CONFIG_FILENAMES = [
991
1230
  function hasDocusaurusConfig(directory) {
992
1231
  return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
993
1232
  const path = resolveWithin(directory, filename);
994
- return existsSync3(path) && lstatSync2(path).isFile();
1233
+ return existsSync3(path) && lstatSync3(path).isFile();
1234
+ });
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
+ }
995
1247
  });
996
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
+ }
997
1274
  function findDocusaurusProjectRoot(repositoryDir, docsDir) {
998
1275
  if (docsDir !== void 0) {
999
- let candidate = docsDir.replace(/\/+$/, "");
1276
+ let candidate = trimTrailingSlashes(docsDir);
1000
1277
  while (true) {
1001
1278
  const path = resolveWithin(repositoryDir, candidate || ".");
1002
1279
  if (hasDocusaurusConfig(path)) return path;
@@ -1023,14 +1300,14 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
1023
1300
  return null;
1024
1301
  }
1025
1302
  function readDocusaurusConfigSource(projectRoot) {
1026
- const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync3(candidate) && lstatSync2(candidate).isFile());
1027
- if (!path || lstatSync2(path).size > 1e6) return "";
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 "";
1028
1305
  return readFileSync3(path, "utf8");
1029
1306
  }
1030
1307
  function primaryDocusaurusDocsDirectory(projectRoot) {
1031
1308
  const source = readDocusaurusConfigSource(projectRoot);
1032
1309
  const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
1033
- return configured?.replace(/^\.\//, "").replace(/\/+$/, "") || "docs";
1310
+ return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
1034
1311
  }
1035
1312
  function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1036
1313
  const source = readDocusaurusConfigSource(projectRoot);
@@ -1038,12 +1315,12 @@ function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1038
1315
  const plugins = [];
1039
1316
  const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
1040
1317
  for (const match of source.matchAll(matcher)) {
1041
- const localPath = match[2].replace(/^\.\//, "").replace(/\/+$/, "");
1042
- const routePrefix = match[4].replace(/^\/+|\/+$/g, "");
1318
+ const localPath = trimTrailingSlashes(match[2].replace(/^\.\//, ""));
1319
+ const routePrefix = trimEdgeSlashes(match[4]);
1043
1320
  if (!localPath || !routePrefix) continue;
1044
1321
  const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
1045
1322
  const absolute = resolveWithin(repositoryDir, docsDir);
1046
- if (!existsSync3(absolute) || !lstatSync2(absolute).isDirectory()) continue;
1323
+ if (!existsSync3(absolute) || !lstatSync3(absolute).isDirectory()) continue;
1047
1324
  plugins.push({ docsDir, routePrefix });
1048
1325
  }
1049
1326
  return plugins;
@@ -1100,13 +1377,13 @@ async function cloneGitHubRepository(source, targetDir) {
1100
1377
  });
1101
1378
  });
1102
1379
  }
1103
- function detectRepositoryPlatform(repositoryDir) {
1104
- if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
1105
- 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");
1106
1384
  if (existsSync3(docsJson)) {
1107
1385
  try {
1108
1386
  const config = JSON.parse(readFileSync3(docsJson, "utf8"));
1109
- if (String(config.$schema ?? "").includes("mintlify") || "navigation" in config) return "mintlify";
1110
1387
  if (Array.isArray(config.tabs)) return "thally";
1111
1388
  } catch {
1112
1389
  }
@@ -1132,7 +1409,7 @@ function containsMarkdown(directory, depth = 0) {
1132
1409
  function detectRepositoryDocsDir(repositoryDir) {
1133
1410
  for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
1134
1411
  const path = resolveWithin(repositoryDir, candidate);
1135
- if (existsSync3(path) && lstatSync2(path).isDirectory() && containsMarkdown(path)) return candidate;
1412
+ if (existsSync3(path) && lstatSync3(path).isDirectory() && containsMarkdown(path)) return candidate;
1136
1413
  }
1137
1414
  return "";
1138
1415
  }
@@ -1154,30 +1431,135 @@ function scanFiles(root) {
1154
1431
  function normalizedReferenceKey(value) {
1155
1432
  return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "").replace(/\/(?:index|readme)$/i, "").replace(/^(?:index|readme)$/i, "") || "introduction";
1156
1433
  }
1434
+ function exactReferenceKey(value) {
1435
+ return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
1436
+ }
1157
1437
  function findOpenApi(files) {
1158
1438
  return files.find((file) => OPENAPI_FILENAMES.has(basename(file.relativePath).toLowerCase())) ?? null;
1159
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
+ }
1160
1451
  function withoutFrontmatter(value) {
1161
1452
  return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
1162
1453
  }
1163
- 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()) {
1164
1547
  if (depth >= 8) return raw;
1165
1548
  const snippets = /* @__PURE__ */ new Map();
1166
1549
  const withoutImports = raw.replace(
1167
1550
  /^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm,
1168
1551
  (_statement, componentName, sourcePath) => {
1169
1552
  try {
1170
- const repositoryRelative = sourcePath.startsWith("@site/") ? relative3(repositoryRoot, resolveWithin(siteRoot, sourcePath.slice("@site/".length))).replace(/\\/g, "/") : sourcePath.startsWith("/") ? sourcePath.replace(/^\/+/, "") : relative3(repositoryRoot, resolvePath(dirname2(currentFile), sourcePath)).replace(/\\/g, "/");
1171
- const candidate = resolveWithin(repositoryRoot, repositoryRelative);
1172
- resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
1173
- 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");
1174
1555
  const nested = inlineMdxSnippets(
1175
1556
  withoutFrontmatter(readFileSync3(candidate, "utf8")),
1176
1557
  candidate,
1177
1558
  repositoryRoot,
1178
1559
  warnings,
1179
1560
  depth + 1,
1180
- siteRoot
1561
+ siteRoot,
1562
+ globalAliases
1181
1563
  );
1182
1564
  snippets.set(componentName, nested);
1183
1565
  return "";
@@ -1192,26 +1574,40 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
1192
1574
  }
1193
1575
  }
1194
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
+ }
1195
1590
  let result = withoutImports;
1196
1591
  for (const [componentName, snippet] of snippets) {
1197
- 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
+ });
1198
1597
  }
1199
1598
  return result;
1200
1599
  }
1201
1600
  function injectOpenApi(config, filename) {
1202
- const tabs = config.tabs.filter((tab) => !tab.tab.toLowerCase().includes("api"));
1203
- const apiTab = {
1204
- tab: "API Reference",
1205
- api: { source: `/${filename}` }
1206
- };
1207
- const changelog = tabs.findIndex((tab) => tab.tab.toLowerCase() === "changelog");
1208
- if (changelog >= 0) tabs.splice(changelog, 0, apiTab);
1209
- 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}` } });
1210
1605
  return { ...config, tabs };
1211
1606
  }
1212
1607
  function migrateRepository(options) {
1213
1608
  const repositoryDir = options.repositoryDir;
1214
- 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;
1215
1611
  const docusaurusProjectRoot = platform === "docusaurus" ? findDocusaurusProjectRoot(repositoryDir, options.docsDir) : null;
1216
1612
  const configuredDocsDir = (() => {
1217
1613
  if (options.docsDir !== void 0) {
@@ -1226,23 +1622,30 @@ function migrateRepository(options) {
1226
1622
  const projectRelative = relative3(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
1227
1623
  return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
1228
1624
  }
1625
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1626
+ return relative3(repositoryDir, mintlifyProjectRoot).replace(/\\/g, "/");
1627
+ }
1229
1628
  return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
1230
1629
  })();
1231
1630
  const warnings = [];
1232
1631
  let docsConfig = { tabs: [] };
1233
1632
  const referenceMap = /* @__PURE__ */ new Map();
1633
+ const exactReferenceMap = /* @__PURE__ */ new Map();
1234
1634
  const referenceOrder = /* @__PURE__ */ new Map();
1235
1635
  let docusaurusSidebars = null;
1636
+ let mintlifyConfig = null;
1236
1637
  if (platform === "mintlify") {
1237
1638
  try {
1238
- const config = readMintlifyConfig(repositoryDir);
1639
+ const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
1239
1640
  if (config) {
1641
+ mintlifyConfig = config;
1240
1642
  const projected = projectMintlifyNavigation(config);
1241
1643
  docsConfig = projected.docsConfig;
1242
1644
  warnings.push(...projected.warnings);
1243
1645
  for (const [index, reference] of projected.pageReferences.entries()) {
1244
1646
  const key = normalizedReferenceKey(reference.ref);
1245
1647
  referenceMap.set(key, reference);
1648
+ exactReferenceMap.set(exactReferenceKey(reference.ref), reference);
1246
1649
  referenceOrder.set(key, index);
1247
1650
  }
1248
1651
  }
@@ -1264,7 +1667,7 @@ function migrateRepository(options) {
1264
1667
  }
1265
1668
  }
1266
1669
  const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
1267
- if (!existsSync3(contentRoot) || !lstatSync2(contentRoot).isDirectory()) {
1670
+ if (!existsSync3(contentRoot) || !lstatSync3(contentRoot).isDirectory()) {
1268
1671
  throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
1269
1672
  }
1270
1673
  const files = scanFiles(contentRoot);
@@ -1280,6 +1683,7 @@ function migrateRepository(options) {
1280
1683
  const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
1281
1684
  return leftOrder - rightOrder || left.relativePath.localeCompare(right.relativePath);
1282
1685
  });
1686
+ const snippetAliases = platform === "mintlify" && mintlifyProjectRoot ? globalSnippetAliases(files, repositoryDir, mintlifyProjectRoot) : /* @__PURE__ */ new Map();
1283
1687
  for (const file of pageFiles) {
1284
1688
  if (!isDocumentationExtension(file.relativePath)) continue;
1285
1689
  const sourceSegments = file.relativePath.split("/");
@@ -1288,7 +1692,7 @@ function migrateRepository(options) {
1288
1692
  continue;
1289
1693
  }
1290
1694
  const rootFilename = !file.relativePath.includes("/") ? basename(file.relativePath).toLowerCase() : "";
1291
- if (!configuredDocsDir && REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
1695
+ if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename) && !exactReferenceMap.has(exactReferenceKey(file.relativePath))) {
1292
1696
  skipped++;
1293
1697
  continue;
1294
1698
  }
@@ -1297,21 +1701,21 @@ function migrateRepository(options) {
1297
1701
  warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
1298
1702
  continue;
1299
1703
  }
1300
- const size = lstatSync2(file.absolutePath).size;
1704
+ const size = lstatSync3(file.absolutePath).size;
1301
1705
  if (size > MAX_PAGE_BYTES) {
1302
1706
  skipped++;
1303
1707
  warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
1304
1708
  continue;
1305
1709
  }
1306
1710
  const key = normalizedReferenceKey(file.relativePath);
1307
- const referenced = referenceMap.get(key);
1711
+ const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
1308
1712
  let locale = referenced?.locale;
1309
- let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath);
1713
+ let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
1310
1714
  if (!referenced && localeConfig) {
1311
1715
  const prefix = file.relativePath.split("/", 1)[0];
1312
1716
  if (localeConfig.locales.some((entry) => entry.code === prefix)) {
1313
1717
  locale = prefix;
1314
- navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1));
1718
+ navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1), platform === "mintlify");
1315
1719
  }
1316
1720
  }
1317
1721
  if (!navigationId) {
@@ -1327,7 +1731,8 @@ function migrateRepository(options) {
1327
1731
  repositoryDir,
1328
1732
  warnings,
1329
1733
  0,
1330
- docusaurusProjectRoot ?? repositoryDir
1734
+ mintlifyProjectRoot ?? docusaurusProjectRoot ?? repositoryDir,
1735
+ snippetAliases
1331
1736
  );
1332
1737
  let docusaurusDescriptor;
1333
1738
  const page = parseMarkdownPage({
@@ -1335,11 +1740,11 @@ function migrateRepository(options) {
1335
1740
  navigationId,
1336
1741
  ...locale ? { locale } : {},
1337
1742
  raw,
1338
- source: `${options.sourceUrl}#${file.relativePath}`,
1743
+ source: `${options.sourceUrl}#${relative3(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
1339
1744
  ...platform === "docusaurus" ? {
1340
1745
  resolveIdentity: (frontmatter, fallback) => {
1341
1746
  const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
1342
- const routePrefix = options.docusaurusRoutePrefix?.replace(/^\/+|\/+$/g, "");
1747
+ const routePrefix = options.docusaurusRoutePrefix === void 0 ? void 0 : trimEdgeSlashes(options.docusaurusRoutePrefix);
1343
1748
  if (!routePrefix) {
1344
1749
  docusaurusDescriptor = resolved.descriptor;
1345
1750
  return resolved.identity;
@@ -1362,6 +1767,9 @@ function migrateRepository(options) {
1362
1767
  skipped++;
1363
1768
  continue;
1364
1769
  }
1770
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1771
+ page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot);
1772
+ }
1365
1773
  if (seenPageIds.has(page.id)) {
1366
1774
  skipped++;
1367
1775
  warnings.push({ code: "collision", message: `Multiple source files map to ${page.id}; the first file was kept.`, source: file.relativePath });
@@ -1382,7 +1790,7 @@ function migrateRepository(options) {
1382
1790
  const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
1383
1791
  const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
1384
1792
  const root = resolveWithin(docusaurusAssetRoot, directory);
1385
- if (!existsSync3(root) || !lstatSync2(root).isDirectory()) return [];
1793
+ if (!existsSync3(root) || !lstatSync3(root).isDirectory()) return [];
1386
1794
  return scanFiles(root).map((file) => ({
1387
1795
  ...file,
1388
1796
  relativePath: `${directory}/${file.relativePath}`
@@ -1391,8 +1799,9 @@ function migrateRepository(options) {
1391
1799
  let totalAssetBytes = 0;
1392
1800
  for (const file of [...files, ...repositoryAssets]) {
1393
1801
  const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
1394
- if (!ASSET_DIRECTORIES.has(firstSegment) || !ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
1395
- 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;
1396
1805
  if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
1397
1806
  warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
1398
1807
  continue;
@@ -1430,7 +1839,7 @@ function migrateRepository(options) {
1430
1839
  }
1431
1840
  }
1432
1841
  if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
1433
- const openApi = findOpenApi(files);
1842
+ const openApi = findConfiguredMintlifyOpenApi(mintlifyConfig, files) ?? findOpenApi(files);
1434
1843
  if (openApi) {
1435
1844
  const filename = basename(openApi.relativePath);
1436
1845
  if (!assets.some((asset) => asset.path === filename)) {
@@ -1438,6 +1847,7 @@ function migrateRepository(options) {
1438
1847
  }
1439
1848
  docsConfig = injectOpenApi(docsConfig, filename);
1440
1849
  }
1850
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
1441
1851
  if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
1442
1852
  for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
1443
1853
  if (plugin.docsDir === configuredDocsDir) continue;
@@ -1479,6 +1889,12 @@ function migrateRepository(options) {
1479
1889
  pages,
1480
1890
  assets,
1481
1891
  docsConfig,
1892
+ ...mintlifyConfig ? {
1893
+ site: {
1894
+ ...typeof mintlifyConfig.name === "string" ? { name: mintlifyConfig.name } : {},
1895
+ ...typeof mintlifyConfig.description === "string" ? { description: mintlifyConfig.description } : {}
1896
+ }
1897
+ } : {},
1482
1898
  warnings,
1483
1899
  stats: { discovered, imported: pages.length, skipped }
1484
1900
  };
@@ -1492,8 +1908,13 @@ function renderPage(bundle, page) {
1492
1908
  return [
1493
1909
  "---",
1494
1910
  `title: ${yamlString(page.title)}`,
1911
+ page.navTitle ? `navTitle: ${yamlString(page.navTitle)}` : null,
1495
1912
  `description: ${yamlString(page.description)}`,
1913
+ page.badge ? `badge: ${yamlString(page.badge)}` : null,
1496
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,
1497
1918
  page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
1498
1919
  bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
1499
1920
  "---",
@@ -1591,10 +2012,13 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
1591
2012
  "Color",
1592
2013
  "Column",
1593
2014
  "Columns",
2015
+ "ContentPanel",
1594
2016
  "Danger",
1595
2017
  "Error",
1596
2018
  "Expandable",
1597
2019
  "Agent",
2020
+ "AgentPrompt",
2021
+ "BannerPreview",
1598
2022
  "Embed",
1599
2023
  "File",
1600
2024
  "Folder",
@@ -1785,7 +2209,7 @@ function pageIdForUrl(url, scopePath) {
1785
2209
  else if (path === scopePath) path = "";
1786
2210
  else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
1787
2211
  }
1788
- return pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
2212
+ return pageIdFromReference(path.replace(/^\/+/, "") || "introduction", true);
1789
2213
  }
1790
2214
  function canonicalLocalizedPageId(id) {
1791
2215
  const [segment, ...rest] = id.split("/");
@@ -1807,6 +2231,72 @@ function detectUrlPlatform(document) {
1807
2231
  if (value.includes("starlight")) return "starlight";
1808
2232
  return "generic";
1809
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
+ }
1810
2300
  function plainDescription(value) {
1811
2301
  return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_>#-]/g, "").replace(/\s+/g, " ").trim().slice(0, 240);
1812
2302
  }
@@ -2045,7 +2535,12 @@ function remoteMarkdownMetadata(body) {
2045
2535
  return {
2046
2536
  content: body.slice(contentStart),
2047
2537
  title: scalar("title"),
2048
- 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
2049
2544
  };
2050
2545
  }
2051
2546
  function stripMintlifyDocumentationIndex(body) {
@@ -2105,6 +2600,11 @@ function markdownPage(document, id) {
2105
2600
  "---",
2106
2601
  `title: ${JSON.stringify(title)}`,
2107
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"] : [],
2108
2608
  ...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
2109
2609
  "---",
2110
2610
  "",
@@ -2487,7 +2987,7 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2487
2987
  const collectCandidates = (selector) => {
2488
2988
  const candidates2 = [];
2489
2989
  $(selector).each((_containerIndex, container) => {
2490
- const entries = [];
2990
+ const entries2 = [];
2491
2991
  const seenSections = /* @__PURE__ */ new Set();
2492
2992
  let anchorCount = 0;
2493
2993
  $(container).find("a[href]").each((_index, element) => {
@@ -2503,22 +3003,40 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2503
3003
  const section = targetId.split("/", 1)[0] || "introduction";
2504
3004
  if (!pageId || seenSections.has(section)) return;
2505
3005
  seenSections.add(section);
2506
- entries.push({ section, label, pageId });
3006
+ entries2.push({ section, label, pageId });
2507
3007
  } catch {
2508
3008
  }
2509
3009
  });
2510
- if (entries.length > 1) {
2511
- candidates2.push({ entries, anchorCount });
3010
+ if (entries2.length > 1) {
3011
+ candidates2.push({ entries: entries2, anchorCount });
2512
3012
  }
2513
3013
  });
2514
3014
  return candidates2;
2515
3015
  };
2516
3016
  const tabCandidates = collectCandidates(".nav-tabs");
2517
3017
  const candidates = tabCandidates.length > 0 ? tabCandidates : collectCandidates('nav, [role="navigation"]');
2518
- return candidates.sort((left, right) => {
3018
+ const entries = candidates.sort((left, right) => {
2519
3019
  const densityDifference = right.entries.length / right.anchorCount - left.entries.length / left.anchorCount;
2520
3020
  return densityDifference || right.entries.length - left.entries.length;
2521
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
+ };
2522
3040
  }
2523
3041
  function markdownLinks(body) {
2524
3042
  return [...body.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s+['"][^'"]*['"])?\)/g)].map((match) => match[1]);
@@ -2583,8 +3101,13 @@ async function migrateUrl(options) {
2583
3101
  }
2584
3102
  const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
2585
3103
  const scopePath = platform === "mintlify" ? machineIndexScopePath(source, [initial, scopedHtmlProbe]) ?? submittedScopePath : docusaurusDiscovery?.scopePath ?? submittedScopePath;
2586
- const sourceTopLevelNavigation = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
2587
- 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);
2588
3111
  const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
2589
3112
  if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
2590
3113
  const queue = [source];
@@ -2605,6 +3128,13 @@ async function migrateUrl(options) {
2605
3128
  queued.add(candidateIdentity(url));
2606
3129
  queue.push(url);
2607
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
+ }
2608
3138
  const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
2609
3139
  if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
2610
3140
  for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
@@ -2808,10 +3338,11 @@ async function migrateUrl(options) {
2808
3338
  ...entry,
2809
3339
  pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
2810
3340
  }));
2811
- 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, {
2812
3342
  topLevelTabs: platform === "mintlify",
2813
3343
  topLevelNavigation
2814
3344
  });
3345
+ if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
2815
3346
  if (docusaurusRedirects.length > 0) {
2816
3347
  docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
2817
3348
  `${redirect.source}:${redirect.destination}`,
@@ -2841,6 +3372,7 @@ async function migrateUrl(options) {
2841
3372
  ]
2842
3373
  };
2843
3374
  }
3375
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
2844
3376
  return {
2845
3377
  sourceUrl: source.toString(),
2846
3378
  sourceKind: "url",
@@ -2848,6 +3380,12 @@ async function migrateUrl(options) {
2848
3380
  pages,
2849
3381
  assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
2850
3382
  docsConfig,
3383
+ ...sourceMintlifyConfig ? {
3384
+ site: {
3385
+ ...typeof sourceMintlifyConfig.name === "string" ? { name: sourceMintlifyConfig.name } : {},
3386
+ ...typeof sourceMintlifyConfig.description === "string" ? { description: sourceMintlifyConfig.description } : {}
3387
+ }
3388
+ } : {},
2851
3389
  warnings,
2852
3390
  stats: {
2853
3391
  discovered: visited.size,
@@ -2857,6 +3395,7 @@ async function migrateUrl(options) {
2857
3395
  };
2858
3396
  }
2859
3397
  export {
3398
+ addMintlifyDirectoryRedirects,
2860
3399
  buildNavigationFromPages,
2861
3400
  cloneGitHubRepository,
2862
3401
  defaultMigrationFetcher,