@thallylabs/migrate 0.2.42 → 0.2.44

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
@@ -128,6 +128,11 @@ versions, languages, and reusable `$ref` navigation files; it also preserves
128
128
  dropdown presentation, collection descriptions and icons, explicit default
129
129
  versions, page route casing, redirects, local snippets, static assets,
130
130
  compatible page frontmatter, site identity, and OpenAPI configuration.
131
+ A page with no `description`, other than one that names an API operation, gets
132
+ one from its first paragraph, which keeps it for search and metadata but marks it
133
+ `descriptionPlacement: body`, so the runtime does not print the same sentence under
134
+ the title and again in the body; an authored description is shown under the title as
135
+ written. (Docusaurus and Fern pages are always marked `body`.)
131
136
  Analytics come across too: docs.json `integrations` and legacy mint.json
132
137
  `analytics` for GA4, Google Tag Manager, PostHog, and Plausible are validated
133
138
  and written to `docs.json` `integrations` (`integrations` wins per provider when
@@ -135,7 +140,17 @@ both exist). Invalid values are skipped with a warning, and every other
135
140
  Mintlify provider (Amplitude, Mixpanel, Segment, and so on) is listed once in a
136
141
  warning instead of being dropped silently. For public Mintlify
137
142
  sites, the URL adapter reads the structured configuration embedded in the
138
- rendered application before falling back to DOM link inference.
143
+ rendered application before falling back to DOM link inference. A navigation
144
+ entry's `tag` label and `boost` search weight have no Thally counterpart: one
145
+ warning for each says how many entries had it.
146
+
147
+ A group with a `root` page and a `directory` of `card` or `accordion` (set on
148
+ the group or inherited from a group above it; `none` turns it off) ends its root
149
+ page with the group's pages as cards, as Mintlify's page directory does: the
150
+ group's own pages first, in order, then each nested group, as a label with its
151
+ cards (a group nested deeper becomes one card that gives its page count). A
152
+ hidden or access-restricted page is left out and named in a warning, and
153
+ `accordion` is shown as cards with a warning.
139
154
 
140
155
  An `openapi` (or `asyncapi`) reference is resolved wherever Mintlify allows
141
156
  it — the top-level `api.openapi`, or on any individual
@@ -146,13 +161,24 @@ a tab `menu` (string or `{ source, directory }`) becomes its own API tab named
146
161
  only content it binds to the tab itself. The item's `icon` and `hidden` carry
147
162
  onto that tab, and a `directory` output folder is not used (a warning says so:
148
163
  endpoint pages are generated under `/api/`). An item Thally cannot represent
149
- produces a warning naming it. Specs that share a file name across folders are
164
+ produces a warning naming it. A top-level `api.openapi` that no navigation entry
165
+ lists and that pages name in their `openapi:` frontmatter (a bare `GET /x` names
166
+ it only when it is the site's one spec) gets no API tab, because Mintlify
167
+ publishes just those pages: the spec is bound to a hidden tab (`OpenAPI: <spec>`)
168
+ of its own that the pages' endpoints resolve against. It never binds to an
169
+ existing tab whose label merely contains "API" or is the same as its own (an
170
+ authored tab named `OpenAPI: <spec>` is an ordinary tab), and no other spec ever
171
+ takes that tab over (one that cannot share a tab is left out with a warning, never
172
+ swapped in for the first). A spec no page names keeps its API tab. Specs that share a file name across folders are
150
173
  kept as separate files in `openapi/`. A remote `https://` spec is not
151
174
  downloaded by `migrateRepository`; the bundle lists it and warns, and
152
175
  `hydrateRemoteApiSpecs` (which `create-thally-docs migrate` runs after the
153
- conversion) downloads it from a public host, up to 25 MB, saves it as `.json`
154
- or `.yaml` by its content rather than its URL, and binds it to its tab. A
155
- download that fails leaves a warning naming the URL. An `http://` spec is never
176
+ conversion) downloads it from a public host, up to 25 MB once decompressed (it
177
+ asks for gzip), saves it as `.json` or `.yaml` by its content rather than its
178
+ URL, and binds it to its tab. A download is abandoned after 30 seconds without
179
+ data or 5 minutes in all (one clock for the whole redirect chain, the DNS lookup
180
+ of every hop included, so a stalled lookup cannot hold it past the limit),
181
+ however slowly the bytes arrive. A download that fails leaves a warning naming the URL. An `http://` spec is never
156
182
  imported. Missing local specs also produce warnings
157
183
  instead of silently dropping that API tab's reference. AsyncAPI has no Thally
