@thallylabs/migrate 0.2.6 → 0.2.8

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
@@ -15,10 +15,113 @@ compatible page frontmatter, site identity, and OpenAPI configuration. For publi
15
15
  sites, the URL adapter reads the structured configuration embedded in the
16
16
  rendered application before falling back to DOM link inference.
17
17
 
18
+ An `openapi` (or `asyncapi`) reference is resolved wherever Mintlify allows
19
+ it — the top-level `api.openapi`, or on any individual
20
+ tab/anchor/dropdown/product/version, e.g. `navigation.tabs[].openapi` — and
21
+ binds to that exact tab, not only the first one found. Remote `http://` and
22
+ `https://` specs are not downloaded during migration. The report names each
23
+ remote URL and tells you to add its spec to `public/`, then set the API source
24
+ in `docs.json` to that local path. Missing local specs also produce warnings
25
+ instead of silently dropping that API tab's reference. AsyncAPI has no Thally
26
+ renderer, so it always produces a clear warning naming the spec instead of
27
+ disappearing.
28
+
18
29
  Docusaurus sidebars are parsed as bounded, data-only JSON5—never executed—and
19
30
  preserve explicit categories, autogenerated slices, document IDs/slugs,
20
31
  positions, generated indexes, admonitions, tabs, and `static/` assets where
