@thallylabs/migrate 0.2.2 → 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/README.md CHANGED
@@ -5,12 +5,13 @@ Cloud. It imports documentation repositories and public documentation URLs
5
5
  into one canonical bundle before any caller writes files.
6
6
 
7
7
  Repository migration includes dedicated Mintlify and Docusaurus adapters.
8
- Mintlify repositories are located inside monorepos automatically and their
9
- `docs.json`/`mint.json` configuration is projected without flattening the
10
- information architecture. The adapter preserves nested groups, tabs,
11
- dropdowns, products, versions, languages, reusable `$ref` navigation files,
12
- page route casing, redirects, local snippets, static assets, compatible page
13
- frontmatter, site identity, and OpenAPI configuration. For public Mintlify
8
+ Mintlify repositories are located inside monorepos automatically. Their
9
+ `docs.json`/`mint.json` page and group trees are projected without flattening
10
+ authored nesting or order. The adapter supports tabs, dropdowns, products,
11
+ versions, languages, and reusable `$ref` navigation files; it also preserves
12
+ dropdown presentation, collection descriptions and icons, explicit default
13
+ versions, page route casing, redirects, local snippets, static assets,
14
+ compatible page frontmatter, site identity, and OpenAPI configuration. For public Mintlify
14
15
  sites, the URL adapter reads the structured configuration embedded in the
15
16
  rendered application before falling back to DOM link inference.
16
17
 
package/dist/index.d.ts CHANGED
@@ -12,8 +12,12 @@ interface MigrationNavigationGroup {
12
12
  }
13
13
  interface MigrationNavigationTab {
14
14
  tab: string;
15
+ description?: string;
16
+ icon?: string;
15
17
  href?: string;
16
18
  hidden?: boolean;
19
+ /** Root page/group nodes used when the source has no synthetic section heading. */
20
+ pages?: Array<string | MigrationNavigationGroup>;
17
21
  groups?: Array<MigrationNavigationGroup>;
18
22
  api?: {
19
23
  source: string;
@@ -54,6 +58,10 @@ interface MigrationFooterConfig {
54
58
  }
55
59
  interface MigrationDocsConfig {
56
60
  tabs: Array<MigrationNavigationTab>;
61
+ navigation?: {
62
+ /** How sibling documentation collections are presented to readers. */
63
+ display?: 'tabs' | 'dropdown';
64
+ };
57
65
  theme?: 'default' | 'maple' | 'sharp' | 'minimal';
58
66
  banner?: MigrationBannerConfig;
59
67
  navbar?: MigrationNavbarConfig;
package/dist/index.js CHANGED
@@ -776,6 +776,12 @@ function iconName(value) {
776
776
  const icon = objectValue2(value);
777
777
  return typeof icon?.name === "string" && icon.name ? icon.name : void 0;
778
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
+ };
784
+ }
779
785
  function registerReference(value, context) {
780
786
  const localePrefix = context.locale ? `${context.locale}/` : "";
781
787
  const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
@@ -825,56 +831,76 @@ function convertPage(value, context) {
825
831
  }
826
832
  return null;
827
833
  }
828
- function convertGroups(values, context, fallbackGroup) {
829
- const groups = [];
830
- const loosePages = [];
831
- for (const value of values) {
832
- const converted = convertPage(value, context);
833
- if (!converted) continue;
834
- if (typeof converted === "string") loosePages.push(converted);
835
- else groups.push(converted);
836
- }
837
- if (loosePages.length > 0) groups.unshift({ group: fallbackGroup, pages: loosePages });
838
- return groups;
834
+ function convertNavigationValues(values, context) {
835
+ return values.flatMap((value) => {
836
+ const page = convertPage(value, context);
837
+ return page ? [page] : [];
838
+ });
839
839
  }
840
- function convertContainerToTabs(containerValue, context, fallbackTab) {
840
+ function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
841
841
  const container = objectValue2(containerValue);
842
842
  if (!container) return [];
843
- 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) {
844
845
  if (!Array.isArray(container[key])) continue;
845
- 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) => {
846
851
  const object = objectValue2(value);
847
852
  if (!object) return [];
848
853
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
849
854
  const href = projectedHref(object.href);
850
- if (href && !object.pages && !object.groups) {
851
- return [{ tab, href, ...object.hidden === true ? { hidden: true } : {} }];
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
+ }];
852
863
  }
853
- const nested = convertContainerToTabs(object, context, tab);
864
+ const nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
854
865
  if (nested.length > 0) {
855
866
  if (nested.length === 1) {
856
867
  return [{
857
868
  ...nested[0],
858
869
  tab,
870
+ ...containerPresentation(object),
859
871
  ...href ? { href } : {},
860
872
  ...object.hidden === true ? { hidden: true } : {}
861
873
  }];
862
874
  }
863
- return nested.map((item) => ({ ...item, tab: `${tab}: ${item.tab}` }));
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
+ }));
864
883
  }