158
184
  renderer, so it always produces a clear warning naming the spec instead of
@@ -191,11 +217,12 @@ operations still have their pages. This is separate from a `directory`'s flat
191
217
  operation list, which still only drives the endpoint redirects.
192
218
 
193
219
  Mintlify serves one generated page per operation a navigation container lists,
194
- at `/<directory>/<first tag>/<summary>` (`directory` is the reference's
195
- `{ source, directory }` folder, `api-reference` when it is a plain string).
220
+ at `/<directory>/<first tag>/<title>` (`directory` is the reference's
221
+ `{ source, directory }` folder, `api-reference` when it is a plain string; an
222
+ untagged operation has no tag folder).
196
223
  Each such operation becomes an MDX page at that path under `src/content`, so it
197
224
  keeps its URL, sidebar entry, eyebrow and previous/next links. Its frontmatter is
198
- the summary as `title` (else the operation id, else `METHOD /path`), the first
225
+ the summary as `title` (else `<Method> <words of the path>`, never the operation id), the first
199
226
  sentence of the description as `description` (the title when there is none), and
200
227
  `openapi: "<spec file> METHOD /path"`; the body is empty. `descriptionPlacement: operation`
201
228
  keeps that short summary for search and metadata while the runtime renders the
@@ -224,10 +251,23 @@ the spec has none of the listed operations, or the tab is bound to another spec.
224
251
  A reference that lists no operation (Mintlify generated every endpoint) is not
225
252
  affected. Operations whose path item is a local `$ref` are read as the renderer
226
253
  reads them (hidden or excluded on any level of the reference withholds them).
254
+
255
+ An authored page that names an operation (`openapi: post /links`) or a webhook
256
+ (`openapi: webhook link.created`, read from the spec's `webhooks`) and sets neither
257
+ a `title` nor a leading heading is titled by that operation, as Mintlify titles it:
258
+ the summary, else the operation id, else `METHOD /path` (`POST link.created` for a
259
+ webhook), where its file name would otherwise give it "Create" for `links/create.mdx`. A page that names its spec reads
260
+ that spec; a bare reference reads the first spec that publishes the operation, so
261
+ when a site has a remote spec, `hydrateRemoteApiSpecs` titles these pages once every
262
+ spec is bound. A page whose operation no spec publishes (a remote spec that failed to
263
+ download, say) keeps its file name title. `sidebarTitle` still becomes
264
+ `navTitle`, and an authored title always wins.
227
265
  The renderer tries the first visible API tab's spec first for a bare
228
266
  `openapi: "METHOD /path"` page; when converting a tab to pages stops its spec being
229
267
  that one, each bare reference to an operation the converted spec defines is
230
268
  written with that spec's path in front, so it still renders the same operation.
269
+ A spec a tab rejects (Thally binds one API spec per tab) has no binding, so no
270
+ reference is moved to it.
231
271
 
232
272
  Mintlify `styling.eyebrows: "breadcrumbs"` becomes `eyebrow: "path"` (the full
233
273
  group path above the page title); `"section"` or no setting writes nothing.
@@ -258,10 +298,10 @@ no operation points to that section's overview page instead, and one warning
258
298
  lists the affected links (up to ten). A link to an address the site answers
259
299
  itself (a guide authored inside an API directory, trailing slash or `/index` or
260
300
  not, or the source of an authored redirect) is never rewritten. A spec that
261
- Mintlify mounted under a `directory` also gets a redirect from each endpoint URL that directory served
262
- (not the operations a container lists, which are pages at those URLs),
263
- to the matching endpoint page, and never to an operation the published spec
264
- withholds with `x-hidden` or `x-excluded`. A remote spec gets every directory
301
+ Mintlify mounted under a `directory` also gets a redirect from each endpoint URL
302
+ that directory served (not the operations a container lists, which are pages at
303
+ those URLs), to the matching endpoint page, and never to an operation the
304
+ published spec withholds with `x-hidden` or `x-excluded`. A remote spec gets every directory
265
305
  each of its tabs mounted it under, limited to the operations the navigation
266
306
  listed under a directory when it listed any. A local spec gets one directory
267
307
  per tab (the first) and every operation of the spec. When directories nest
@@ -274,12 +314,21 @@ id its spec is served under), so they stay as authored until then. A remote
274
314
  directory whose download failed still owns the addresses under it: they stay as
275
315
  authored, never rewritten or redirected. A link under any
276
316
  directory reaches only that directory's own endpoint URLs; a link under none is
277
- redirected by its last two segments (tag and summary) only when exactly one
317
+ redirected by its last two segments (tag and title, as they appear in that URL) only when exactly one
278
318
  published operation across every spec carries them, to the id its spec is
