@thallylabs/migrate 0.2.2 → 0.2.4

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
@@ -534,15 +534,26 @@ function projectDocusaurusNavigation(input) {
534
534
  }
535
535
 
536
536
  // src/frontmatter.ts
537
- import matter from "gray-matter";
538
- var FRONTMATTER_OPTIONS = {
539
- engines: {
540
- javascript: () => ({}),
541
- js: () => ({})
542
- }
543
- };
537
+ import { parse as parseYaml2 } from "yaml";
544
538
  function parseFrontmatter(raw) {
545
- return matter(raw, FRONTMATTER_OPTIONS);
539
+ const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
540
+ const opening = /^---([^\r\n]*)\r?\n/.exec(source);
541
+ if (!opening || opening[1].startsWith("-")) return { content: source, data: {} };
542
+ const language = opening[1].trim().toLowerCase();
543
+ const remainder = source.slice(opening[0].length);
544
+ const closing = /^---[ \t]*\r?$/m.exec(remainder);
545
+ const matter = closing ? remainder.slice(0, closing.index) : remainder;
546
+ let content = closing ? remainder.slice(closing.index + closing[0].length) : "";
547
+ if (content.startsWith("\r\n")) content = content.slice(2);
548
+ else if (content.startsWith("\n")) content = content.slice(1);
549
+ if (matter.trim() === "" || !["", "yaml", "yml"].includes(language)) {
550
+ return { content, data: {} };
551
+ }
552
+ const parsed = parseYaml2(matter);
553
+ return {
554
+ content,
555
+ data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
556
+ };
546
557
  }
547
558
 
548
559
  // src/mdx.ts
@@ -776,6 +787,12 @@ function iconName(value) {
776
787
  const icon = objectValue2(value);
777
788
  return typeof icon?.name === "string" && icon.name ? icon.name : void 0;
778
789
  }
790
+ function containerPresentation(value) {
791
+ return {
792
+ ...typeof value.description === "string" && value.description.trim() ? { description: value.description.trim() } : {},
793
+ ...iconName(value.icon) ? { icon: iconName(value.icon) } : {}
794
+ };
795
+ }
779
796
  function registerReference(value, context) {
780
797
  const localePrefix = context.locale ? `${context.locale}/` : "";
781
798
  const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
@@ -825,56 +842,76 @@ function convertPage(value, context) {
825
842
  }
826
843
  return null;
827
844
  }
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;
845
+ function convertNavigationValues(values, context) {
846
+ return values.flatMap((value) => {
847
+ const page = convertPage(value, context);
848
+ return page ? [page] : [];
849
+ });
839
850
  }