865
884
  return [];
866
885
  });
867
- if (tabs.length > 0) return tabs;
868
- }
869
- const rawPages = [
870
- ...Array.isArray(container.groups) ? container.groups : [],
871
- ...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
872
898
  ];
873
- if (typeof container.root === "string") rawPages.unshift(container.root);
874
- const groups = convertGroups(rawPages, context, "Overview");
875
- return groups.length > 0 ? [{
899
+ const children = convertNavigationValues(values, context);
900
+ return children.length > 0 ? [{
876
901
  tab: fallbackTab,
877
- groups,
902
+ ...hasRootNodes ? { pages: children } : { groups: children },
903
+ ...containerPresentation(container),
878
904
  ...projectedHref(container.href) ? { href: projectedHref(container.href) } : {},
879
905
  ...container.hidden === true ? { hidden: true } : {}
880
906
  }] : [];
@@ -1003,9 +1029,10 @@ function projectMintlifyNavigation(config, options = {}) {
1003
1029
  const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
1004
1030
  let tabs = [];
1005
1031
  let i18n;
1032
+ const projectionTrace = {};
1006
1033
  if (languages.length > 0) {
1007
1034
  const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
1008
- const defaultLocale = String(defaultLanguage.language ?? "en");
1035
+ const defaultLocale = String(defaultLanguage.language ?? defaultLanguage.locale ?? "en");
1009
1036
  const locales = languages.map((entry) => {
1010
1037
  const code = String(entry.language ?? entry.locale ?? "en");
1011
1038
  let label = LANGUAGE_LABELS[code];
@@ -1026,15 +1053,26 @@ function projectMintlifyNavigation(config, options = {}) {
1026
1053
  warnings,
1027
1054
  warningKeys
1028
1055
  };
1029
- const languageTabs = convertContainerToTabs(language, context, "Documentation");
1056
+ const languageTabs = convertContainerToTabs(
1057
+ language,
1058
+ context,
1059
+ "Documentation",
1060
+ language === defaultLanguage ? projectionTrace : void 0
1061
+ );
1030
1062
  if (language === defaultLanguage) tabs = languageTabs;
1031
1063
  }
1032
1064
  } else {
1033
1065
  const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
1034
- tabs = convertContainerToTabs(navigation, context, "Documentation");
1066
+ tabs = convertContainerToTabs(navigation, context, "Documentation", projectionTrace);
1035
1067
  if (tabs.length === 0 && Array.isArray(config.navigation)) {
1036
- const groups = convertGroups(config.navigation, context, "Overview");
1037
- 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
+ }
1038
1076
  }
1039
1077
  }
1040
1078
  if (tabs.length === 0) {
@@ -1058,6 +1096,7 @@ function projectMintlifyNavigation(config, options = {}) {
1058
1096
  return {
1059
1097
  docsConfig: {
1060
1098
  tabs,
1099
+ ...projectionTrace.rootContainerKind === "dropdowns" ? { navigation: { display: "dropdown" } } : {},
1061
1100
  ...projectedCompatibleConfig(config),
1062
1101
  ...i18n ? { i18n } : {},
1063
1102
  ...redirects.length > 0 ? { redirects } : {}
@@ -1927,41 +1966,56 @@ function mergeMigrationConfig(existing, incoming) {
1927
1966
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
1928
1967
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
1929
1968
  const seenPages = /* @__PURE__ */ new Set();
1930
- function recordPages(groups) {
1931
- for (const group of groups ?? []) {
1932
- for (const page of group.pages) {
1933
- if (typeof page === "string") seenPages.add(page);
1934
- else recordPages([page]);
1935
- }
1969
+ function recordPages(pages = []) {
1970
+ for (const page of pages) {
1971
+ if (typeof page === "string") seenPages.add(page);
1972
+ else recordPages(page.pages);
1936
1973
  }
1937
1974
  }
1938
- for (const tab of tabs) recordPages(tab.groups);
1939
- function uniqueGroups(groups) {
1940
- return (groups ?? []).flatMap((group) => {
1941
- const pages = group.pages.flatMap((page) => {
1942
- if (typeof page === "string") {
1943
- if (seenPages.has(page)) return [];
1944
- seenPages.add(page);
1945
- return [page];
1946
- }
1947
- const [nested] = uniqueGroups([page]);
1948
- return nested ? [nested] : [];
1949
- });
1950
- 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 }] : [];
1951
1988
  });
1952
1989
  }
1990
+ function uniqueGroups(groups) {
1991
+ return uniquePages(groups ?? []);
1992
+ }
1953
1993
  for (const tab of incoming.tabs) {
1954
1994
  if (tab.tab.toLowerCase() === "changelog") continue;
1955
- const uniqueTab = { ...tab, ...tab.groups ? { groups: uniqueGroups(tab.groups) } : {} };
1956
- 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;
1957
2001
  if (!names.has(tab.tab.toLowerCase())) {
1958
2002
  tabs.push(uniqueTab);
1959
2003
  names.add(tab.tab.toLowerCase());
1960
2004
  continue;
1961
2005
  }
1962
2006
  const target = tabs.find((item) => item.tab.toLowerCase() === tab.tab.toLowerCase());
1963
- if (!target || !uniqueTab.groups) continue;
1964
- 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
+ }
1965
2019
  }
1966
2020
  const changelog = existing.tabs.find((tab) => tab.tab.toLowerCase() === "changelog") ?? incoming.tabs.find((tab) => tab.tab.toLowerCase() === "changelog");
1967
2021
  if (changelog) tabs.push(changelog);
@@ -1975,6 +2029,9 @@ function mergeMigrationConfig(existing, incoming) {
1975
2029
  ...incoming,
1976
2030
  ...existing,
1977
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,
1978
2035
  ...i18n ? { i18n } : {}
1979
2036
  };
1980
2037
  }
@@ -3025,15 +3082,17 @@ function htmlTopLevelNavigation(document, source, scopePath) {
3025
3082
  }
3026
3083
  function remapNavigationHome(config, sourceHomePageId) {
3027
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) });
3028
3086
  const remapGroups = (groups) => groups.map((group) => ({
3029
3087
  ...group,
3030
- pages: group.pages.map((page) => typeof page === "string" ? page === sourceHomePageId ? "introduction" : page : remapGroups([page])[0])
3088
+ pages: remapPages(group.pages)
3031
3089
  }));
3032
3090
  return {
3033
3091
  ...config,
3034
3092
  tabs: config.tabs.map((tab) => ({
3035
3093
  ...tab,
3036
3094
  ...tab.href === `/${sourceHomePageId}` ? { href: "/" } : {},
3095
+ ...tab.pages ? { pages: remapPages(tab.pages) } : {},
3037
3096
  ...tab.groups ? { groups: remapGroups(tab.groups) } : {}
3038
3097
  }))
3039
3098
  };
@@ -3352,12 +3411,8 @@ async function migrateUrl(options) {
3352
3411
  const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
3353
3412
  if (openApiAsset) {
3354
3413
  const operationPageIds = new Set(pages.filter((page) => page.openapi).map((page) => page.navigationId));
3355
- const apiTab = docsConfig.tabs.find((tab) => tab.groups?.some((group) => {
3356
- const containsOperation = (pages2) => pages2.some((page) => {
3357
- return typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages);
3358
- });
3359
- return containsOperation(group.pages);
3360
- })) ?? 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];
3361
3416
  if (apiTab) {
3362
3417
  apiTab.api = { source: "/openapi.yaml", navigation: false };
3363
3418
  }