279
319
  served under. A Mintlify default version's pages also get a redirect from their
280
320
  address without the version segment, since the original site serves them both
281
321
  ways.
282
322
 
323
+ An endpoint URL Mintlify generates is `<directory>/<first tag>/<title>`: the
324
+ title is the summary, else `<Method> <words of the path>` (the operation id is
325
+ never used), an untagged operation has no tag folder, and an operation whose URL
326
+ repeats an earlier one's gets `-1`, `-2` and so on after it, in the order the
327
+ spec lists them. A navigation container (a group, tab or other) that names a
328
+ spec without listing any of its operations had every one of them generated, under
329
+ `api-reference` when it names no `directory`, so such a spec is mounted there
330
+ too; the top-level `api` setting generates nothing.
331
+
283
332
  Redirects are checked once, after every platform and step has added its own.
284
333
  Next.js applies a redirect before it looks for a page and compares a source
285
334
  without regard to a trailing slash, so a redirect whose source is a page of the
@@ -302,7 +351,14 @@ URLs case-sensitively. Next.js reads a redirect source as a route pattern, so on
302
351
  whose address has a `+ ? ( ) [ ] { }` character (a `directory` such as
303
352
  `sdk/c++`) would fail the build; every redirect from authored config, a local
304
353
  spec or a downloaded spec is left out instead, and one warning lists the first
305
- ten of them.
354
+ ten of them. A source that ends in a catch-all `/:name*` also matches the path
355
+ without that segment, so when that path is a page of the site, or is where the
356
+ redirect itself goes (`/docs/sdks/typescript/:path*` to `/docs/sdks/typescript`),
357
+ the redirect would send the page to itself. Its source is rewritten to
358
+ `/:name+`, which keeps redirecting every deeper path and leaves the page alone.
359
+ For `/:name*` that path is the root `/`, which the site answers with its
360
+ introduction page or with a redirect of its own, so such a catch-all
361
+ (`/:path*` to `https://example.com/docs`) becomes `/:path+` in either case.
306
362
 
307
363
  A Mintlify page whose frontmatter `url` is a web address is a link in the
308
364
  source sidebar. It is imported as a page that is kept out of the sitemap, stays
@@ -313,14 +369,53 @@ purpose; a `url` that is not an `http(s)` address is ignored. The home page
313
369
  both redirect, and the redirect from `/` replaces the generated one to the first
314
370
  page. A redirect for `/` that the source site wrote stays, and a warning says so.
315
371
 
372
+ An authored redirect (Mintlify and Fern `redirects`, Docusaurus
373
+ `plugin-client-redirects`) starts at a path on the site and ends at another
374
+ path on it or at an absolute `http(s)` address, which is kept exactly as written
375
+ (`/docs/examples` to `https://github.com/dubinc/examples`). A destination that
376
+ starts with `//`, uses another scheme such as `javascript:` or `data:`, or holds
377
+ a backslash, whitespace, a control character, credentials or a bracketed IPv6
378
+ host is left out for safety, and one warning lists the first ten sources skipped.
379
+ Only the path of a destination is checked as route syntax: its query is kept as
380
+ written (`https://example.com/?next=https://other.com`), and so is a fragment
381
+ unless it holds a character Next.js reads as route syntax (`: * + ? ( ) { }`). An
382
+ address whose host name, once its percent escapes are read, is not a valid one
383
+ (letters, digits, hyphens, underscores and dots, then an optional port) is left out with a
384
+ warning, since Next.js fails when it follows such a redirect. A `:name` in a
385
+ destination that the source does not define is kept as text (its colon is written
386
+ `%3A`), because Next.js fails when it has no value for it; a wildcard
387
+ (`:name*`, `:name+`) the source does not have, or a wildcard of the source that
388
+ the destination writes without its `*` or `+`, is left out with a warning. A
389
+ redirect that still cannot be carried (a `:` inside a path segment such as
390
+ `Category:Foo`, a `*` anywhere but as the whole last segment, or such a fragment)
391
+ is left out with a warning that names that reason.
392
+ A Mintlify source or destination written without its leading slash
393
+ (`features/extract-beta`) is read as the path it names, as Mintlify's own site
394
+ does, and one warning lists the first ten redirects given a slash; one starting
395
+ with `./` or `../`, a source with a scheme, or a destination with a scheme other
396
+ than `http:` or `https:` is left out like any other unsafe value. Fern
397
+ and Docusaurus ask for a rooted path, so theirs are not read that way.
398
+ A Mintlify or Fern redirect that does not set `permanent` is permanent (308), the
399
+ default both platforms document, so search engines move to the new address;
400
+ `permanent: false` stays temporary (307), and so do the route aliases, endpoint
401
+ URLs and page links the migrator adds itself.
402
+
316
403
  A repository is limited to 5,000 documentation files and, separately, 5,000