840
- function convertContainerToTabs(containerValue, context, fallbackTab) {
851
+ function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
841
852
  const container = objectValue2(containerValue);
842
853
  if (!container) return [];
843
- for (const key of ["tabs", "anchors", "products", "dropdowns", "versions", "menus"]) {
854
+ const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
855
+ for (const key of containerKeys) {
844
856
  if (!Array.isArray(container[key])) continue;
845
- const tabs = container[key].flatMap((value, index) => {
857
+ const entries = [...container[key]];
858
+ if (key === "versions") {
859
+ entries.sort((left, right) => Number(objectValue2(right)?.default === true) - Number(objectValue2(left)?.default === true));
860
+ }
861
+ const tabs = entries.flatMap((value, index) => {
846
862
  const object = objectValue2(value);
847
863
  if (!object) return [];
848
864
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
849
865
  const href = projectedHref(object.href);
850
- if (href && !object.pages && !object.groups) {
851
- return [{ tab, href, ...object.hidden === true ? { hidden: true } : {} }];
866
+ const hasNestedContainers = containerKeys.some((containerKey) => Array.isArray(object[containerKey]));
867
+ if (href && !object.pages && !object.groups && !hasNestedContainers) {
868
+ return [{
869
+ tab,
870
+ href,
871
+ ...containerPresentation(object),
872
+ ...object.hidden === true ? { hidden: true } : {}
873
+ }];
852
874
  }
853
- const nested = convertContainerToTabs(object, context, tab);
875
+ const nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
854
876
  if (nested.length > 0) {
855
877
  if (nested.length === 1) {
856
878
  return [{
857
879
  ...nested[0],
858
880
  tab,
881
+ ...containerPresentation(object),
859
882
  ...href ? { href } : {},
860
883
  ...object.hidden === true ? { hidden: true } : {}
861
884
  }];
862
885
  }
863
- return nested.map((item) => ({ ...item, tab: `${tab}: ${item.tab}` }));
886
+ const presentation = containerPresentation(object);
887
+ return nested.map((item, childIndex) => ({
888
+ ...presentation,
889
+ ...item,
890
+ tab: `${tab}: ${item.tab}`,
891
+ ...childIndex === 0 && href && !item.href ? { href } : {},
892
+ ...object.hidden === true ? { hidden: true } : {}
893
+ }));
864
894
  }
865
895
  return [];
866
896
  });
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 : []
897
+ if (tabs.length > 0) {
898
+ if (depth === 0 && trace) trace.rootContainerKind = key;
899
+ return tabs;
900
+ }
901
+ }
902
+ const rawGroups = Array.isArray(container.groups) ? container.groups : [];
903
+ const rawPages = Array.isArray(container.pages) ? container.pages : [];
904
+ const hasRootNodes = typeof container.root === "string" || rawPages.length > 0;
905
+ const values = [
906
+ ...typeof container.root === "string" ? [container.root] : [],
907
+ ...rawGroups,
908
+ ...rawPages
872
909
  ];
873
- if (typeof container.root === "string") rawPages.unshift(container.root);
874
- const groups = convertGroups(rawPages, context, "Overview");
875
- return groups.length > 0 ? [{
910
+ const children = convertNavigationValues(values, context);
911
+ return children.length > 0 ? [{
876
912
  tab: fallbackTab,
877
- groups,
913
+ ...hasRootNodes ? { pages: children } : { groups: children },
914
+ ...containerPresentation(container),
878
915
  ...projectedHref(container.href) ? { href: projectedHref(container.href) } : {},
879
916
  ...container.hidden === true ? { hidden: true } : {}
880
917
  }] : [];
@@ -1003,9 +1040,10 @@ function projectMintlifyNavigation(config, options = {}) {
1003
1040
  const languages = Array.isArray(navigation.languages) ? navigation.languages.map(objectValue2).filter((value) => Boolean(value)) : [];
1004
1041
  let tabs = [];
1005
1042
  let i18n;
1043
+ const projectionTrace = {};
1006
1044
  if (languages.length > 0) {
1007
1045
  const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
1008
- const defaultLocale = String(defaultLanguage.language ?? "en");
1046
+ const defaultLocale = String(defaultLanguage.language ?? defaultLanguage.locale ?? "en");
1009
1047
  const locales = languages.map((entry) => {
1010
1048
  const code = String(entry.language ?? entry.locale ?? "en");
1011
1049
  let label = LANGUAGE_LABELS[code];
@@ -1026,15 +1064,26 @@ function projectMintlifyNavigation(config, options = {}) {
1026
1064
  warnings,
1027
1065
  warningKeys
1028
1066
  };
1029
- const languageTabs = convertContainerToTabs(language, context, "Documentation");
1067
+ const languageTabs = convertContainerToTabs(
1068
+ language,
1069
+ context,
1070
+ "Documentation",
1071
+ language === defaultLanguage ? projectionTrace : void 0
1072
+ );
1030
1073
  if (language === defaultLanguage) tabs = languageTabs;
1031
1074
  }
1032
1075
  } else {
1033
1076
  const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
1034
- tabs = convertContainerToTabs(navigation, context, "Documentation");
1077
+ tabs = convertContainerToTabs(navigation, context, "Documentation", projectionTrace);
1035
1078
  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 }];
1079
+ const children = convertNavigationValues(config.navigation, context);
1080
+ if (children.length > 0) {
1081
+ const hasRootPages = children.some((page) => typeof page === "string");
1082
+ tabs = [{
1083
+ tab: "Documentation",
1084
+ ...hasRootPages ? { pages: children } : { groups: children }
1085
+ }];
1086
+ }
1038
1087
  }
1039
1088
  }
1040
1089
  if (tabs.length === 0) {
@@ -1058,6 +1107,7 @@ function projectMintlifyNavigation(config, options = {}) {
1058
1107
  return {
1059
1108
  docsConfig: {
1060
1109
  tabs,
1110
+ ...projectionTrace.rootContainerKind === "dropdowns" ? { navigation: { display: "dropdown" } } : {},
1061
1111
  ...projectedCompatibleConfig(config),
1062
1112
  ...i18n ? { i18n } : {},
1063
1113
  ...redirects.length > 0 ? { redirects } : {}
@@ -1927,41 +1977,56 @@ function mergeMigrationConfig(existing, incoming) {
1927
1977
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
1928
1978
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
1929
1979
  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
- }
1980
+ function recordPages(pages = []) {
1981
+ for (const page of pages) {
1982
+ if (typeof page === "string") seenPages.add(page);
1983
+ else recordPages(page.pages);
1936
1984
  }
1937
1985
  }
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 }] : [];
1986
+ for (const tab of tabs) {
1987
+ recordPages(tab.pages);
1988
+ recordPages(tab.groups);
1989
+ }
1990
+ function uniquePages(pages = []) {
1991
+ return pages.flatMap((page) => {
1992
+ if (typeof page === "string") {
1993
+ if (seenPages.has(page)) return [];
1994
+ seenPages.add(page);
1995
+ return [page];
1996
+ }
1997
+ const children = uniquePages(page.pages);
1998
+ return children.length > 0 ? [{ ...page, pages: children }] : [];
1951
1999
  });
1952
2000
  }
2001
+ function uniqueGroups(groups) {
2002
+ return uniquePages(groups ?? []);
2003
+ }
1953
2004
  for (const tab of incoming.tabs) {
1954
2005
  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;
2006
+ const uniqueTab = {
2007
+ ...tab,
2008
+ ...tab.pages ? { pages: uniquePages(tab.pages) } : {},
2009
+ ...tab.groups ? { groups: uniqueGroups(tab.groups) } : {}
2010
+ };
2011
+ if ((tab.pages || tab.groups) && !uniqueTab.pages?.length && !uniqueTab.groups?.length && !tab.href && !tab.api) continue;
1957
2012
  if (!names.has(tab.tab.toLowerCase())) {
1958
2013
  tabs.push(uniqueTab);
1959
2014
  names.add(tab.tab.toLowerCase());
1960
2015
  continue;
1961
2016
  }
1962
2017
  const target = tabs.find((item) => item.tab.toLowerCase() === tab.tab.toLowerCase());
1963
- if (!target || !uniqueTab.groups) continue;
1964
- target.groups = [...target.groups ?? [], ...uniqueTab.groups];
2018
+ if (!target) continue;
2019
+ const incomingRootNodes = [
2020
+ ...uniqueTab.pages ?? [],
2021
+ ...uniqueTab.groups ?? []
2022
+ ];
2023
+ if (uniqueTab.pages?.length) {
2024
+ target.pages = [...target.pages ?? target.groups ?? [], ...incomingRootNodes];
2025
+ delete target.groups;
2026
+ } else if (uniqueTab.groups?.length) {
2027
+ if (target.pages) target.pages = [...target.pages, ...uniqueTab.groups];
2028
+ else target.groups = [...target.groups ?? [], ...uniqueTab.groups];
2029
+ }
1965
2030
  }
1966
2031
  const changelog = existing.tabs.find((tab) => tab.tab.toLowerCase() === "changelog") ?? incoming.tabs.find((tab) => tab.tab.toLowerCase() === "changelog");
1967
2032
  if (changelog) tabs.push(changelog);
@@ -1975,6 +2040,9 @@ function mergeMigrationConfig(existing, incoming) {
1975
2040
  ...incoming,
1976
2041
  ...existing,
1977
2042
  tabs,
2043
+ // Collection presentation belongs to the imported information
2044
+ // architecture; starter defaults must not coerce source dropdowns to tabs.
2045
+ navigation: incoming.navigation ?? existing.navigation,
1978
2046
  ...i18n ? { i18n } : {}
1979
2047
  };
1980
2048
  }
@@ -1994,7 +2062,7 @@ function renderMigrationFiles(bundle, options = {}) {
1994
2062
  // src/url.ts
1995
2063
  import { load } from "cheerio";
1996
2064
  import TurndownService from "turndown";
1997
- import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
2065
+ import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
1998
2066
  var DEFAULT_MAX_PAGES = 1e3;
1999
2067
  var DEFAULT_MAX_TOTAL_BYTES = 1e8;
2000
2068
  var MAX_DISCOVERED_URLS = 5e3;
@@ -2556,7 +2624,7 @@ function extractEmbeddedOpenApi(content) {
2556
2624
  const match = fencePattern.exec(content);
2557
2625
  if (!match) return { content };
2558
2626
  try {
2559
- const parsed = parseYaml2(match[5], { maxAliasCount: 50 });
2627
+ const parsed = parseYaml3(match[5], { maxAliasCount: 50 });
2560
2628
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
2561
2629
  const document = parsed;
2562
2630
  if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
@@ -3025,15 +3093,17 @@ function htmlTopLevelNavigation(document, source, scopePath) {
3025
3093
  }
3026
3094
  function remapNavigationHome(config, sourceHomePageId) {
3027
3095
  if (!sourceHomePageId || sourceHomePageId === "introduction") return config;
3096
+ const remapPages = (pages) => pages.map((page) => typeof page === "string" ? page === sourceHomePageId ? "introduction" : page : { ...page, pages: remapPages(page.pages) });
3028
3097
  const remapGroups = (groups) => groups.map((group) => ({
3029
3098
  ...group,
3030
- pages: group.pages.map((page) => typeof page === "string" ? page === sourceHomePageId ? "introduction" : page : remapGroups([page])[0])
3099
+ pages: remapPages(group.pages)
3031
3100
  }));
3032
3101
  return {
3033
3102
  ...config,
3034
3103
  tabs: config.tabs.map((tab) => ({
3035
3104
  ...tab,
3036
3105
  ...tab.href === `/${sourceHomePageId}` ? { href: "/" } : {},
3106
+ ...tab.pages ? { pages: remapPages(tab.pages) } : {},
3037
3107
  ...tab.groups ? { groups: remapGroups(tab.groups) } : {}
3038
3108
  }))
3039
3109
  };
@@ -3352,12 +3422,8 @@ async function migrateUrl(options) {
3352
3422
  const openApiAsset = mergeEmbeddedOpenApi(openApiFragments);
3353
3423
  if (openApiAsset) {
3354
3424
  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];
3425
+ const containsOperation = (navigationPages = []) => navigationPages.some((page) => typeof page === "string" ? operationPageIds.has(page) : containsOperation(page.pages));
3426
+ const apiTab = docsConfig.tabs.find((tab) => containsOperation(tab.pages) || containsOperation(tab.groups)) ?? docsConfig.tabs[0];
3361
3427
  if (apiTab) {
3362
3428
  apiTab.api = { source: "/openapi.yaml", navigation: false };
3363
3429
  }