21
- supported.
32
+ supported. Every `@docusaurus/plugin-content-docs` instance declared in
33
+ `docusaurus.config` (not just the default one) is imported as its own tab,
34
+ routed under its `routeBasePath`; a `path` is resolved the way Docusaurus
35
+ itself resolves it (relative to the detected project root), with a fallback
36
+ to the repository root for monorepos that build by copying the project
37
+ root's contents up into the repository root before generating the site
38
+ (Playwright's own `nodejs/` is one real example) — a `path` still not found
39
+ under either root is reported with a warning naming the plugin instance
40
+ instead of being silently dropped. The same repository-root fallback applies
41
+ to `@site/...` aliases, relative component imports, and relative asset
42
+ `require()` calls in copied components; a `require()` that still can't be
43
+ resolved is replaced with an empty string (a broken image, not a build
44
+ failure) and warned about instead of left dangling. `@docusaurus/plugin-client-redirects`
45
+ rules are imported whether declared inline in `docusaurus.config` or in a
46
+ separate module it imports (a named export, e.g. `redirects.ts`); a `from`
47
+ may be a single path or an array of paths. Its `createRedirects` function
48
+ computes redirects dynamically and can't be evaluated statically, so its
49
+ presence is reported with a warning instead of being silently ignored. A
50
+ static `--ifm-color-primary` accent color in the classic theme's `customCss`
51
+ file is imported the same way Mintlify's and Fern's theme colors are.
52
+ Versioned docs under `versioned_docs/` and translated docs under
53
+ `i18n/<locale>/docusaurus-plugin-content-docs/` are imported with their
54
+ navigation, including locale-prefixed routes such as `/fr/...`. Each version
55
+ and locale keeps its own route tree; sidebar entries without source documents
56
+ are reported.
57
+ Docusaurus heading ID comments such as `{/* #custom-id */}` become explicit
58
+ HTML anchors so links to those headings keep working after migration. Links
59
+ using Docusaurus' implicit heading slugs also get explicit aliases when the
60
+ linked heading or option-table field can be matched uniquely. Repeated option
61
+ fields use the nearest preceding row when all local references agree;
62
+ otherwise ambiguous anchors remain warnings.
63
+ Longer fenced code examples keep nested shorter fences inert. An archived
64
+ admonition missing its closing delimiter is closed before the next top-level
65
+ heading (or at the end of the page) so its remaining content stays importable.
66
+ Docusaurus `mdx-code-block` wrappers are opened as live MDX when the resulting
67
+ page compiles and its special fences contain no module declarations. Pages
68
+ whose fragments still need Docusaurus theme imports or remark plugins remain
69
+ fenced and are retained.
70
+
71
+ Fern repositories are located by their `fern/docs.yml` + `fern/fern.config.json`
72
+ pair. `docs.yml` is parsed as bounded YAML—never executed—and preserves tabs,
73
+ nested sections (including `slug`/`skip-slug`), navbar links, redirects, the
74
+ default Fern version's navigation, and theme accent colors. Only pages
75
+ referenced from `docs.yml` are imported, matching Fern's own publishing model.
76
+ Versioned `folder:` entries expand the Markdown files they contain, even when
77
+ the folder or an individual page is outside `fern/` but still inside the
78
+ checkout. An explicit empty tab `slug` keeps pages at the site root; a folder
79
+ title changes its navigation label without changing its directory-based URL.
80
+ Internal links are adjusted for the site's domain path, selected version, and
81
+ source-file routes. Links to sections not imported into Thally stay on the
82
+ published Fern site and produce a migration warning, so the generated site can
83
+ build while the remaining content is moved separately.
84
+ Every `api:` section's OpenAPI document is resolved from its own
85
+ `generators.yml` (`api.specs[].openapi`, or the legacy `api:` string) and
86
+ imported into its own tab — a docs.yml with several API sections (e.g. a REST
87
+ API and a WebSocket API in separate tabs) gets a spec per tab, not just the
88
+ first. The per-API `fern/apis/<name>/` layout is checked first, keyed by the
89
+ node's `api-name` (the folder name) rather than `api` (its display title in
90
+ the nav) — the two are commonly different. The project-root `generators.yml`
91
+ is used only when the node has no `api-name` or the repo has no `fern/apis/`
92
+ folder. `openapi:` is conventionally relative to `generators.yml`'s own
93
+ directory rather than the Fern root, so it commonly resolves outside the API
94
+ folder (or outside `fern/` entirely, e.g. `../../../openapi.yaml`); the spec
95
+ is still found as long as it stays inside the repository checkout, and a path
96
+ that escapes the checkout is reported with a warning naming it, instead of
97
+ the API being silently dropped. A Fern Definition (a non-OpenAPI API with no
98
+ document to attach), an AsyncAPI/OpenRPC-only `api:` section (Thally has no
99
+ renderer for either), and an `api:` section for which no spec could be
100
+ resolved at all, are each reported as a warning naming the API instead of the
101
+ tab being silently dropped, as is `changelog`, which has no equivalent Thally
102
+ field yet.
103
+
104
+ A section/page/tab's explicit `slug` is used as literal Fern routing text
105
+ (e.g. `slug: baml_client`, matching a `baml_client/` folder) even when
106
+ Thally's own route still hyphenates it for consistency (`baml-client`); an
107
+ in-body link written against that literal, unslugified form — which Fern's
108
+ own file-path-based routing tolerates — gets a redirect to the route Thally
109
+ actually serves, instead of 404ing after migration.
110
+
111
+ KaTeX-style `$$...$$` math (block or inline) has no Thally renderer yet.
112
+ Rather than crashing the MDX compiler and silently excluding the whole page,
113
+ a block becomes a fenced ```` ```math ```` code block and an inline span
114
+ becomes an inline code span — both preserve the original math text — with a
115
+ warning.
116
+
117
+ A `products:` docs.yml (Fern's multi-product/product-switcher layout, as on
118
+ buildwithfern.com/learn) imports every product: each product's own
119
+ `navigation:`/`tabs:` becomes a top-level Thally tab named by its
120
+ `display-name`, routed under its `slug` (or a slugified display-name), with
121
+ its pages read from that product's own directory. Local component imports
122
+ from Fern MDX pages (`../../components/...`, resolved against the Fern
123
+ project root) are migrated the same way Mintlify and Docusaurus components
124
+ are — see "Mintlify compatibility" below.
22
125
 
23
126
  ## Mintlify compatibility
24
127
 
@@ -35,12 +138,90 @@ The importer creates a root redirect to the first available page in source
35
138
  navigation when no root introduction exists, while retaining a separate custom
36
139
  homepage and explicit redirects. Locale tags and recognized locale-prefixed
37
140
  sections are normalized without discarding original URL redirects.
141
+ Mintlify's generated `param-*` links for settings headings and named fields
142
+ become explicit anchors in migrated MDX.
38
143
 
39
144
  Simple interactive HTML blocks in MDX are extracted into client components.
145
+ For a copied client component that receives a simple page-authored JSX callback,
146
+ the importer moves the callback into a client wrapper and keeps the page. If
147
+ the callback cannot be moved safely, it keeps the page and the control's child
148
+ content as a passive fallback, with a warning identifying the control. A
149
+ stateful local snippet component is kept as a client component instead of
150
+ flattening away its hooks and state.
40
151
  The Mintlify `search-bar-entry` click trigger is mapped to Thally's search
41
- shortcut. Markdown mixed inside interactive JSX, computed imports, namespace
42
- imports, and external packages that require installation are reported for
43
- manual review. Unsupported source is preserved, not replaced with empty stubs.
44
- File paths cannot escape the documentation root; symlinks and oversized graphs
45
- are rejected. This is compatibility analysis, **not a code sandbox**: imported
46
- JavaScript executes when the developer builds or runs the resulting site.
152
+ shortcut. Markdown mixed inside interactive JSX, computed imports, and
153
+ namespace imports are reported for manual review; unsupported source is
154
+ preserved, not replaced with empty stubs. An external package's JSX usage is
155
+ dropped and replaced with a safe fallback (with a warning); if that package's
156
+ binding is instead referenced outside JSX (an expression, a prop, or an
157
+ inline declaration) the import can't be rewritten the same way, so the whole
158
+ page is excluded — instead of shipping with an import `next build` can't
159
+ resolve — and pruned from navigation, with a warning naming the page, the
160
+ package, and the binding. A local/relative component import is fully owned by
161
+ the migration instead: the whole file (and its own local dependency graph) is
162
+ copied, keeping every binding's original name, so it makes no difference
163
+ whether a binding is used as a bare JSX tag or only referenced elsewhere (an
164
+ enum passed into a sibling component's prop, a member tag, a wrapping
165
+ declaration, ...) — the page is excluded only when the copy itself genuinely
166
+ fails (a missing file, a computed/dynamic dependency, a cycle, or a budget
167
+ overrun), with a warning naming the page and the component. Packages the starter already installs (`next`,
168
+ `react-dom`, `clsx`, `lucide-react`, `tailwind-merge`, and their subpaths) are
169
+ kept as-is (except `react-dom/server`, whose string renderers throw under
170
+ Next) and copied into any extracted client module. A source site's own
171
+ `@/…` path aliases are treated like an unavailable package. A page that
172
+ passes a page-authored function into a client component needs the client
173
+ wrapper described above; unsupported callback shapes retain their content
174
+ with a warning. An unconfirmed target is warned for review.
175
+ File paths cannot escape the repository checkout; symlinks and oversized
176
+ graphs are rejected. This is compatibility analysis, **not a code sandbox**:
177
+ imported JavaScript executes when the developer builds or runs the resulting
178
+ site.
179
+
180
+ A copied Mintlify `.jsx` snippet that uses the implicit `MintlifyComponents`
181
+ global (`const { Card } = MintlifyComponents`, Mintlify's own no-import
182
+ convention for its built-ins) gets that binding provided automatically,
183
+ reusing Thally's own built-in MDX component registry — the same way an
184
+ implicit `React`/hook global is already provided.
185
+
186
+ A copied component is prepended with `// @ts-nocheck`: the source
187
+ platform's own build never ran `next build`'s full `tsc --noEmit` type
188
+ check, so loose types, ambient globals (`window.ethereum`), and implicit
189
+ `any` parameters that were fine there would otherwise fail the migrated
190
+ build. A locally-owned wrapper component whose own body imports a known
191
+ YouTube-embed package (e.g. a custom `LiteYouTube` wrapping
192
+ `react-lite-youtube-embed`) gets the same working `<iframe>` fallback as a
193
+ page that imports that package directly, instead of losing the embed to the
194
+ generic unknown-component fallback.
195
+
196
+ ## Branding
197
+
198
+ A source site's logo, favicon, and theme accent color(s) are extracted where
199
+ each platform's config exposes them (Mintlify's `colors`, Fern's
200
+ `colors.accent-primary`, Docusaurus's classic theme `customCss`). A valid
201
+ accent color is wired into the scaffolded project's `brandPreset` the same
202
+ way `create-thally-docs`'s own `--colors` flag does. Logo and favicon files
203
+ are copied into `public/`, but Thally's site configuration has no static
204
+ field for them — branding is managed from the admin dashboard at runtime —
205
+ so each site gets one warning naming the copied files and pointing at the
206
+ dashboard's branding settings instead of leaving the loss silent.
207
+
208
+ ## Cloning a GitHub repository
209
+
210
+ `cloneGitHubRepository` retries a transient network-class failure with
211
+ backoff, and each underlying git process (the clone, and any submodule
212
+ fetch) is killed and treated as a retryable failure if it stalls — a
213
+ generous 10-minute default, configurable via
214
+ `THALLY_MIGRATE_CLONE_TIMEOUT_MS` (milliseconds) for an unusually large
215
+ repository on a slow link. The clone neutralizes the `filter.lfs.*`
216
+ smudge/clean/process filter driver for that one process only (via
217
+ `GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n`, never the user's
218
+ global git config), so a repository that uses Git LFS still clones even when
219
+ the host has no `git-lfs` binary installed — including a host where
220
+ `filter.lfs.*` is still registered globally from a previous install,
221
+ pointing at a command that no longer exists; an asset that's still a Git
222
+ LFS pointer file afterward (not its real content) is skipped with a warning
223
+ instead of being copied as if it were the real file. Submodules are
224
+ initialized one at a time, best-effort,
225
+ after a successful clone — a submodule that can't be fetched (private,
226
+ deleted, network trouble) is named in a warning instead of failing the
227
+ whole migration.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * import paths. Callers materialize this bundle only after discovery and
4
4
  * normalization finish, so a partial crawl can never leave a half-written site.