317
404
  asset files. Past that, files are kept in navigation order with the default
318
405
  version first, then pages not listed in navigation, and one warning counts and
319
406
  names (the first three of) the pages left out and suggests `--docs-dir` to
320
407
  migrate a smaller part of the repository. Scanning itself stops after 100,000
321
- files with its own warning. Individual assets over 25 MB, or beyond 500 MB in
322
- total, are not copied; one warning per reason gives the count, the first five
323
- paths, and the next step (copy them into `public/` manually).
408
+ files with its own warning. An asset is not copied when it is over 25 MB and no
409
+ page uses it, over 100 MB when a page does, or when the copied assets would pass
410
+ 500 MB in total; one warning per reason gives the count, the first five paths,
411
+ and the next step (copy them into `public/` manually). The budget is spent in
412
+ order: the root stylesheet, the site's own scripts and fonts, and the files
413
+ `docs.json` names (logo, favicon, background; a bare `logo.svg` counts like
414
+ `/logo.svg`) first, then the assets pages use, then the rest, so a full budget
415
+ never costs the site its branding. Cloudflare, including Thally Cloud, serves a
416
+ static file only up to 25 MiB (26,214,400 bytes), so a copied asset over that
417
+ size gets its own warning: a Cloudflare deploy fails until it is made smaller
418
+ (for example, an animated GIF converted to a video) or hosted elsewhere.
324
419
 
325
420
  Headings written with an explicit `{#custom-id}` become a preceding
326
421
  `<a id="custom-id"></a>` anchor and keep their text, so links to that id still
@@ -509,8 +604,28 @@ For a copied client component that receives a simple page-authored JSX callback,
509
604
  the importer moves the callback into a client wrapper and keeps the page. If
510
605
  the callback cannot be moved safely, it keeps the page and the control's child
511
606
  content as a passive fallback, with a warning identifying the control. A
512
- stateful local snippet component is kept as a client component instead of
513
- flattening away its hooks and state.
607
+ function the page imports from a copied module and passes as a prop to such a
608
+ component (`badges={badgesFor}`) is imported by a client adapter instead, so
609
+ the page never hands a function across the server boundary, however the module
610
+ exports it (`export default format`, `export { format as f }`); an export that
611
+ cannot be classified without running the module is moved too, since importing
612
+ plain data inside a client module is just as safe (a `let` or `var` is one, as a later
613
+ assignment may change it: only a `const` literal stays on the page). A tag gets one
614
+ adapter for all of its function props, a page-authored callback and imported
615
+ functions alike, so none is left crossing from the page; that holds for a
616
+ component the page also uses as a value (`C={Widget}`), which keeps a real import
617
+ instead of an alias. A client component gets the adapter for either kind of
618
+ function. A copied server component keeps receiving an imported function from the
619
+ page, as authored, and gets one for a page-authored callback only when it can run
620
+ once wrapped: an async one (`export default async function Widget`) is left as
621
+ authored, since wrapped it would be an async client component. A function prop
622
+ that a later spread or a later prop of the same name overrides is never moved
623
+ (the adapter would put it over them), and neither is a function passed as
624
+ `children` (the adapter hands the component its own children): a page-authored
625
+ callback then stays passive, and an imported function stays on the page, each
626
+ with a warning. A stateful local
627
+ snippet component is kept as a client component instead of flattening away its
628
+ hooks and state.
514
629
  A component a page declares itself (`export const Counter = () => ...`) that
515
630
  calls a React hook is moved into its own `'use client'` module, together with
516
631
  every page-local component, helper and constant it uses (a sibling it renders,
@@ -524,7 +639,32 @@ over a same-named built-in. If any dependency cannot run in a client module
524
639
  the migration removed, a dynamic `import()`), the component is left where it
525
640
  is and a warning names it and the blocker.
526
641
  `<Snippet file="...">` accepts the documented `snippets/`-relative path as well
