@thallylabs/migrate 0.2.1 → 0.2.3

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,39 @@ 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;
778
+ }
779
+ function containerPresentation(value) {
780
+ return {
781
+ ...typeof value.description === "string" && value.description.trim() ? { description: value.description.trim() } : {},
782
+ ...iconName(value.icon) ? { icon: iconName(value.icon) } : {}
783
+ };
694
784
  }
695
785
  function registerReference(value, context) {
696
786
  const localePrefix = context.locale ? `${context.locale}/` : "";
697
787
  const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
698
- const navigationId = normalizePageRef(localizedValue);
788
+ const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
699
789
  if (!navigationId) return null;
700
790
  const key = `${context.locale ?? ""}:${value}`;
701
791
  if (!context.seenReferences.has(key)) {
@@ -709,6 +799,17 @@ function convertPage(value, context) {
709
799
  const object = objectValue2(value);
710
800
  if (!object) return null;
711
801
  if (typeof object.page === "string") return registerReference(object.page, context);
802
+ const href = projectedHref(object.href);
803
+ if (href && !object.pages && !object.groups) {
804
+ const page = registerReference(href, context);
805
+ if (page) return page;
806
+ warnOnce(
807
+ context,
808
+ "external-page-link",
809
+ "External links nested inside a Mintlify sidebar cannot be represented as Thally pages and were omitted."
810
+ );
811
+ return null;
812
+ }
712
813
  const pages = Array.isArray(object.pages) ? object.pages : [];
713
814
  if ("group" in object || pages.length > 0) {
714
815
  const children = [];
@@ -723,84 +824,255 @@ function convertPage(value, context) {
723
824
  if (children.length === 0) return null;
724
825
  return {
725
826
  group: labelFor(object, "Documentation"),
726
- ...typeof object.icon === "string" ? { icon: object.icon } : {},
827
+ ...iconName(object.icon) ? { icon: iconName(object.icon) } : {},
828
+ ...object.hidden === true ? { hidden: true } : {},
727
829
  pages: children
728
830
  };
729
831
  }
730
832
  return null;
731
833
  }
732
- function convertGroups(values, context, fallbackGroup) {
733
- const groups = [];
734
- const loosePages = [];
735
- for (const value of values) {
736
- const converted = convertPage(value, context);
737
- if (!converted) continue;
738
- if (typeof converted === "string") loosePages.push(converted);
739
- else groups.push(converted);
740
- }
741
- if (loosePages.length > 0) groups.unshift({ group: fallbackGroup, pages: loosePages });
742
- return groups;
834
+ function convertNavigationValues(values, context) {
835
+ return values.flatMap((value) => {
836
+ const page = convertPage(value, context);
837
+ return page ? [page] : [];
838
+ });
743
839
  }
744
- function convertContainerToTabs(containerValue, context, fallbackTab) {
840
+ function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
745
841
  const container = objectValue2(containerValue);
746
842
  if (!container) return [];
747
- for (const key of ["tabs", "anchors", "products", "dropdowns", "versions", "menus"]) {
843
+ const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
844
+ for (const key of containerKeys) {
748
845
  if (!Array.isArray(container[key])) continue;
749
- const tabs = container[key].flatMap((value, index) => {
846
+ const entries = [...container[key]];
847
+ if (key === "versions") {
848
+ entries.sort((left, right) => Number(objectValue2(right)?.default === true) - Number(objectValue2(left)?.default === true));
849
+ }
850
+ const tabs = entries.flatMap((value, index) => {
750
851
  const object = objectValue2(value);
751
852
  if (!object) return [];
752
853
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
753
- if (typeof object.href === "string" && !object.pages && !object.groups) {
754
- return [{ tab, href: object.href }];
854
+ const href = projectedHref(object.href);
855
+ const hasNestedContainers = containerKeys.some((containerKey) => Array.isArray(object[containerKey]));
856
+ if (href && !object.pages && !object.groups && !hasNestedContainers) {
857
+ return [{
858
+ tab,
859
+ href,
860
+ ...containerPresentation(object),
861
+ ...object.hidden === true ? { hidden: true } : {}
862
+ }];
755
863
  }
756
- const nested = convertContainerToTabs(object, context, tab);
864
+ const nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
757
865
  if (nested.length > 0) {
758
- if (nested.length === 1) return [{ ...nested[0], tab }];
759
- return nested.map((item) => ({ ...item, tab: `${tab}: ${item.tab}` }));
866
+ if (nested.length === 1) {
867
+ return [{
868
+ ...nested[0],
869
+ tab,
870
+ ...containerPresentation(object),
871
+ ...href ? { href } : {},
872
+ ...object.hidden === true ? { hidden: true } : {}
873
+ }];
874
+ }
875
+ const presentation = containerPresentation(object);
876
+ return nested.map((item, childIndex) => ({
877
+ ...presentation,
878
+ ...item,
879
+ tab: `${tab}: ${item.tab}`,
880
+ ...childIndex === 0 && href && !item.href ? { href } : {},
881
+ ...object.hidden === true ? { hidden: true } : {}
882
+ }));
760
883
  }
761
884
  return [];
762
885
  });
763
- if (tabs.length > 0) return tabs;
764
- }
765
- const rawPages = [
766
- ...Array.isArray(container.groups) ? container.groups : [],
767
- ...Array.isArray(container.pages) ? container.pages : []
886
+ if (tabs.length > 0) {
887
+ if (depth === 0 && trace) trace.rootContainerKind = key;
888
+ return tabs;
889
+ }
890
+ }
891
+ const rawGroups = Array.isArray(container.groups) ? container.groups : [];
892
+ const rawPages = Array.isArray(container.pages) ? container.pages : [];
893
+ const hasRootNodes = typeof container.root === "string" || rawPages.length > 0;
894
+ const values = [
895
+ ...typeof container.root === "string" ? [container.root] : [],
896
+ ...rawGroups,
897
+ ...rawPages
768
898
  ];
769
- if (typeof container.root === "string") rawPages.unshift(container.root);
770
- const groups = convertGroups(rawPages, context, "Overview");
771
- return groups.length > 0 ? [{ tab: fallbackTab, groups }] : [];
899
+ const children = convertNavigationValues(values, context);
900
+ return children.length > 0 ? [{
901
+ tab: fallbackTab,
902
+ ...hasRootNodes ? { pages: children } : { groups: children },
903
+ ...containerPresentation(container),
904
+ ...projectedHref(container.href) ? { href: projectedHref(container.href) } : {},
905
+ ...container.hidden === true ? { hidden: true } : {}
906
+ }] : [];
907
+ }
908
+ function projectedTheme(value) {
909
+ if (value === "maple") return "maple";
910
+ if (["aspen", "luma", "sequoia"].includes(String(value))) return "sharp";
911
+ if (["almond", "palm"].includes(String(value))) return "minimal";
912
+ return typeof value === "string" ? "default" : void 0;
913
+ }
914
+ function projectedNavbar(value) {
915
+ const navbar = objectValue2(value);
916
+ if (!navbar) return void 0;
917
+ const links = Array.isArray(navbar.links) ? navbar.links.flatMap((entry) => {
918
+ const link = objectValue2(entry);
919
+ const href = projectedHref(link?.href);
920
+ if (!link || !href) return [];
921
+ const label = typeof link.label === "string" ? link.label : typeof link.name === "string" ? link.name : "";
922
+ if (!label) return [];
923
+ return [{ label, href, ...link.type === "github" ? { type: "github" } : {} }];
924
+ }) : [];
925
+ const primaryValue = objectValue2(navbar.primary);
926
+ const primaryHref = projectedHref(primaryValue?.href);
927
+ const primary = primaryValue && primaryHref ? {
928
+ label: typeof primaryValue.label === "string" ? primaryValue.label : primaryValue.type === "github" ? "GitHub" : "Get started",
929
+ href: primaryHref
930
+ } : void 0;
931
+ return links.length > 0 || primary ? { ...links.length > 0 ? { links } : {}, ...primary ? { primary } : {} } : void 0;
932
+ }
933
+ function projectedGlobalNavigationLinks(value) {
934
+ const global = objectValue2(value);
935
+ if (!global) return [];
936
+ const links = [];
937
+ const visit = (entry) => {
938
+ if (Array.isArray(entry)) {
939
+ entry.forEach(visit);
940
+ return;
941
+ }
942
+ const item = objectValue2(entry);
943
+ if (!item) return;
944
+ const href = projectedHref(item.href);
945
+ if (href) {
946
+ const label = labelFor(item, "");
947
+ if (label) links.push({ label, href });
948
+ }
949
+ for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus"]) {
950
+ if (Array.isArray(item[key])) visit(item[key]);
951
+ }
952
+ };
953
+ visit(global);
954
+ return links;
955
+ }
956
+ function projectedFooter(value) {
957
+ const footer = objectValue2(value);
958
+ if (!footer) return void 0;
959
+ const socials = objectValue2(footer.socials);
960
+ const socialLinks = socials ? Object.fromEntries(Object.entries(socials).flatMap(([key, value2]) => {
961
+ const href = projectedHref(value2);
962
+ return href ? [[key, href]] : [];
963
+ })) : void 0;
964
+ const links = Array.isArray(footer.links) ? footer.links.flatMap((entry) => {
965
+ const column = objectValue2(entry);
966
+ if (!column || !Array.isArray(column.items)) return [];
967
+ const heading = typeof column.heading === "string" ? column.heading : typeof column.header === "string" ? column.header : "";
968
+ const items = column.items.flatMap((item) => {
969
+ const link = objectValue2(item);
970
+ const href = projectedHref(link?.href);
971
+ return link && typeof link.label === "string" && href ? [{ label: link.label, href }] : [];
972
+ });
973
+ return heading && items.length > 0 ? [{ heading, items }] : [];
974
+ }) : [];
975
+ return socialLinks && Object.keys(socialLinks).length > 0 || links.length > 0 ? { ...socialLinks && Object.keys(socialLinks).length > 0 ? { socials: socialLinks } : {}, ...links.length > 0 ? { links } : {} } : void 0;
976
+ }
977
+ function projectedFont(value) {
978
+ if (typeof value === "string" && value) return { family: value };
979
+ const font = objectValue2(value);
980
+ if (!font) return void 0;
981
+ const family = typeof font.family === "string" ? font.family : typeof font.name === "string" ? font.name : void 0;
982
+ if (!family) return void 0;
983
+ const weight = Array.isArray(font.weight) ? font.weight.map(String) : Array.isArray(font.weights) ? font.weights.map(String) : void 0;
984
+ return { family, ...weight && weight.length > 0 ? { weight } : {} };
985
+ }
986
+ function projectedCompatibleConfig(config) {
987
+ const banner = objectValue2(config.banner);
988
+ const bannerContent = banner && (typeof banner.content === "string" || objectValue2(banner.content)) ? banner.content : void 0;
989
+ const bannerType = banner && ["info", "warning", "critical"].includes(String(banner.type ?? banner.variant)) ? String(banner.type ?? banner.variant) : void 0;
990
+ const bannerColor = objectValue2(banner?.color);
991
+ const bodyFont = projectedFont(objectValue2(config.fonts)?.body ?? config.fonts);
992
+ const headingFont = projectedFont(objectValue2(config.fonts)?.heading);
993
+ const feedback = objectValue2(config.feedback);
994
+ const seo = objectValue2(config.seo);
995
+ const navbar = projectedNavbar(config.navbar);
996
+ const globalLinks = projectedGlobalNavigationLinks(objectValue2(config.navigation)?.global);
997
+ const navbarLinks = [...new Map(
998
+ [...navbar?.links ?? [], ...globalLinks].map((link) => [`${link.label}:${link.href}`, link])
999
+ ).values()];
1000
+ const projectedNavigation = navbarLinks.length > 0 || navbar?.primary ? { ...navbarLinks.length > 0 ? { links: navbarLinks } : {}, ...navbar?.primary ? { primary: navbar.primary } : {} } : void 0;
1001
+ return {
1002
+ ...projectedTheme(config.theme) ? { theme: projectedTheme(config.theme) } : {},
1003
+ ...bannerContent ? {
1004
+ banner: {
1005
+ content: bannerContent,
1006
+ ...banner?.dismissible === false ? { dismissible: false } : {},
1007
+ ...typeof banner?.id === "string" ? { id: banner.id } : {},
1008
+ ...typeof banner?.revision === "string" ? { revision: banner.revision } : {},
1009
+ ...bannerType ? { type: bannerType } : {},
1010
+ ...bannerColor ? { color: {
1011
+ ...typeof bannerColor.light === "string" ? { light: bannerColor.light } : {},
1012
+ ...typeof bannerColor.dark === "string" ? { dark: bannerColor.dark } : {}
1013
+ } } : {}
1014
+ }
1015
+ } : {},
1016
+ ...projectedNavigation ? { navbar: projectedNavigation } : {},
1017
+ ...projectedFooter(config.footer) ? { footer: projectedFooter(config.footer) } : {},
1018
+ ...bodyFont || headingFont ? { fonts: { ...bodyFont ? { body: bodyFont } : {}, ...headingFont ? { heading: headingFont } : {} } } : {},
1019
+ ...seo?.indexing === "all" ? { seo: { indexing: "all" } } : {},
1020
+ ...typeof feedback?.thumbsRating === "boolean" ? { feedback: { thumbsRating: feedback.thumbsRating } } : {}
1021
+ };
772
1022
  }
773
- function projectMintlifyNavigation(config) {
1023
+ function projectMintlifyNavigation(config, options = {}) {
774
1024
  const warnings = [];
775
1025
  const references = [];
776
1026
  const seenReferences = /* @__PURE__ */ new Set();
1027
+ const warningKeys = /* @__PURE__ */ new Set();
777
1028
  const navigation = objectValue2(config.navigation) ?? config;
778
1029
  const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
779
1030
  let tabs = [];
780
1031
  let i18n;
1032
+ const projectionTrace = {};
781
1033
  if (languages.length > 0) {
782
- const defaultLanguage = languages.find((entry) => entry.language === "en") ?? languages[0];
783
- const defaultLocale = String(defaultLanguage.language ?? "en");
1034
+ const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
1035
+ const defaultLocale = String(defaultLanguage.language ?? defaultLanguage.locale ?? "en");
784
1036
  const locales = languages.map((entry) => {
785
1037
  const code = String(entry.language ?? entry.locale ?? "en");
786
- return { code, label: LANGUAGE_LABELS[code] ?? code.toUpperCase() };
1038
+ let label = LANGUAGE_LABELS[code];
1039
+ try {
1040
+ label ??= new Intl.DisplayNames(["en"], { type: "language" }).of(code) ?? void 0;
1041
+ } catch {
1042
+ }
1043
+ return { code, label: label ?? code.toUpperCase() };
787
1044
  });
788
1045
  i18n = { defaultLocale, locales };
789
1046
  for (const language of languages) {
790
1047
  const locale = String(language.language ?? language.locale ?? defaultLocale);
791
1048
  const context = {
792
1049
  locale,
1050
+ pathPrefix: options.pathPrefix,
793
1051
  references,
794
- seenReferences
1052
+ seenReferences,
1053
+ warnings,
1054
+ warningKeys
795
1055
  };
796
- const languageTabs = convertContainerToTabs(language, context, "Documentation");
1056
+ const languageTabs = convertContainerToTabs(
1057
+ language,
1058
+ context,
1059
+ "Documentation",
1060
+ language === defaultLanguage ? projectionTrace : void 0
1061
+ );
797
1062
  if (language === defaultLanguage) tabs = languageTabs;
798
1063
  }
799
1064
  } else {
800
- tabs = convertContainerToTabs(navigation, { references, seenReferences }, "Documentation");
1065
+ const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
1066
+ tabs = convertContainerToTabs(navigation, context, "Documentation", projectionTrace);
801
1067
  if (tabs.length === 0 && Array.isArray(config.navigation)) {
802
- const groups = convertGroups(config.navigation, { references, seenReferences }, "Overview");
803
- if (groups.length > 0) tabs = [{ tab: "Documentation", groups }];
1068
+ const children = convertNavigationValues(config.navigation, context);
1069
+ if (children.length > 0) {
1070
+ const hasRootPages = children.some((page) => typeof page === "string");
1071
+ tabs = [{
1072
+ tab: "Documentation",
1073
+ ...hasRootPages ? { pages: children } : { groups: children }
1074
+ }];
1075
+ }
804
1076
  }
805
1077
  }
806
1078
  if (tabs.length === 0) {
@@ -809,20 +1081,26 @@ function projectMintlifyNavigation(config) {
809
1081
  message: "Mintlify navigation could not be projected; generated navigation will be used."
810
1082
  });
811
1083
  }
812
- if (!tabs.some((tab) => tab.tab.toLowerCase() === "changelog")) {
813
- tabs.push({ tab: "Changelog", href: "/changelog" });
814
- }
815
1084
  const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
816
1085
  const redirect = objectValue2(value);
817
1086
  if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
1087
+ const source = redirect.source.trim();
1088
+ const destination = redirect.destination.trim();
1089
+ if (!source.startsWith("/") || !destination.startsWith("/") || source.startsWith("//") || destination.startsWith("//")) return [];
818
1090
  return [{
819
- source: redirect.source,
820
- destination: redirect.destination,
1091
+ source,
1092
+ destination,
821
1093
  ...typeof redirect.permanent === "boolean" ? { permanent: redirect.permanent } : {}
822
1094
  }];
823
1095
  }) : [];
824
1096
  return {
825
- docsConfig: { tabs, ...i18n ? { i18n } : {}, ...redirects.length > 0 ? { redirects } : {} },
1097
+ docsConfig: {
1098
+ tabs,
1099
+ ...projectionTrace.rootContainerKind === "dropdowns" ? { navigation: { display: "dropdown" } } : {},
1100
+ ...projectedCompatibleConfig(config),
1101
+ ...i18n ? { i18n } : {},
1102
+ ...redirects.length > 0 ? { redirects } : {}
1103
+ },
826
1104
  pageReferences: references,
827
1105
  warnings
828
1106
  };
@@ -991,12 +1269,50 @@ var DOCUSAURUS_CONFIG_FILENAMES = [
991
1269
  function hasDocusaurusConfig(directory) {
992
1270
  return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
993
1271
  const path = resolveWithin(directory, filename);
994
- return existsSync3(path) && lstatSync2(path).isFile();
1272
+ return existsSync3(path) && lstatSync3(path).isFile();
995
1273
  });
996
1274
  }
1275
+ function hasMintlifyConfig(directory) {
1276
+ return ["docs.json", "mint.json"].some((filename) => {
1277
+ const path = resolveWithin(directory, filename);
1278
+ if (!existsSync3(path) || !lstatSync3(path).isFile()) return false;
1279
+ if (filename === "mint.json") return true;
1280
+ try {
1281
+ const config = JSON.parse(readFileSync3(path, "utf8"));
1282
+ return String(config.$schema ?? "").includes("mintlify") || "navigation" in config;
1283
+ } catch {
1284
+ return false;
1285
+ }
1286
+ });
1287
+ }
1288
+ function findMintlifyProjectRoot(repositoryDir, docsDir) {
1289
+ if (docsDir !== void 0) {
1290
+ let candidate = trimTrailingSlashes(docsDir);
1291
+ while (true) {
1292
+ const path = resolveWithin(repositoryDir, candidate || ".");
1293
+ if (hasMintlifyConfig(path)) return path;
1294
+ if (!candidate) break;
1295
+ const parent = dirname2(candidate);
1296
+ candidate = parent === "." ? "" : parent;
1297
+ }
1298
+ }
1299
+ const queue = [{ directory: repositoryDir, depth: 0 }];
1300
+ let visited = 0;
1301
+ while (queue.length > 0 && visited < 500) {
1302
+ const current = queue.shift();
1303
+ visited++;
1304
+ if (hasMintlifyConfig(current.directory)) return current.directory;
1305
+ if (current.depth >= 4) continue;
1306
+ for (const entry of readdirSync(current.directory, { withFileTypes: true })) {
1307
+ if (!entry.isDirectory() || entry.isSymbolicLink() || IGNORED_DIRECTORIES.has(entry.name)) continue;
1308
+ queue.push({ directory: resolveWithin(current.directory, entry.name), depth: current.depth + 1 });
1309
+ }
1310
+ }
1311
+ return null;
1312
+ }
997
1313
  function findDocusaurusProjectRoot(repositoryDir, docsDir) {
998
1314
  if (docsDir !== void 0) {
999
- let candidate = docsDir.replace(/\/+$/, "");
1315
+ let candidate = trimTrailingSlashes(docsDir);
1000
1316
  while (true) {
1001
1317
  const path = resolveWithin(repositoryDir, candidate || ".");
1002
1318
  if (hasDocusaurusConfig(path)) return path;
@@ -1023,14 +1339,14 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
1023
1339
  return null;
1024
1340
  }
1025
1341
  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 "";
1342
+ const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync3(candidate) && lstatSync3(candidate).isFile());
1343
+ if (!path || lstatSync3(path).size > 1e6) return "";
1028
1344
  return readFileSync3(path, "utf8");
1029
1345
  }
1030
1346
  function primaryDocusaurusDocsDirectory(projectRoot) {
1031
1347
  const source = readDocusaurusConfigSource(projectRoot);
1032
1348
  const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
1033
- return configured?.replace(/^\.\//, "").replace(/\/+$/, "") || "docs";
1349
+ return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
1034
1350
  }
1035
1351
  function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1036
1352
  const source = readDocusaurusConfigSource(projectRoot);
@@ -1038,12 +1354,12 @@ function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
1038
1354
  const plugins = [];
1039
1355
  const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
1040
1356
  for (const match of source.matchAll(matcher)) {
1041
- const localPath = match[2].replace(/^\.\//, "").replace(/\/+$/, "");
1042
- const routePrefix = match[4].replace(/^\/+|\/+$/g, "");
1357
+ const localPath = trimTrailingSlashes(match[2].replace(/^\.\//, ""));
1358
+ const routePrefix = trimEdgeSlashes(match[4]);
1043
1359
  if (!localPath || !routePrefix) continue;
1044
1360
  const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
1045
1361
  const absolute = resolveWithin(repositoryDir, docsDir);
1046
- if (!existsSync3(absolute) || !lstatSync2(absolute).isDirectory()) continue;
1362
+ if (!existsSync3(absolute) || !lstatSync3(absolute).isDirectory()) continue;
1047
1363
  plugins.push({ docsDir, routePrefix });
1048
1364
  }
1049
1365
  return plugins;
@@ -1100,13 +1416,13 @@ async function cloneGitHubRepository(source, targetDir) {
1100
1416
  });
1101
1417
  });
1102
1418
  }
1103
- function detectRepositoryPlatform(repositoryDir) {
1104
- if (existsSync3(resolveWithin(repositoryDir, "mint.json"))) return "mintlify";
1105
- const docsJson = resolveWithin(repositoryDir, "docs.json");
1419
+ function detectRepositoryPlatform(repositoryDir, docsDir) {
1420
+ if (findMintlifyProjectRoot(repositoryDir, docsDir)) return "mintlify";
1421
+ const selectedRoot = docsDir === void 0 ? repositoryDir : resolveWithin(repositoryDir, docsDir || ".");
1422
+ const docsJson = resolveWithin(selectedRoot, "docs.json");
1106
1423
  if (existsSync3(docsJson)) {
1107
1424
  try {
1108
1425
  const config = JSON.parse(readFileSync3(docsJson, "utf8"));
1109
- if (String(config.$schema ?? "").includes("mintlify") || "navigation" in config) return "mintlify";
1110
1426
  if (Array.isArray(config.tabs)) return "thally";
1111
1427
  } catch {
1112
1428
  }
@@ -1132,7 +1448,7 @@ function containsMarkdown(directory, depth = 0) {
1132
1448
  function detectRepositoryDocsDir(repositoryDir) {
1133
1449
  for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
1134
1450
  const path = resolveWithin(repositoryDir, candidate);
1135
- if (existsSync3(path) && lstatSync2(path).isDirectory() && containsMarkdown(path)) return candidate;
1451
+ if (existsSync3(path) && lstatSync3(path).isDirectory() && containsMarkdown(path)) return candidate;
1136
1452
  }
1137
1453
  return "";
1138
1454
  }
@@ -1154,30 +1470,135 @@ function scanFiles(root) {
1154
1470
  function normalizedReferenceKey(value) {
1155
1471
  return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "").replace(/\/(?:index|readme)$/i, "").replace(/^(?:index|readme)$/i, "") || "introduction";
1156
1472
  }
1473
+ function exactReferenceKey(value) {
1474
+ return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
1475
+ }
1157
1476
  function findOpenApi(files) {
1158
1477
  return files.find((file) => OPENAPI_FILENAMES.has(basename(file.relativePath).toLowerCase())) ?? null;
1159
1478
  }
1479
+ function findConfiguredMintlifyOpenApi(config, files) {
1480
+ const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
1481
+ const configured = typeof api?.openapi === "string" ? [api.openapi] : Array.isArray(api?.openapi) ? api.openapi.filter((value) => typeof value === "string") : [];
1482
+ for (const reference of configured) {
1483
+ if (/^(?:https?:)?\/\//i.test(reference)) continue;
1484
+ const key = reference.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/");
1485
+ const match = files.find((file) => file.relativePath === key);
1486
+ if (match) return match;
1487
+ }
1488
+ return null;
1489
+ }
1160
1490
  function withoutFrontmatter(value) {
1161
1491
  return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
1162
1492
  }
1163
- function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot) {
1493
+ function staticSnippetProperties(attributes) {
1494
+ const properties = /* @__PURE__ */ new Map();
1495
+ const matcher = /\b([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*"([^"]*)"\s*\}|\{\s*'([^']*)'\s*\})/g;
1496
+ for (const match of attributes.matchAll(matcher)) {
1497
+ properties.set(match[1], match[2] ?? match[3] ?? match[4] ?? match[5] ?? "");
1498
+ }
1499
+ return properties;
1500
+ }
1501
+ function interpolateSnippet(snippet, attributes) {
1502
+ const properties = staticSnippetProperties(attributes);
1503
+ if (properties.size === 0) return snippet;
1504
+ let fence = "";
1505
+ return snippet.split("\n").map((line) => {
1506
+ const marker = line.match(/^\s*(`{3,}|~{3,})/)?.[1];
1507
+ if (marker) {
1508
+ if (!fence) fence = marker[0];
1509
+ else if (marker[0] === fence) fence = "";
1510
+ return line;
1511
+ }
1512
+ if (fence) return line;
1513
+ return line.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (original, name) => {
1514
+ return properties.get(name) ?? original;
1515
+ });
1516
+ }).join("\n");
1517
+ }
1518
+ function resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot) {
1519
+ 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, "/");
1520
+ const candidate = resolveWithin(repositoryRoot, repositoryRelative);
1521
+ resolveWithin(repositoryRoot, relative3(repositoryRoot, candidate));
1522
+ return candidate;
1523
+ }
1524
+ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
1525
+ const aliases = /* @__PURE__ */ new Map();
1526
+ const matcher = /^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm;
1527
+ for (const file of files) {
1528
+ const segments = file.relativePath.split("/");
1529
+ if (!segments.some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()))) continue;
1530
+ const basenameWithoutExtension = basename(file.relativePath).replace(/\.mdx?$/i, "");
1531
+ const conventionalAlias = basenameWithoutExtension.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1532
+ if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
1533
+ }
1534
+ for (const file of files) {
1535
+ if (![".md", ".mdx"].includes(extname3(file.relativePath).toLowerCase())) continue;
1536
+ const raw = readFileSync3(file.absolutePath, "utf8");
1537
+ for (const match of raw.matchAll(matcher)) {
1538
+ try {
1539
+ const candidate = resolveSnippetPath(match[2], file.absolutePath, repositoryRoot, siteRoot);
1540
+ if (existsSync3(candidate) && lstatSync3(candidate).isFile()) {
1541
+ aliases.set(match[1], candidate);
1542
+ }
1543
+ } catch {
1544
+ }
1545
+ }
1546
+ }
1547
+ return aliases;
1548
+ }
1549
+ function repositoryAssetHref(value, currentFile, siteRoot) {
1550
+ const isBracketed = value.startsWith("<") && value.endsWith(">");
1551
+ const raw = isBracketed ? value.slice(1, -1) : value;
1552
+ if (!raw || raw.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(raw)) return null;
1553
+ const suffixIndex = raw.search(/[?#]/);
1554
+ const pathname = suffixIndex >= 0 ? raw.slice(0, suffixIndex) : raw;
1555
+ const suffix = suffixIndex >= 0 ? raw.slice(suffixIndex) : "";
1556
+ let decodedPath;
1557
+ try {
1558
+ decodedPath = decodeURIComponent(pathname);
1559
+ } catch {
1560
+ return null;
1561
+ }
1562
+ if (!ASSET_EXTENSIONS.has(extname3(decodedPath).toLowerCase())) return null;
1563
+ try {
1564
+ const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(dirname2(currentFile), decodedPath);
1565
+ const siteRelative = relative3(siteRoot, candidate).replace(/\\/g, "/");
1566
+ resolveWithin(siteRoot, siteRelative);
1567
+ if (!existsSync3(candidate) || !lstatSync3(candidate).isFile()) return null;
1568
+ const normalized = normalizeAssetPath(siteRelative);
1569
+ if (!normalized) return null;
1570
+ const rewritten = `/${normalized}${suffix}`;
1571
+ return isBracketed ? `<${rewritten}>` : rewritten;
1572
+ } catch {
1573
+ return null;
1574
+ }
1575
+ }
1576
+ function rewriteRepositoryAssetLinks(body, currentFile, siteRoot) {
1577
+ return body.replace(/(!?\[[^\]]*\]\()(<[^>]+>|[^)\s]+)([^)]*\))/g, (original, opening, destination, closing) => {
1578
+ const rewritten = repositoryAssetHref(destination, currentFile, siteRoot);
1579
+ return rewritten ? `${opening}${rewritten}${closing}` : original;
1580
+ }).replace(/\b(src|img|image|href)=(['"])([^'"]+)\2/g, (original, property, quote, destination) => {
1581
+ const rewritten = repositoryAssetHref(destination, currentFile, siteRoot);
1582
+ return rewritten ? `${property}=${quote}${rewritten}${quote}` : original;
1583
+ });
1584
+ }
1585
+ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map()) {
1164
1586
  if (depth >= 8) return raw;
1165
1587
  const snippets = /* @__PURE__ */ new Map();
1166
1588
  const withoutImports = raw.replace(
1167
1589
  /^import\s+(?:\{\s*default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}?\s+from\s+['"]([^'"]+\.mdx?)['"]\s*;?(?:\s*\/\/.*)?$/gm,
1168
1590
  (_statement, componentName, sourcePath) => {
1169
1591
  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");
1592
+ const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
1593
+ if (!existsSync3(candidate) || !lstatSync3(candidate).isFile()) throw new Error("file not found");
1174
1594
  const nested = inlineMdxSnippets(
1175
1595
  withoutFrontmatter(readFileSync3(candidate, "utf8")),
1176
1596
  candidate,
1177
1597
  repositoryRoot,
1178
1598
  warnings,
1179
1599
  depth + 1,
1180
- siteRoot
1600
+ siteRoot,
1601
+ globalAliases
1181
1602
  );
1182
1603
  snippets.set(componentName, nested);
1183
1604
  return "";
@@ -1192,26 +1613,40 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
1192
1613
  }
1193
1614
  }
1194
1615
  );
1616
+ for (const [componentName, candidate] of globalAliases) {
1617
+ if (snippets.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
1618
+ const nested = inlineMdxSnippets(
1619
+ withoutFrontmatter(readFileSync3(candidate, "utf8")),
1620
+ candidate,
1621
+ repositoryRoot,
1622
+ warnings,
1623
+ depth + 1,
1624
+ siteRoot,
1625
+ globalAliases
1626
+ );
1627
+ snippets.set(componentName, nested);
1628
+ }
1195
1629
  let result = withoutImports;
1196
1630
  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);
1631
+ result = result.replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)\\s*/>`, "g"), (_tag, attributes) => {
1632
+ return interpolateSnippet(snippet, attributes);
1633
+ }).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>(?:[\\s\\S]*?)<\\/${componentName}>`, "g"), (_tag, attributes) => {
1634
+ return interpolateSnippet(snippet, attributes);
1635
+ });
1198
1636
  }
1199
1637
  return result;
1200
1638
  }
1201
1639
  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);
1640
+ const tabs = config.tabs.map((tab) => ({ ...tab }));
1641
+ const apiTab = tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
1642
+ if (apiTab) apiTab.api = { source: `/${filename}`, navigation: false };
1643
+ else tabs.push({ tab: "API Reference", api: { source: `/${filename}` } });
1210
1644
  return { ...config, tabs };
1211
1645
  }
1212
1646
  function migrateRepository(options) {
1213
1647
  const repositoryDir = options.repositoryDir;
1214
- const platform = options.platform ?? detectRepositoryPlatform(repositoryDir);
1648
+ const platform = options.platform ?? detectRepositoryPlatform(repositoryDir, options.docsDir);
1649
+ const mintlifyProjectRoot = platform === "mintlify" ? findMintlifyProjectRoot(repositoryDir, options.docsDir) : null;
1215
1650
  const docusaurusProjectRoot = platform === "docusaurus" ? findDocusaurusProjectRoot(repositoryDir, options.docsDir) : null;
1216
1651
  const configuredDocsDir = (() => {
1217
1652
  if (options.docsDir !== void 0) {
@@ -1226,23 +1661,30 @@ function migrateRepository(options) {
1226
1661
  const projectRelative = relative3(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
1227
1662
  return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
1228
1663
  }
1664
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1665
+ return relative3(repositoryDir, mintlifyProjectRoot).replace(/\\/g, "/");
1666
+ }
1229
1667
  return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
1230
1668
  })();
1231
1669
  const warnings = [];
1232
1670
  let docsConfig = { tabs: [] };
1233
1671
  const referenceMap = /* @__PURE__ */ new Map();
1672
+ const exactReferenceMap = /* @__PURE__ */ new Map();
1234
1673
  const referenceOrder = /* @__PURE__ */ new Map();
1235
1674
  let docusaurusSidebars = null;
1675
+ let mintlifyConfig = null;
1236
1676
  if (platform === "mintlify") {
1237
1677
  try {
1238
- const config = readMintlifyConfig(repositoryDir);
1678
+ const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
1239
1679
  if (config) {
1680
+ mintlifyConfig = config;
1240
1681
  const projected = projectMintlifyNavigation(config);
1241
1682
  docsConfig = projected.docsConfig;
1242
1683
  warnings.push(...projected.warnings);
1243
1684
  for (const [index, reference] of projected.pageReferences.entries()) {
1244
1685
  const key = normalizedReferenceKey(reference.ref);
1245
1686
  referenceMap.set(key, reference);
1687
+ exactReferenceMap.set(exactReferenceKey(reference.ref), reference);
1246
1688
  referenceOrder.set(key, index);
1247
1689
  }
1248
1690
  }
@@ -1264,7 +1706,7 @@ function migrateRepository(options) {
1264
1706
  }
1265
1707
  }
1266
1708
  const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
1267
- if (!existsSync3(contentRoot) || !lstatSync2(contentRoot).isDirectory()) {
1709
+ if (!existsSync3(contentRoot) || !lstatSync3(contentRoot).isDirectory()) {
1268
1710
  throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
1269
1711
  }
1270
1712
  const files = scanFiles(contentRoot);
@@ -1280,6 +1722,7 @@ function migrateRepository(options) {
1280
1722
  const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
1281
1723
  return leftOrder - rightOrder || left.relativePath.localeCompare(right.relativePath);
1282
1724
  });
1725
+ const snippetAliases = platform === "mintlify" && mintlifyProjectRoot ? globalSnippetAliases(files, repositoryDir, mintlifyProjectRoot) : /* @__PURE__ */ new Map();
1283
1726
  for (const file of pageFiles) {
1284
1727
  if (!isDocumentationExtension(file.relativePath)) continue;
1285
1728
  const sourceSegments = file.relativePath.split("/");
@@ -1288,7 +1731,7 @@ function migrateRepository(options) {
1288
1731
  continue;
1289
1732
  }
1290
1733
  const rootFilename = !file.relativePath.includes("/") ? basename(file.relativePath).toLowerCase() : "";
1291
- if (!configuredDocsDir && REPOSITORY_ONLY_DOCUMENTS.has(rootFilename)) {
1734
+ if (REPOSITORY_ONLY_DOCUMENTS.has(rootFilename) && !exactReferenceMap.has(exactReferenceKey(file.relativePath))) {
1292
1735
  skipped++;
1293
1736
  continue;
1294
1737
  }
@@ -1297,21 +1740,21 @@ function migrateRepository(options) {
1297
1740
  warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
1298
1741
  continue;
1299
1742
  }
1300
- const size = lstatSync2(file.absolutePath).size;
1743
+ const size = lstatSync3(file.absolutePath).size;
1301
1744
  if (size > MAX_PAGE_BYTES) {
1302
1745
  skipped++;
1303
1746
  warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
1304
1747
  continue;
1305
1748
  }
1306
1749
  const key = normalizedReferenceKey(file.relativePath);
1307
- const referenced = referenceMap.get(key);
1750
+ const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
1308
1751
  let locale = referenced?.locale;
1309
- let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath);
1752
+ let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
1310
1753
  if (!referenced && localeConfig) {
1311
1754
  const prefix = file.relativePath.split("/", 1)[0];
1312
1755
  if (localeConfig.locales.some((entry) => entry.code === prefix)) {
1313
1756
  locale = prefix;
1314
- navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1));
1757
+ navigationId = pageIdFromReference(file.relativePath.slice(prefix.length + 1), platform === "mintlify");
1315
1758
  }
1316
1759
  }
1317
1760
  if (!navigationId) {
@@ -1327,7 +1770,8 @@ function migrateRepository(options) {
1327
1770
  repositoryDir,
1328
1771
  warnings,
1329
1772
  0,
1330
- docusaurusProjectRoot ?? repositoryDir
1773
+ mintlifyProjectRoot ?? docusaurusProjectRoot ?? repositoryDir,
1774
+ snippetAliases
1331
1775
  );
1332
1776
  let docusaurusDescriptor;
1333
1777
  const page = parseMarkdownPage({
@@ -1335,11 +1779,11 @@ function migrateRepository(options) {
1335
1779
  navigationId,
1336
1780
  ...locale ? { locale } : {},
1337
1781
  raw,
1338
- source: `${options.sourceUrl}#${file.relativePath}`,
1782
+ source: `${options.sourceUrl}#${relative3(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
1339
1783
  ...platform === "docusaurus" ? {
1340
1784
  resolveIdentity: (frontmatter, fallback) => {
1341
1785
  const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
1342
- const routePrefix = options.docusaurusRoutePrefix?.replace(/^\/+|\/+$/g, "");
1786
+ const routePrefix = options.docusaurusRoutePrefix === void 0 ? void 0 : trimEdgeSlashes(options.docusaurusRoutePrefix);
1343
1787
  if (!routePrefix) {
1344
1788
  docusaurusDescriptor = resolved.descriptor;
1345
1789
  return resolved.identity;
@@ -1362,6 +1806,9 @@ function migrateRepository(options) {
1362
1806
  skipped++;
1363
1807
  continue;
1364
1808
  }
1809
+ if (platform === "mintlify" && mintlifyProjectRoot) {
1810
+ page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot);
1811
+ }
1365
1812
  if (seenPageIds.has(page.id)) {
1366
1813
  skipped++;
1367
1814
  warnings.push({ code: "collision", message: `Multiple source files map to ${page.id}; the first file was kept.`, source: file.relativePath });
@@ -1382,7 +1829,7 @@ function migrateRepository(options) {
1382
1829
  const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
1383
1830
  const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
1384
1831
  const root = resolveWithin(docusaurusAssetRoot, directory);
1385
- if (!existsSync3(root) || !lstatSync2(root).isDirectory()) return [];
1832
+ if (!existsSync3(root) || !lstatSync3(root).isDirectory()) return [];
1386
1833
  return scanFiles(root).map((file) => ({
1387
1834
  ...file,
1388
1835
  relativePath: `${directory}/${file.relativePath}`
@@ -1391,8 +1838,9 @@ function migrateRepository(options) {
1391
1838
  let totalAssetBytes = 0;
1392
1839
  for (const file of [...files, ...repositoryAssets]) {
1393
1840
  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;
1841
+ if (!ASSET_EXTENSIONS.has(extname3(file.relativePath).toLowerCase())) continue;
1842
+ if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
1843
+ const size = lstatSync3(file.absolutePath).size;
1396
1844
  if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
1397
1845
  warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
1398
1846
  continue;
@@ -1430,7 +1878,7 @@ function migrateRepository(options) {
1430
1878
  }
1431
1879
  }
1432
1880
  if (docsConfig.tabs.length === 0) docsConfig = buildNavigationFromPages(pages);
1433
- const openApi = findOpenApi(files);
1881
+ const openApi = findConfiguredMintlifyOpenApi(mintlifyConfig, files) ?? findOpenApi(files);
1434
1882
  if (openApi) {
1435
1883
  const filename = basename(openApi.relativePath);
1436
1884
  if (!assets.some((asset) => asset.path === filename)) {
@@ -1438,6 +1886,7 @@ function migrateRepository(options) {
1438
1886
  }
1439
1887
  docsConfig = injectOpenApi(docsConfig, filename);
1440
1888
  }
1889
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
1441
1890
  if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
1442
1891
  for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
1443
1892
  if (plugin.docsDir === configuredDocsDir) continue;
@@ -1479,6 +1928,12 @@ function migrateRepository(options) {
1479
1928
  pages,
1480
1929
  assets,
1481
1930
  docsConfig,
1931
+ ...mintlifyConfig ? {
1932
+ site: {
1933
+ ...typeof mintlifyConfig.name === "string" ? { name: mintlifyConfig.name } : {},
1934
+ ...typeof mintlifyConfig.description === "string" ? { description: mintlifyConfig.description } : {}
1935
+ }
1936
+ } : {},
1482
1937
  warnings,
1483
1938
  stats: { discovered, imported: pages.length, skipped }
1484
1939
  };
@@ -1492,8 +1947,13 @@ function renderPage(bundle, page) {
1492
1947
  return [
1493
1948
  "---",
1494
1949
  `title: ${yamlString(page.title)}`,
1950
+ page.navTitle ? `navTitle: ${yamlString(page.navTitle)}` : null,
1495
1951
  `description: ${yamlString(page.description)}`,
1952
+ page.badge ? `badge: ${yamlString(page.badge)}` : null,
1496
1953
  page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
1954
+ page.mode ? `mode: ${yamlString(page.mode)}` : null,
1955
+ page.hidden ? "hidden: true" : null,
1956
+ page.noindex ? "noindex: true" : null,
1497
1957
  page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
1498
1958
  bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
1499
1959
  "---",
@@ -1506,41 +1966,56 @@ function mergeMigrationConfig(existing, incoming) {
1506
1966
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
1507
1967
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
1508
1968
  const seenPages = /* @__PURE__ */ new Set();
1509
- function recordPages(groups) {
1510
- for (const group of groups ?? []) {
1511
- for (const page of group.pages) {
1512
- if (typeof page === "string") seenPages.add(page);
1513
- else recordPages([page]);
1514
- }
1969
+ function recordPages(pages = []) {
1970
+ for (const page of pages) {
1971
+ if (typeof page === "string") seenPages.add(page);
1972
+ else recordPages(page.pages);
1515
1973
  }
1516
1974
  }
1517
- for (const tab of tabs) recordPages(tab.groups);
1518
- function uniqueGroups(groups) {
1519
- return (groups ?? []).flatMap((group) => {
1520
- const pages = group.pages.flatMap((page) => {
1521
- if (typeof page === "string") {
1522
- if (seenPages.has(page)) return [];
1523
- seenPages.add(page);
1524
- return [page];
1525
- }
1526
- const [nested] = uniqueGroups([page]);
1527
- return nested ? [nested] : [];
1528
- });
1529
- return pages.length > 0 ? [{ ...group, pages }] : [];
1975
+ for (const tab of tabs) {
1976
+ recordPages(tab.pages);
1977
+ recordPages(tab.groups);
1978
+ }
1979
+ function uniquePages(pages = []) {
1980
+ return pages.flatMap((page) => {
1981
+ if (typeof page === "string") {
1982
+ if (seenPages.has(page)) return [];
1983
+ seenPages.add(page);
1984
+ return [page];
1985
+ }
1986
+ const children = uniquePages(page.pages);
1987
+ return children.length > 0 ? [{ ...page, pages: children }] : [];
1530
1988
  });
1531
1989
  }
1990
+ function uniqueGroups(groups) {
1991
+ return uniquePages(groups ?? []);
1992
+ }
1532
1993
  for (const tab of incoming.tabs) {
1533
1994
  if (tab.tab.toLowerCase() === "changelog") continue;
1534
- const uniqueTab = { ...tab, ...tab.groups ? { groups: uniqueGroups(tab.groups) } : {} };
1535
- if (tab.groups && uniqueTab.groups?.length === 0 && !tab.href && !tab.api) continue;
1995
+ const uniqueTab = {
1996
+ ...tab,
1997
+ ...tab.pages ? { pages: uniquePages(tab.pages) } : {},
1998
+ ...tab.groups ? { groups: uniqueGroups(tab.groups) } : {}
1999
+ };
2000
+ if ((tab.pages || tab.groups) && !uniqueTab.pages?.length && !uniqueTab.groups?.length && !tab.href && !tab.api) continue;
1536
2001
  if (!names.has(tab.tab.toLowerCase())) {
1537
2002
  tabs.push(uniqueTab);
1538
2003
  names.add(tab.tab.toLowerCase());
1539
2004
  continue;
1540
2005
  }
1541
2006
  const target = tabs.find((item) => item.tab.toLowerCase() === tab.tab.toLowerCase());
1542
- if (!target || !uniqueTab.groups) continue;
1543
- target.groups = [...target.groups ?? [], ...uniqueTab.groups];
2007
+ if (!target) continue;
2008
+ const incomingRootNodes = [
2009
+ ...uniqueTab.pages ?? [],
2010
+ ...uniqueTab.groups ?? []
2011
+ ];
2012
+ if (uniqueTab.pages?.length) {
2013
+ target.pages = [...target.pages ?? target.groups ?? [], ...incomingRootNodes];
2014
+ delete target.groups;
2015
+ } else if (uniqueTab.groups?.length) {
2016
+ if (target.pages) target.pages = [...target.pages, ...uniqueTab.groups];
2017
+ else target.groups = [...target.groups ?? [], ...uniqueTab.groups];
2018
+ }
1544
2019
  }
1545
2020
  const changelog = existing.tabs.find((tab) => tab.tab.toLowerCase() === "changelog") ?? incoming.tabs.find((tab) => tab.tab.toLowerCase() === "changelog");
1546
2021
  if (changelog) tabs.push(changelog);
@@ -1554,6 +2029,9 @@ function mergeMigrationConfig(existing, incoming) {
1554
2029
  ...incoming,
1555
2030
  ...existing,
1556
2031
  tabs,
2032
+ // Collection presentation belongs to the imported information
2033
+ // architecture; starter defaults must not coerce source dropdowns to tabs.
2034
+ navigation: incoming.navigation ?? existing.navigation,
1557
2035
  ...i18n ? { i18n } : {}
1558
2036
  };
1559
2037
  }
@@ -1591,10 +2069,13 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
1591
2069
  "Color",
1592
2070
  "Column",
1593
2071
  "Columns",
2072
+ "ContentPanel",
1594
2073
  "Danger",
1595
2074
  "Error",
1596
2075
  "Expandable",
1597
2076
  "Agent",
2077
+ "AgentPrompt",
2078
+ "BannerPreview",
1598
2079
  "Embed",
1599
2080
  "File",
1600
2081
  "Folder",
@@ -1785,7 +2266,7 @@ function pageIdForUrl(url, scopePath) {
1785
2266
  else if (path === scopePath) path = "";
1786
2267
  else if (path.startsWith(`${scopePath}/`)) path = path.slice(scopePath.length + 1);
1787
2268
  }
1788
- return pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
2269
+ return pageIdFromReference(path.replace(/^\/+/, "") || "introduction", true);
1789
2270
  }
1790
2271
  function canonicalLocalizedPageId(id) {
1791
2272
  const [segment, ...rest] = id.split("/");
@@ -1807,6 +2288,72 @@ function detectUrlPlatform(document) {
1807
2288
  if (value.includes("starlight")) return "starlight";
1808
2289
  return "generic";
1809
2290
  }
2291
+ function jsonObjectAfterKey(value, key) {
2292
+ let cursor = 0;
2293
+ const marker = `"${key}":`;
2294
+ while (cursor < value.length) {
2295
+ const markerIndex = value.indexOf(marker, cursor);
2296
+ if (markerIndex < 0) return null;
2297
+ let start = markerIndex + marker.length;
2298
+ while (/\s/.test(value[start] ?? "")) start += 1;
2299
+ if (value[start] !== "{") {
2300
+ cursor = start + 1;
2301
+ continue;
2302
+ }
2303
+ let depth = 0;
2304
+ let isQuoted = false;
2305
+ let isEscaped = false;
2306
+ for (let index = start; index < value.length; index += 1) {
2307
+ const character = value[index];
2308
+ if (isQuoted) {
2309
+ if (isEscaped) isEscaped = false;
2310
+ else if (character === "\\") isEscaped = true;
2311
+ else if (character === '"') isQuoted = false;
2312
+ continue;
2313
+ }
2314
+ if (character === '"') {
2315
+ isQuoted = true;
2316
+ continue;
2317
+ }
2318
+ if (character === "{") depth += 1;
2319
+ if (character === "}") {
2320
+ depth -= 1;
2321
+ if (depth !== 0) continue;
2322
+ try {
2323
+ const parsed = JSON.parse(value.slice(start, index + 1));
2324
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2325
+ } catch {
2326
+ break;
2327
+ }
2328
+ }
2329
+ }
2330
+ cursor = start + 1;
2331
+ }
2332
+ return null;
2333
+ }
2334
+ function embeddedMintlifyConfig(body) {
2335
+ const $ = load(body);
2336
+ const flightPayloads = [];
2337
+ $("script").each((_index, element) => {
2338
+ const script = $(element).text();
2339
+ for (const match of script.matchAll(/self\.__next_f\.push\(\[1,("(?:\\.|[^"\\])*")\]\)/g)) {
2340
+ try {
2341
+ const payload2 = JSON.parse(match[1]);
2342
+ if (typeof payload2 === "string") flightPayloads.push(payload2);
2343
+ } catch {
2344
+ }
2345
+ }
2346
+ });
2347
+ if (flightPayloads.length === 0) return null;
2348
+ const payload = flightPayloads.join("\n");
2349
+ const docsConfig = jsonObjectAfterKey(payload, "docsConfig");
2350
+ const scopedNavigation = jsonObjectAfterKey(payload, "scopedNav");
2351
+ if (!docsConfig && !scopedNavigation) return null;
2352
+ return {
2353
+ ...docsConfig ?? {},
2354
+ ...scopedNavigation ? { navigation: scopedNavigation } : {}
2355
+ };
2356
+ }
1810
2357
  function plainDescription(value) {
1811
2358
  return value.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_>#-]/g, "").replace(/\s+/g, " ").trim().slice(0, 240);
1812
2359
  }
@@ -2045,7 +2592,12 @@ function remoteMarkdownMetadata(body) {
2045
2592
  return {
2046
2593
  content: body.slice(contentStart),
2047
2594
  title: scalar("title"),
2048
- description: scalar("description")
2595
+ description: scalar("description"),
2596
+ sidebarTitle: scalar("sidebarTitle"),
2597
+ tag: scalar("tag"),
2598
+ mode: scalar("mode"),
2599
+ hidden: scalar("hidden") === "true" ? true : void 0,
2600
+ noindex: scalar("noindex") === "true" ? true : void 0
2049
2601
  };
2050
2602
  }
2051
2603
  function stripMintlifyDocumentationIndex(body) {
@@ -2105,6 +2657,11 @@ function markdownPage(document, id) {
2105
2657
  "---",
2106
2658
  `title: ${JSON.stringify(title)}`,
2107
2659
  ...description ? [`description: ${JSON.stringify(description)}`] : [],
2660
+ ...parsed.sidebarTitle ? [`sidebarTitle: ${JSON.stringify(parsed.sidebarTitle)}`] : [],
2661
+ ...parsed.tag ? [`tag: ${JSON.stringify(parsed.tag)}`] : [],
2662
+ ...parsed.mode ? [`mode: ${JSON.stringify(parsed.mode)}`] : [],
2663
+ ...parsed.hidden ? ["hidden: true"] : [],
2664
+ ...parsed.noindex ? ["noindex: true"] : [],
2108
2665
  ...embeddedOpenApi.openapi ? [`openapi: ${JSON.stringify(embeddedOpenApi.openapi)}`] : [],
2109
2666
  "---",
2110
2667
  "",
@@ -2487,7 +3044,7 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2487
3044
  const collectCandidates = (selector) => {
2488
3045
  const candidates2 = [];
2489
3046
  $(selector).each((_containerIndex, container) => {
2490
- const entries = [];
3047
+ const entries2 = [];
2491
3048
  const seenSections = /* @__PURE__ */ new Set();
2492
3049
  let anchorCount = 0;
2493
3050
  $(container).find("a[href]").each((_index, element) => {
@@ -2503,22 +3060,42 @@ function htmlTopLevelNavigation(document, source, scopePath) {
2503
3060
  const section = targetId.split("/", 1)[0] || "introduction";
2504
3061
  if (!pageId || seenSections.has(section)) return;
2505
3062
  seenSections.add(section);
2506
- entries.push({ section, label, pageId });
3063
+ entries2.push({ section, label, pageId });
2507
3064
  } catch {
2508
3065
  }
2509
3066
  });
2510
- if (entries.length > 1) {
2511
- candidates2.push({ entries, anchorCount });
3067
+ if (entries2.length > 1) {
3068
+ candidates2.push({ entries: entries2, anchorCount });
2512
3069
  }
2513
3070
  });
2514
3071
  return candidates2;
2515
3072
  };
2516
3073
  const tabCandidates = collectCandidates(".nav-tabs");
2517
3074
  const candidates = tabCandidates.length > 0 ? tabCandidates : collectCandidates('nav, [role="navigation"]');
2518
- return candidates.sort((left, right) => {
3075
+ const entries = candidates.sort((left, right) => {
2519
3076
  const densityDifference = right.entries.length / right.anchorCount - left.entries.length / left.anchorCount;
2520
3077
  return densityDifference || right.entries.length - left.entries.length;
2521
3078
  })[0]?.entries ?? [];
3079
+ const normalizedLabels = entries.map((entry) => entry.label.trim().toLowerCase());
3080
+ if (entries.length > 12 || new Set(normalizedLabels).size !== normalizedLabels.length) return [];
3081
+ return entries;
3082
+ }
3083
+ function remapNavigationHome(config, sourceHomePageId) {
3084
+ if (!sourceHomePageId || sourceHomePageId === "introduction") return config;
3085
+ const remapPages = (pages) => pages.map((page) => typeof page === "string" ? page === sourceHomePageId ? "introduction" : page : { ...page, pages: remapPages(page.pages) });
3086
+ const remapGroups = (groups) => groups.map((group) => ({
3087
+ ...group,
3088
+ pages: remapPages(group.pages)
3089
+ }));
3090
+ return {
3091
+ ...config,
3092
+ tabs: config.tabs.map((tab) => ({
3093
+ ...tab,
3094
+ ...tab.href === `/${sourceHomePageId}` ? { href: "/" } : {},
3095
+ ...tab.pages ? { pages: remapPages(tab.pages) } : {},
3096
+ ...tab.groups ? { groups: remapGroups(tab.groups) } : {}
3097
+ }))
3098
+ };
2522
3099
  }
2523
3100
  function markdownLinks(body) {
2524
3101
  return [...body.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s+['"][^'"]*['"])?\)/g)].map((match) => match[1]);
@@ -2583,8 +3160,13 @@ async function migrateUrl(options) {
2583
3160
  }
2584
3161
  const docusaurusDiscovery = platform === "docusaurus" && docusaurusHtmlProbe ? deriveDocusaurusScope(source, docusaurusHtmlProbe, submittedScopePath) : null;
2585
3162
  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);
3163
+ const sourceMintlifyConfig = platform === "mintlify" && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? embeddedMintlifyConfig(scopedHtmlProbe.body) : null;
3164
+ const sourceMintlifyProjection = sourceMintlifyConfig ? projectMintlifyNavigation(sourceMintlifyConfig, { pathPrefix: scopePath }) : null;
3165
+ const hasStructuredMintlifyNavigation = Boolean(
3166
+ sourceMintlifyProjection?.docsConfig.tabs.length && sourceMintlifyProjection.pageReferences.length
3167
+ );
3168
+ const sourceTopLevelNavigation = platform === "mintlify" && !hasStructuredMintlifyNavigation && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? htmlTopLevelNavigation(scopedHtmlProbe, source, scopePath) : void 0;
3169
+ const sourceHomePageId = sourceMintlifyProjection?.pageReferences[0]?.navigationId ?? sourceTopLevelNavigation?.[0]?.pageId ?? (platform === "mintlify" && scopePath === "/" ? canonicalLocalizedPageId(pageIdForUrl(source, scopePath) ?? "") || void 0 : void 0);
2588
3170
  const cache = /* @__PURE__ */ new Map([[source.toString(), initial]]);
2589
3171
  if (docusaurusHtmlProbe) cache.set(docusaurusHtmlProbe.finalUrl.toString(), docusaurusHtmlProbe);
2590
3172
  const queue = [source];
@@ -2605,6 +3187,13 @@ async function migrateUrl(options) {
2605
3187
  queued.add(candidateIdentity(url));
2606
3188
  queue.push(url);
2607
3189
  }
3190
+ for (const reference of sourceMintlifyProjection?.pageReferences ?? []) {
3191
+ let href = reference.ref;
3192
+ if (scopePath !== "/" && href.startsWith("/") && href !== scopePath && !href.startsWith(`${scopePath}/`)) {
3193
+ href = `${scopePath}${href}`;
3194
+ }
3195
+ enqueue(href, source);
3196
+ }
2608
3197
  const navigationProbe = docusaurusHtmlProbe ?? scopedHtmlProbe;
2609
3198
  if (navigationProbe && /html/i.test(navigationProbe.contentType)) {
2610
3199
  for (const link of htmlNavigationLinks(navigationProbe)) enqueue(link, navigationProbe.finalUrl);
@@ -2808,10 +3397,11 @@ async function migrateUrl(options) {
2808
3397
  ...entry,
2809
3398
  pageId: entry.pageId === sourceHomePageId ? "introduction" : entry.pageId
2810
3399
  }));
2811
- const docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : buildNavigationFromPages(pages, {
3400
+ let docsConfig = platform === "docusaurus" ? docusaurusNavigationFromSnapshots(docusaurusSidebars, pages, scopePath) : hasStructuredMintlifyNavigation && sourceMintlifyProjection ? remapNavigationHome(sourceMintlifyProjection.docsConfig, sourceHomePageId) : buildNavigationFromPages(pages, {
2812
3401
  topLevelTabs: platform === "mintlify",
2813
3402
  topLevelNavigation
2814
3403
  });
3404
+ if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
2815
3405
  if (docusaurusRedirects.length > 0) {
2816
3406
  docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
2817
3407
  `${redirect.source}:${redirect.destination}`,
@@ -2821,12 +3411,8 @@ async function migrateUrl(options) {
2821
3411
  const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
2822
3412
  if (openApiAsset) {
2823
3413
  const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
2824
- const apiTab = docsConfig.tabs.find((tab) => tab.groups?.some((group) => {
2825
- const containsOperation = (pages2) => pages2.some((page) => {
2826
- return typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages);
2827
- });
2828
- return containsOperation(group.pages);
2829
- })) ?? docsConfig.tabs[0];
3414
+ const containsOperation = (navigationPages = []) => navigationPages.some((page) => typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages));
3415
+ const apiTab = docsConfig.tabs.find((tab) => containsOperation(tab.pages) || containsOperation(tab.groups)) ?? docsConfig.tabs[0];
2830
3416
  if (apiTab) {
2831
3417
  apiTab.api = { source: "/openapi.yaml", navigation: false };
2832
3418
  }
@@ -2841,6 +3427,7 @@ async function migrateUrl(options) {
2841
3427
  ]
2842
3428
  };
2843
3429
  }
3430
+ if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
2844
3431
  return {
2845
3432
  sourceUrl: source.toString(),
2846
3433
  sourceKind: "url",
@@ -2848,6 +3435,12 @@ async function migrateUrl(options) {
2848
3435
  pages,
2849
3436
  assets: openApiAsset ? [{ path: "openapi.yaml", content: openApiAsset }] : [],
2850
3437
  docsConfig,
3438
+ ...sourceMintlifyConfig ? {
3439
+ site: {
3440
+ ...typeof sourceMintlifyConfig.name === "string" ? { name: sourceMintlifyConfig.name } : {},
3441
+ ...typeof sourceMintlifyConfig.description === "string" ? { description: sourceMintlifyConfig.description } : {}
3442
+ }
3443
+ } : {},
2851
3444
  warnings,
2852
3445
  stats: {
2853
3446
  discovered: visited.size,
@@ -2857,6 +3450,7 @@ async function migrateUrl(options) {
2857
3450
  };
2858
3451
  }
2859
3452
  export {
3453
+ addMintlifyDirectoryRedirects,
2860
3454
  buildNavigationFromPages,
2861
3455
  cloneGitHubRepository,
2862
3456
  defaultMigrationFetcher,