5
5
  */
6
- type MigrationPlatform = 'mintlify' | 'docusaurus' | 'gitbook' | 'nextra' | 'vitepress' | 'starlight' | 'thally' | 'generic' | 'unknown';
6
+ type MigrationPlatform = 'mintlify' | 'docusaurus' | 'fern' | 'gitbook' | 'nextra' | 'vitepress' | 'starlight' | 'thally' | 'generic' | 'unknown';
7
7
  interface MigrationNavigationGroup {
8
8
  group: string;
9
9
  icon?: string;
@@ -154,6 +154,18 @@ interface MigrationBundle {
154
154
  site?: {
155
155
  name?: string;
156
156
  description?: string;
157
+ /**
158
+ * Source theme accent color(s), each a `#rrggbb`/`#rgb` hex string.
159
+ * Follows Mintlify's own `colors` schema: `light` is the color used in
160
+ * dark mode and `dark` is the color used in light mode. Fern's colors
161
+ * are normal (its `light`/`dark` match the mode they paint), so
162
+ * extraction swaps them onto this shape for a single downstream contract.
163
+ */
164
+ colors?: {
165
+ primary?: string;
166
+ light?: string;
167
+ dark?: string;
168
+ };
157
169
  };
158
170
  warnings: Array<MigrationWarning>;
159
171
  stats: {
@@ -182,9 +194,13 @@ interface RenderedMigrationFile {
182
194
  }
183
195
 
184
196
  /**
185
- * Repository adapter for the shared migration engine. Traversal is bounded,
186
- * symbolic links are ignored, and Git is always invoked with an argument array
187
- * so source-controlled branch names can never become shell commands.
197
+ * Repository adapter for the shared migration engine. Traversal is bounded;
198
+ * `scanFiles` follows a symbolic link only when its resolved real path stays
199
+ * inside the repository checkout (this covers a submodule mounted as a
200
+ * symlink, e.g. Oasis's `docs/core -> ../external/oasis-core/docs`) and
201
+ * guards against a cycle, but every other directory walk in this file still
202
+ * skips symlinks outright. Git is always invoked with an argument array so
203
+ * source-controlled branch names can never become shell commands.
188
204
  */
189
205
 
190
206
  interface GitHubRepositorySource {
@@ -205,13 +221,30 @@ interface RepositoryMigrationOptions {
205
221
  docusaurusSkipPlugins?: boolean;
206
222
  /** @internal Additional docs plugins default to autogenerated navigation. */
207
223
  docusaurusSkipSidebar?: boolean;
224
+ /** @internal A version's sidebar is parsed as static data. */
225
+ docusaurusSidebarPath?: string;
208
226
  /** @internal Static assets are shared across docs-plugin instances. */
209
227
  docusaurusSkipAssets?: boolean;
228
+ /** @internal Redirects are global config, read once, not per plugin instance. */
229
+ docusaurusSkipRedirects?: boolean;
210
230
  }
211
231
  /** Parse and validate a public GitHub repository URL. */
212
232
  declare function parseGitHubRepositoryUrl(rawUrl: string): GitHubRepositorySource;
213
- /** Clone a repository without a shell; callers own and remove `targetDir`. */
214
- declare function cloneGitHubRepository(source: GitHubRepositorySource, targetDir: string): Promise<void>;
233
+ /** Every submodule path declared in a cloned repository's `.gitmodules`, in file order. */
234
+ declare function gitmodulePaths(targetDir: string): Array<string>;
235
+ /**
236
+ * Clone a repository without a shell; callers own and remove `targetDir`. A
237
+ * large repository on a flaky connection can drop mid-clone (`RPC failed`,
238
+ * `Recv failure`, a `curl 56`, …) — retry a network-class failure a couple
239
+ * of times with backoff instead of surfacing a hard failure on the first
240
+ * blip. A partial checkout from the failed attempt is removed first, or
241
+ * `git clone` refuses to reuse the (now non-empty) target directory. A
242
+ * stalled clone/submodule-init is killed by `runGit`'s own timeout, which
243
+ * surfaces as a retryable error. `warnings`, if given, collects a
244
+ * submodule-fetch-failure warning (this function otherwise returns exactly
245
+ * as before, so existing callers are unaffected).
246
+ */
247
+ declare function cloneGitHubRepository(source: GitHubRepositorySource, targetDir: string, warnings?: Array<MigrationWarning>): Promise<void>;
215
248
  /** Detect a supported repository docs platform from unambiguous config files. */
216
249
  declare function detectRepositoryPlatform(repositoryDir: string, docsDir?: string): MigrationPlatform;
217
250
  /** Pick the first conventional content root containing Markdown. */
@@ -257,6 +290,65 @@ interface GeneratedNavigationOptions {
257
290
  }
258
291
  /** Build deterministic fallback navigation from imported default-locale pages. */
259
292
  declare function buildNavigationFromPages(pages: Array<MigrationPage>, options?: GeneratedNavigationOptions): MigrationDocsConfig;
293
+ /**
294
+ * A source page can be excluded after navigation is projected (invalid MDX,
295
+ * a client-boundary function prop, a duplicate id). Its reference still sits
296
+ * in the projected tabs/groups at that point, across every tab, group, and
297
+ * locale, since the nav tree and the page list are built from the same
298
+ * source config independently. Drop those dangling references here so
299
+ * `thally check` never reports a nav entry with no backing MDX file, and
300
+ * drop any group left with no pages as a result. Shared by every platform's
301
+ * navigation projection (Mintlify, Fern, Docusaurus).
302
+ */
303
+ declare function pruneMissingNavigationPages(config: MigrationDocsConfig, availableIds: ReadonlySet<string>): MigrationDocsConfig;
304
+
305
+ /**
306
+ * Static Fern (`fern/docs.yml`) repository projection. Fern navigation and
307
+ * generator config are plain YAML data files, so this adapter only ever
308
+ * parses them with `yaml` and never imports or executes JavaScript.
309
+ */
310
+
311
+ interface FernPageDescriptor {
312
+ /** Path to the referenced page file, relative to the Fern root. */
313
+ sourcePath: string;
314
+ navigationId: string;
315
+ }
316
+ /** One `api:` navigation node, resolved to the name/tab Thally needs to bind its spec. */
317
+ interface FernApiSection {
318
+ /** The node's own value (a display name or, in multi-API repos, a `fern/apis/<name>` identifier). */
319
+ name: string;
320
+ /** True when `name` came from an explicit `api-name` field rather than falling back to the `api:` display title. */
321
+ nameExplicit: boolean;
322
+ /** Label of the tab that owns this node, if that tab also has other content and survived projection. */
323
+ tabLabel?: string;
324
+ /**
325
+ * Route segments (section slugs) leading to this node, used only to
326
+ * disambiguate two `api:` nodes that land on the same `tabLabel` — a
327
+ * "Production API Reference" and "Testnet API Reference" section both
328
+ * nested under one tab both want that tab's label; without this, the
329
+ * second one silently overwrites the first tab's binding instead of
330
+ * getting its own tab.
331
+ */
332
+ routeSegments: Array<string>;
333
+ }
334
+ interface FernNavigationResult {
335
+ docsConfig: MigrationDocsConfig;
336
+ descriptors: Array<FernPageDescriptor>;
337
+ warnings: Array<MigrationWarning>;
338
+ /** Every `api:` node found in navigation, in document order. A repo can declare several (e.g. a REST and a WebSocket API in separate tabs). */
339
+ apiSections: Array<FernApiSection>;
340
+ }
341
+ /** Read `fern/docs.yml` without executing anything. */
342
+ declare function readFernConfig(fernRoot: string): {
343
+ config: Record<string, unknown>;
344
+ sourcePath: string;
345
+ } | null;
346
+ /** Project a Fern `docs.yml` navigation tree into Thally tabs/groups. */
347
+ declare function projectFernNavigation(input: {
348
+ config: Record<string, unknown>;
349
+ fernRoot: string;
350
+ repositoryRoot?: string;
351
+ }): FernNavigationResult;
260
352
 
261
353
  /** Markdown/MDX normalization that preserves every component Thally supports. */
262
354
 
@@ -266,7 +358,7 @@ interface MarkdownPageIdentity {
266
358
  locale?: string;
267
359
  }
268
360
  /** Normalize only syntax Thally cannot render; supported source JSX stays intact. */
269
- declare function normalizeMdx(body: string): string;
361
+ declare function normalizeMdx(body: string, platform?: MigrationPlatform, unwrapMdxCodeBlocks?: boolean): string;
270
362
  /** Parse source Markdown or MDX into the canonical page representation. */
271
363
  declare function parseMarkdownPage(input: {
272
364
  id: string;
@@ -274,6 +366,8 @@ declare function parseMarkdownPage(input: {
274
366
  locale?: string;
275
367
  raw: string;
276
368
  source: string;
369
+ /** Gates platform-specific textual renames in `normalizeMdx`; omitted runs them all. */
370
+ platform?: MigrationPlatform;
277
371
  /** Resolve platform-specific routes from the already-parsed frontmatter. */
278
372
  resolveIdentity?: (frontmatter: Record<string, unknown>, fallback: MarkdownPageIdentity) => MarkdownPageIdentity;
279
373
  }): MigrationPage | null;
@@ -310,4 +404,4 @@ declare const defaultMigrationFetcher: MigrationFetcher;
310
404
  /** Crawl one public docs origin into a canonical, caller-independent bundle. */
311
405
  declare function migrateUrl(options: UrlMigrationOptions): Promise<MigrationBundle>;
312
406
 
313
- export { type GitHubRepositorySource, type MigrationAsset, type MigrationBundle, type MigrationDocsConfig, type MigrationFetchRequest, type MigrationFetchResponse, type MigrationFetcher, type MigrationNavigationGroup, type MigrationNavigationTab, type MigrationPage, type MigrationPlatform, type MigrationWarning, type RenderedMigrationFile, type RepositoryMigrationOptions, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, mergeMigrationConfig, migrateRepository, migrateUrl, normalizeMdx, parseGitHubRepositoryUrl, parseMarkdownPage, projectMintlifyNavigation, readMintlifyConfig, renderMigrationFiles, validateMigrationUrl };
407
+ export { type GitHubRepositorySource, type MigrationAsset, type MigrationBundle, type MigrationDocsConfig, type MigrationFetchRequest, type MigrationFetchResponse, type MigrationFetcher, type MigrationNavigationGroup, type MigrationNavigationTab, type MigrationPage, type MigrationPlatform, type MigrationWarning, type RenderedMigrationFile, type RepositoryMigrationOptions, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, gitmodulePaths, mergeMigrationConfig, migrateRepository, migrateUrl, normalizeMdx, parseGitHubRepositoryUrl, parseMarkdownPage, projectFernNavigation, projectMintlifyNavigation, pruneMissingNavigationPages, readFernConfig, readMintlifyConfig, renderMigrationFiles, validateMigrationUrl };