527
- as a `/snippets/...` or page-relative one. An outer code fence that contains a
642
+ as a `/snippets/...` or page-relative one. A multi-line snippet keeps the
643
+ indentation of the tag it replaces on every line (aligned with the item text
644
+ after a list marker), so a list or block stays inside its `<Step>` or list item;
645
+ lines inside a JS expression, and every line of a fenced code block, are left as
646
+ written so a template literal and the code keep their text. The string props a snippet is called with are its variables, as on
647
+ Mintlify: `{mode === "x" && ...}` and `props.mode` in the snippet read the
648
+ value of `<Snippet mode="x" />` at each use, so the content a prop selects is
649
+ kept. A prop binds only where its name is a free variable, which a scope
650
+ analysis of the expression decides (`eslint-scope`): a name the snippet declares
651
+ itself (a top-level `export const mode`, a function or an import, or a parameter,
652
+ `var`, block, loop, `catch` clause or class around the use) is that declaration's,
653
+ never the prop's, while a default parameter (`(x = mode) => { var mode }`) still
654
+ reads the outer name. That includes `props` itself (`props.mode` binds only where
655
+ the snippet does not declare `props`, and a callback parameter named `props`
656
+ shields only its own uses of it). A property key, a member name after a dot, a
657
+ label (`mode: for (...)`, `break mode`) and the name of a class member (method,
658
+ field, accessor, `#private`) are names, not uses, and are never replaced; a
659
+ computed key (`[mode]`) is a use.
660
+ A bare `{mode}` follows the same rules, so a callback parameter or a declaration
661
+ of that name keeps its own value there too; it becomes the value as text in a
662
+ Markdown snippet and `{"value"}` in a JSX one; `{children}` becomes the children as
663
+ written in text and as the child of an element inside an expression
664
+ (`{mode === "x" && <Note>{children}</Note>}`) alike. A `{name}` inside a fenced code
665
+ block is never replaced, and a fence closes only as CommonMark closes it (the same
666
+ character, at least as long, nothing after it), so a three-backtick block inside a
667
+ four-backtick one stays code. An outer code fence that contains a
528
668
  same-length nested fence is widened so the inner fence no longer closes it, and
529
669
  a bare placeholder tag in prose (`<Feature> requires a plan`) that would stop
530
670
  the page compiling is escaped as text rather than losing the page.
@@ -673,13 +813,62 @@ page. Those files are copied to `public/` (tooling config such as
673
813
  output, dot-directories, and `node_modules` are not) under the normal asset
674
814
  size limits. Scripts are added to `docs.json` `customScripts` (URL-encoded),
675
815
  except modules that pages import as components, which are copied as components
676
- instead. Thally cannot
677
- load a stylesheet from `docs.json`, so each `.css` is preserved with a warning
678
- that it is not applied and likely targets Mintlify's DOM. Fonts named by
816
+ instead. Thally loads a local stylesheet that `docs.json` lists under
817
+ `stylesheets`, but the migrator does not list the `.css` files it copies, since
818
+ their rules likely target Mintlify's DOM: each is preserved with a warning that it
819
+ is not applied. Fonts named by
679
820
  `fonts.source` (top level, `heading`, `body`) that are local `.woff`, `.woff2`,
680
- `.ttf`, or `.otf` files are copied; Thally's `fonts` setting loads Google Fonts
681
- by family only, so a warning says the file is not applied. Remote font URLs
682
- are not downloaded, and unsafe or missing paths are warned about.
821
+ `.ttf`, or `.otf` files are copied. Thally's `fonts` setting loads Google Fonts
822
+ by family only, so a font with a `source` of its own is not written to it (the
823
+ page would ask Google for a family it does not have, and fail): the site keeps its
824
+ default font, and one warning per font says so and how to use the copied file (an
825
+ `@font-face` rule in a stylesheet in `public/` that `docs.json` lists under
826
+ `stylesheets`, then `--font-heading` or `--font-sans`). A `source` that is a
827
+ Google Fonts address (`fonts.googleapis.com`, `fonts.gstatic.com`) still loads by
828
+ family. Remote font URLs are not downloaded, and unsafe or missing paths are
829
+ warned about.
830
+
831
+ The root `style.css` is the exception to that: it is projected, and the
832
+ projection is listed under `stylesheets`. It keeps the rules for markup the
833
+ migrated site authors (a class or id in a page, a script, or a copied component,
834
+ including a class a component builds from a template or a family name of at
835
+ least three characters) and skips those for Mintlify's own markup, the global
836
+ reset and utility classes. Every rule it keeps is confined to the page content,
837
+ so an imported stylesheet can never restyle the navbar, sidebar, table of
838
+ contents, breadcrumbs, pagination, search or footer, whatever its classes, ids and
839
+ variables are named. The content is the element the Thally runtime renders a
840
+ page's content in (`.thally-docs-prose`), and `.x p` becomes
841
+ `:where(.thally-docs-prose) .x p`; a leading `html`, `body`, `:root`, `.dark` or
842
+ `.light` stays above it (`.dark :where(.thally-docs-prose) .x p`). A selector
843
+ for the page itself (`html.x`, `body > .x`) is skipped, and so is one that starts
844
+ with an `:is()` or `:where()` of which only some alternatives are the page
845
+ (`:is(.dark, .x) .y`). `:root` and `.dark`
846
+ blocks keep only the custom properties a kept rule reads, directly or through
847
+ another kept property, and declare them on that element, so they reach the
848
+ content and nothing around it; any other declaration in those blocks is left
849
+ out. A rule nested inside another rule (CSS nesting) is skipped, since a nested
850
+ selector is read relative to the rule around it; write it as a separate rule to
851
+ keep it. A `@media` or `@supports` written inside a rule is kept, except inside a
852
+ `:root` or `.dark` block, which takes custom properties and nothing else. A
853
+ `@property` registration applies to the whole page, so it is kept only when a
854
+ kept rule reads the property and the site's own `:root` or `.dark` block declares
855
+ properties with the same prefix (`--ifx-` in `--ifx-glow`), which shows the name
856
+ is the site's and not the shell's; any other is left out and named in a warning.
857
+ Every `@layer` is kept, even an empty one or a bare `@layer a, b;`, since
858
+ the order of the layers decides which rule wins. A keyframes name is global to the page, so a site's `spin` would replace
859
+ the runtime's: each `@keyframes` is kept under a prefix of the site's own
860
+ (`src-<id>-spin`, the id being that of the stylesheet's directory in
861
+ `public/migrated/`), and the `animation` and `animation-name` declarations (and
862
+ custom properties) that name it follow. `@font-face` is kept as written, and so
863
+ is a `@counter-style` or `@font-palette-values` that a kept rule names (as it
864
+ is, with whatever it names in turn). Any other at-rule that holds no style rule,
865
+ such as `@page`, an unreferenced `@counter-style` or a stray `@namespace`, is
866
+ left out. A warning counts the selectors and at-rules that were skipped. Thally renders a page in `mode: custom` with no such element, so the
867
+ migration wraps that page's content (everything after its imports and exports)
868
+ in `<div data-thally-source-page>`, and every rule is confined to that as well
869
+ (`:where(.thally-docs-prose, [data-thally-source-page])`). A custom page that
870
+ cannot take the wrapper (an export follows its content) is left as it was, and a
871
+ warning says how many there are.
683
872
 
684
873
  Legacy `mint.json` `topbarLinks` and `topbarCtaButton` (including
685
874
  `type: github`) map to `navbar.links` and `navbar.primary`; `docs.json`
@@ -734,3 +923,11 @@ initialized one at a time, best-effort,
734
923
  after a successful clone — a submodule that can't be fetched (private,
735
924
  deleted, network trouble) is named in a warning instead of failing the
736
925
  whole migration.
926
+
927
+ `migrateUrl({ sourceUrl, openapiUrl })` accepts an explicit HTTPS OpenAPI URL,
928
+ using the same bounded specification fetch boundary as repository imports.
929
+ A single publisher-linked `openapi.json` or `openapi.yaml` can also be discovered.
930
+ Crawled endpoint pages with a unique tag/title route become interactive while
931
+ retaining their prose; generated API navigation lists only unrepresented operations.
932
+ This HTTP adapter never reads local paths. Trusted local CLI imports use a separate
933
+ bounded snapshot before calling the existing repository converter.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MigrationPage, a as MigrationAsset, b as MigrationNavigationGroup, R as RenderedMigrationFile, c as MigrationWarning, d as MigrationFetcher, e as MigrationPlatform, f as MigrationBundle, g as MigrationDocsConfig, E as EndpointTabSlot } from './url-run-CPzQfjRW.js';
2
- export { h as MigrationCapture, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, o as MigrationJournalAsset, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, y as mergeMigrationConfig, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-run-CPzQfjRW.js';
1
+ import { M as MigrationPage, a as MigrationAsset, b as MigrationNavigationGroup, R as RenderedMigrationFile, c as MigrationWarning, d as MigrationFetcher, e as MigrationPlatform, f as MigrationBundle, g as MigrationDocsConfig, E as EndpointTabSlot } from './url-run-D-E4dr2h.js';
2
+ export { h as MigrationCapture, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, o as MigrationJournalAsset, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, y as mergeMigrationConfig, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-run-D-E4dr2h.js';
3
3
  export { isPassiveSvg } from './svg-policy.js';
4
4
 
5
5
  /**
@@ -83,6 +83,8 @@ interface RepositoryMigrationOptions {
83
83
  docusaurusSkipAssets?: boolean;
84
84
  /** @internal Test seam: lowers the 5,000-file budget so budget behaviour can be exercised with small fixtures. */
85
85
  maxSourceFiles?: number;
86
+ /** @internal Test seam: lowers the 500 MB total asset budget so budget behaviour can be exercised with small fixtures. */
87
+ maxTotalAssetBytes?: number;
86
88
  /** @internal Redirects are global config, read once, not per plugin instance. */
87
89
  docusaurusSkipRedirects?: boolean;
88
90
  /** Mintlify `sourceRef` repositories already fetched and prepared by `importSourceRef`. */
@@ -107,15 +109,46 @@ declare function gitmodulePaths(targetDir: string): Array<string>;
107
109
  declare function cloneGitHubRepository(source: GitHubRepositorySource, targetDir: string, warnings?: Array<MigrationWarning>,
108
110
  /** Untrusted referenced repositories: never follow their `.gitmodules` to further remotes. */
109
111
  options?: {
110
- skipSubmodules?: boolean;
112
+ skipSubmodules?: boolean; /** The docs directory the migration will read; default: the one in the source URL. */
113
+ docsDir?: string;
111
114
  }): Promise<void>;
112
115
  /** Detect a supported repository docs platform from unambiguous config files. */
113
116
  declare function detectRepositoryPlatform(repositoryDir: string, docsDir?: string): MigrationPlatform;
114
117
  /** Pick the first conventional content root containing Markdown. */
115
118
  declare function detectRepositoryDocsDir(repositoryDir: string): string;
119
+ /** Enumerate generated endpoint routes using the same spec visibility, tab ids and slugs as migration linking. */
120
+ declare function migrationApiRoutes(bundle: Pick<MigrationBundle, 'docsConfig' | 'assets'>): Array<string>;
116
121
  /** Import an already-available repository directory into a canonical bundle. */
117
122
  declare function migrateRepository(options: RepositoryMigrationOptions): MigrationBundle;
118
123
 
124
+ /**
125
+ * Final pass over a migrated redirect set. Next.js applies a redirect before it
126
+ * looks for a page, so a redirect whose source is a served page hides that page,
127
+ * and two redirects that point at each other never settle. Next also parses a
128
+ * redirect source as a route pattern, so one with pattern operators fails the
129
+ * whole build, and it compiles a destination the same way, so one with route
130
+ * syntax in its path or host, or with a `:name` its source does not define,
131
+ * fails when a visitor follows it. Every platform and every step that adds
132
+ * redirects (source config, generated aliases, remote API specs) is checked
133
+ * here once, instead of each caller guarding its own additions.
134
+ *
135
+ * A site runs on either of two servers: `next start` matches a redirect source
136
+ * without regard to letter case, while the Cloudflare build matches it exactly.
137
+ * A conflict is judged by the stricter reading, so a redirect that would hide a
138
+ * page or loop on either server is left out.
139
+ */
140
+
141
+ /** `exactRoute` in lower case: the form routes are compared in, so `/Guide` and `/guide` are one address. */
142
+ declare function literalRoute(route: string): string | null;
143
+ /**
144
+ * Every address that opens a page. That is `pageRoutes`, plus the site root for
145
+ * the home document: the runtime serves `introduction` at `/` (`navigationPagePath`
146
+ * in `@thallylabs/core`, which is also what its sidebar links to) and still accepts
147
+ * `/introduction` as an alias (`matchesNavigationPath`). A redirect that has to take
148
+ * a page over must cover all of them.
149
+ */
150
+ declare function pageEntryRoutes(page: Pick<MigrationPage, 'id'>): Array<string>;
151
+
119
152
  /**
120
153
  * Mintlify-only helpers: page access rules, site-wide CSS/JS/font assets,
121
154
  * and appearance settings. Kept apart from the generic repository walk so the
@@ -160,9 +193,30 @@ interface MintlifyReferenceAccess {
160
193
  ref: string;
161
194
  access: NavigationAccess;
162
195
  }
196
+ /** A page (its navigation id) or nested group a group lists, in authored order. */
197
+ type MintlifyDirectoryItem = string | MintlifyDirectoryGroup;
198
+ interface MintlifyDirectoryGroup {
199
+ group: string;
200
+ /** Navigation id of the group's own landing page (`root`). */
201
+ root?: string;
202
+ hidden?: boolean;
203
+ items: Array<MintlifyDirectoryItem>;
204
+ }
205
+ /** A group with a `root` page and a `directory` mode: its root page lists the group's pages as cards. */
206
+ interface MintlifyDirectory extends MintlifyDirectoryGroup {
207
+ root: string;
208
+ mode: 'accordion' | 'card';
209
+ locale?: string;
210
+ /** Walked under a language other than the default: that language may have no page of its own, as the default language's root page is served instead. */
211
+ otherLanguage?: boolean;
212
+ /** How many API operations (`"GET /users"` entries) the group lists, groups nested in it included: Thally lists pages as cards, not operations. */
213
+ operations?: number;
214
+ }
163
215
  interface MintlifyNavigationResult {
164
216
  docsConfig: MigrationDocsConfig;
165
217
  pageReferences: Array<MintlifyPageReference>;
218
+ /** Groups whose `directory` setting asks their root page to list their pages. */
219
+ directories: Array<MintlifyDirectory>;
166
220
  /**
167
221
  * Access imposed by navigation containers, one entry per appearance of a
168
222
  * page (open appearances included, so a page listed in several places can
@@ -342,6 +396,8 @@ declare function validateIntegrations(integrations: unknown): Array<string>;
342
396
 
343
397
  interface UrlMigrationOptions {
344
398
  sourceUrl: string;
399
+ /** Explicit HTTPS OpenAPI specification when the publisher has no operation mirrors. */
400
+ openapiUrl?: string;
345
401
  /** Explicit caller selection; omitted callers retain source auto-detection. */
346
402
  platform?: MigrationPlatform;
347
403
  fetcher?: MigrationFetcher;
@@ -356,6 +412,9 @@ declare const defaultMigrationFetcher: MigrationFetcher;
356
412
  /** Crawl one public docs origin into a canonical, caller-independent bundle. */
357
413
  declare function migrateUrl(options: UrlMigrationOptions): Promise<MigrationBundle>;
358
414
 
415
+ /** Bind unique crawled endpoints in place and generate only missing operation navigation. */
416
+ declare function hydrateUrlOpenApi(bundle: MigrationBundle, specUrl: string, fetcher?: MigrationFetcher): Promise<MigrationBundle>;
417
+
359
418
  /**
360
419
  * Attach safely downloaded specs to the exact authored API tabs. A spec
361
420
  * mounted under Mintlify `directory` folders gets a redirect from each
@@ -371,4 +430,4 @@ declare function migrateUrl(options: UrlMigrationOptions): Promise<MigrationBund
371
430
  */
372
431
  declare function hydrateRemoteApiSpecs(bundle: MigrationBundle, fetcher?: MigrationFetcher): Promise<MigrationBundle>;
373
432
 
374
- export { type GitHubRepositorySource, MigrationAsset, MigrationBundle, MigrationDocsConfig, MigrationFetcher, MigrationNavigationGroup, MigrationPage, MigrationPlatform, MigrationWarning, RenderedMigrationFile, type RepositoryMigrationOptions, SOURCE_REF_MAX_BYTES, SOURCE_REF_MAX_FILES, SOURCE_REF_MAX_REPOSITORY_KB, type SourceRefImport, type SourceRefMapping, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, gitmodulePaths, hydrateRemoteApiSpecs, importSourceRef, migrateRepository, migrateUrl, normalizeMdx, parseGitHubRepositoryUrl, parseMarkdownPage, parseSourceRefFlags, projectFernNavigation, projectMintlifyIntegrations, projectMintlifyNavigation, pruneMissingNavigationPages, readFernConfig, readMintlifyConfig, sourceRefOversizeReason, validateIntegrations, validateMigrationUrl };
433
+ export { type GitHubRepositorySource, MigrationAsset, MigrationBundle, MigrationDocsConfig, MigrationFetcher, MigrationNavigationGroup, MigrationPage, MigrationPlatform, MigrationWarning, RenderedMigrationFile, type RepositoryMigrationOptions, SOURCE_REF_MAX_BYTES, SOURCE_REF_MAX_FILES, SOURCE_REF_MAX_REPOSITORY_KB, type SourceRefImport, type SourceRefMapping, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, gitmodulePaths, hydrateRemoteApiSpecs, hydrateUrlOpenApi, importSourceRef, literalRoute, migrateRepository, migrateUrl, migrationApiRoutes, normalizeMdx, pageEntryRoutes, parseGitHubRepositoryUrl, parseMarkdownPage, parseSourceRefFlags, projectFernNavigation, projectMintlifyIntegrations, projectMintlifyNavigation, pruneMissingNavigationPages, readFernConfig, readMintlifyConfig, sourceRefOversizeReason, validateIntegrations, validateMigrationUrl };