@thallylabs/migrate 0.2.25 → 0.2.27

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
@@ -115,6 +115,12 @@ Docusaurus `mdx-code-block` wrappers are opened as live MDX when the resulting
115
115
  page compiles and its special fences contain no module declarations. Pages
116
116
  whose fragments still need Docusaurus theme imports or remark plugins remain
117
117
  fenced and are retained.
118
+ Directories and files whose names start with `_`, and `__tests__` directories,
119
+ are skipped, as Docusaurus does not treat them as documents. Pages with `unlisted`
120
+ set stay routable but are left out of navigation. Partial imports of `toc`,
121
+ `frontMatter`, `metadata` and `assets` are bound to values the page can use
122
+ instead of failing. `<Admonition>` becomes a callout like the colon fence, and
123
+ `<ThemedImage>` keeps its light image.
118
124
 
119
125
  Fern repositories are located by their `fern/docs.yml` + `fern/fern.config.json`
120
126
  pair. `docs.yml` is parsed as bounded YAML—never executed—and preserves tabs,
@@ -129,6 +135,8 @@ Internal links are adjusted for the site's domain path, selected version, and
129
135
  source-file routes. Links to sections not imported into Thally stay on the
130
136
  published Fern site and produce a migration warning, so the generated site can
131
137
  build while the remaining content is moved separately.
138
+ `<Markdown src="...">` includes are inlined from the referenced file when it is
139
+ inside the repository.
132
140
  Every `api:` section's OpenAPI document is resolved from its own
133
141
  `generators.yml` (`api.specs[].openapi`, or the legacy `api:` string) and
134
142
  imported into its own tab — a docs.yml with several API sections (e.g. a REST
@@ -332,30 +340,71 @@ generic unknown-component fallback.
332
340
 
333
341
  ### Access-restricted pages
334
342
 
335
- A gated Mintlify page must never migrate as public. A page is withheld when its
336
- frontmatter sets a non-empty `groups` (list or string) or `public: false`
337
- (also `"false"`, `no`, `off`, `0`; empty `groups` restricts nothing), or when it sits
338
- under a navigation group or tab that sets `groups` (a list of strings) or
339
- `public: false`. `public: true` alone is not gating. Withheld pages are not
340
- written to `src/content` and are removed from navigation (emptied groups and
341
- tabs included), whether or not navigation lists them. The original file is
342
- written unchanged to `migration-quarantine/<original path>` at the project
343
- root, which no runtime content reader, search index, or `llms` output walks;
344
- `create-thally-docs` also adds it to the project's `.gitignore` (appended once,
345
- existing entries kept) so it is never committed or pushed. A public page that
346
- imports a withheld page as a component does not inline it (a `gated-page`
347
- warning names the importer). Invalid frontmatter fails the run rather than
348
- importing the page. Each page gets a
349
- `gated-page` warning, plus a summary; `create-thally-docs` prints these last
350
- under an "ACCESS-RESTRICTED CONTENT" heading and the report records them and a
351
- `quarantined` count. Links from other pages to a withheld page are left as
352
- authored and will fail `thally check`; images and other assets used only by
353
- gated pages are still copied to `public/`. Mintlify's site-wide authentication mode lives in its
354
- dashboard and cannot be read from a repository, so when any page sets
343
+ Restricted Mintlify pages migrate as real pages with normalized access
344
+ frontmatter that the Thally runtime enforces (see `/guides/authenticated-docs`):
345
+
346
+ - Frontmatter `groups` becomes a list of trimmed group names (`groups: admin`
347
+ and `groups: "a, b"` become lists); `public` becomes a boolean (`"false"`,
348
+ `no`, `off`, `0` are `false`; `true`/`"true"` is `true`). Empty `groups`
349
+ restricts nothing.
350
+ - Rules on navigation containers (a group, tab, version, dropdown, anchor,
351
+ language, or `menu` item) are pushed down into the frontmatter of every page
352
+ below them, because the runtime reads access only from pages: container
353
+ `groups` (a list of strings) and `public: false` restrict, and Mintlify's
354
+ group-level `public: true` opens pages that name no groups of their own.
355
+ Pages stay in navigation where they were.
356
+ - Rules combine to the strictest: nested and page group lists intersect,
357
+ `public: false` anywhere wins, and a page listed under both an open and a
358
+ restricted container gets the restricted rule.
359
+
360
+ A page whose rules cannot be carried over safely fails closed instead: a
361
+ malformed value (`public: maybe`, `groups: [1]`), group lists that share no
362
+ group, or frontmatter that declares `groups`/`public` but cannot be parsed or
363
+ read. Such a page is not written to `src/content`, is removed from navigation,
364
+ and its original is written unchanged to `migration-quarantine/<original path>`
365
+ at the project root, which no runtime reader walks; `create-thally-docs` adds
366
+ that folder to `.gitignore`. Each quarantined page gets a `gated-page` warning.
367
+
368
+ Fern and Docusaurus have no runtime access mapping in Thally, so their restricted
369
+ pages fail closed in the same way: they are quarantined (not written to
370
+ `src/content`, removed from navigation, original saved under
371
+ `migration-quarantine/`) and never inlined into other pages. Fern signals:
372
+ frontmatter `viewers` (any non-empty value) or `authed`, `<If roles>` or
373
+ `<If viewers>` content in a page, and `viewers` in `docs.yml` on pages, sections,
374
+ tabs, products, versions and api nodes; the specs of a restricted api node are
375
+ withheld too. Docusaurus: a `draft` page, where any value other than unset,
376
+ `false` or `"false"` counts as a draft; the check is skipped for files under `_`
377
+ directories and `__tests__`, which Docusaurus does not publish as documents. Links
378
+ from other pages to a quarantined page are left as authored and will fail
379
+ `thally check`.
380
+
381
+ Fern migration stops with an explicit error if cyclic navigation, excessive
382
+ nesting, or the node budget prevents complete access classification. It emits
383
+ no pages, assets, or API specs, because an unvisited branch could restrict a
384
+ file also listed under a public node. Simplify the source navigation and retry.
385
+
386
+ Assets are copied to `public/` only when a published page, the site configuration,
387
+ copied styles or a migrated component names them by their exact normalized path;
388
+ a restricted page's text never makes an asset public. On a site that withholds
389
+ anything, an asset nothing published names that way, one named only by a bare file
390
+ name or in a different letter case, one built at runtime, and one that shares its
391
+ public path with another file are saved under `migration-quarantine/assets/`
392
+ instead, with a warning listing them so they can be copied back by hand. On every
393
+ platform a page over 2 MB is not inlined, OpenAPI spec reads are capped at 50 MB,
394
+ and a path that resolves outside the repository through a symlink is refused.
395
+
396
+ What a restricted page uses must not leak through surfaces that are not access
397
+ controlled: it is never inlined into another page (a `gated-page` warning names
398
+ the importer), images and other assets used only by restricted pages are not
399
+ copied to `public/` (they are saved under `migration-quarantine/assets/`), and
400
+ OpenAPI operations documented only on restricted pages are withheld from the
401
+ published spec. One `gated-page` summary counts the restricted pages and notes
402
+ that Thally hides them from everyone until reader authentication is configured
403
+ in `docs.json` `auth`; no `auth` block is generated, because Mintlify's
404
+ dashboard settings cannot be read from a repository. When any page sets
355
405
  `public: true` (which implies the source site required sign-in for everything
356
- else) one `gated-page` warning says Thally will publish every imported page.
357
- Restricted-container `groups` and `public: false` are honored fail-closed even
358
- though Mintlify documents only `"public": true` on a group.
406
+ else), one more `gated-page` warning says every unrestricted page will be
407
+ public until `auth` is configured.
359
408
 
360
409
  ### Site-wide scripts, styles, fonts, and settings
361
410
 
@@ -382,6 +431,17 @@ Legacy `mint.json` `topbarLinks` and `topbarCtaButton` (including
382
431
 
383
432
  ## Branding
384
433
 
434
+ Docusaurus custom CSS projects Infima palette, typography, header spacing,
435
+ announcement styling, and local Prism colors into a portable stylesheet.
436
+ For Sass or Tailwind expressions, provide locally compiled
437
+ `build/assets/css/styles.*.css`; unresolved expressions produce a migration
438
+ warning. Configuration and theme modules are read as static data.
439
+
440
+ Remote Fern `global-theme` references fall back to local overrides and base
441
+ Thally tokens with a structured warning. The NVIDIA theme uses `#76B900`
442
+ when no local accent is configured. No remote theme assets are fetched.
443
+ Authored section expansion defaults are retained on desktop and mobile.
444
+
385
445
  A source site's logo, favicon, and theme accent color(s) are extracted where
386
446
  each platform's config exposes them (Mintlify's `colors`, Fern's
387
447
  `colors.accent-primary`, Docusaurus's classic theme `customCss`). A valid
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  type MigrationPlatform = 'mintlify' | 'docusaurus' | 'fern' | 'gitbook' | 'nextra' | 'vitepress' | 'starlight' | 'thally' | 'generic' | 'unknown';
7
7
  interface MigrationNavigationGroup {
8
+ defaultOpen?: boolean;
8
9
  group: string;
9
10
  icon?: string;
10
11
  hidden?: boolean;
@@ -218,6 +219,8 @@ interface MigrationPage {
218
219
  navigationId: string;
219
220
  locale?: string;
220
221
  title: string;
222
+ /** Authored display H1 when the source uses a different metadata title. */
223
+ headingTitle?: string;
221
224
  navTitle?: string;
222
225
  /** Sidebar icon name or image URL from page frontmatter, with its optional style. */
223
226
  icon?: string;
@@ -230,6 +233,13 @@ interface MigrationPage {
230
233
  mode?: 'default' | 'wide' | 'custom' | 'center' | 'home';
231
234
  hidden?: boolean;
232
235
  noindex?: boolean;
236
+ /**
237
+ * Reader access groups (frontmatter `groups`): trimmed, non-empty names the
238
+ * runtime matches exactly. The page needs a signed-in reader in one of them.
239
+ */
240
+ groups?: Array<string>;
241
+ /** Frontmatter `public`: `true` opens the page to anyone, `false` needs any signed-in reader. */
242
+ public?: boolean;
233
243
  /** Social/SEO overrides from `og:*` and `twitter:*` frontmatter, keyed by the migrated frontmatter field. */
234
244
  meta?: Partial<Record<'ogTitle' | 'ogDescription' | 'ogImage' | 'twitterTitle' | 'twitterDescription' | 'twitterImage', string>>;
235
245
  /** OpenAPI operation key rendered by Thally instead of ordinary MDX. */
@@ -254,7 +264,7 @@ interface MigrationAsset {
254
264
  }
255
265
  interface MigrationWarning {
256
266
  code: 'collision' | 'invalid-page' | 'missing-page' | 'unsupported-config' | 'limit-reached' | 'fetch-failed' | 'skipped-file'
257
- /** Access-restricted source page withheld from the published site (or a site-wide auth risk). */
267
+ /** Access-restricted source content: migrated with access rules, withheld (quarantined), or a site-wide auth risk. */
258
268
  | 'gated-page';
259
269
  message: string;
260
270
  source?: string;
@@ -281,7 +291,11 @@ interface MigrationBundle {
281
291
  remoteApiSpecs?: Array<RemoteApiSpec>;
282
292
  /** Customer-owned component source and registry; paths are repository-relative. */
283
293
  componentFiles?: Array<RenderedMigrationFile>;
284
- /** Access-restricted source pages, written outside every published path. */
294
+ /**
295
+ * Restricted content that cannot be published safely (pages whose access
296
+ * rules are malformed or contradictory, and assets used only by restricted
297
+ * pages), written outside every published path.
298
+ */
285
299
  quarantinedFiles?: Array<RenderedMigrationFile>;
286
300
  /** Access-restricted pages the file limit dropped: neither published nor saved in quarantinedFiles. */
287
301
  droppedGatedPages?: number;
@@ -448,6 +462,30 @@ declare function detectRepositoryDocsDir(repositoryDir: string): string;
448
462
  /** Import an already-available repository directory into a canonical bundle. */
449
463
  declare function migrateRepository(options: RepositoryMigrationOptions): MigrationBundle;
450
464
 
465
+ /**
466
+ * Mintlify-only helpers: page access rules, site-wide CSS/JS/font assets,
467
+ * and appearance settings. Kept apart from the generic repository walk so the
468
+ * rules that decide how restricted pages migrate (and what is quarantined)
469
+ * are auditable in one place.
470
+ *
471
+ * Access invariant: a restricted page migrates with normalized `groups` /
472
+ * `public` frontmatter that the Thally runtime enforces; anything ambiguous
473
+ * (malformed values, group lists that share no group) fails closed and the
474
+ * page is quarantined instead.
475
+ */
476
+
477
+ /** Access imposed by every navigation container above one appearance of a page. */
478
+ interface NavigationAccess {
479
+ /** Intersection of every container group list on the path. */
480
+ groups?: Array<string>;
481
+ /** Some container on the path sets `public: false`. */
482
+ hasPublicFalse: boolean;
483
+ /** Some container on the path sets `public: true` (Mintlify's documented group flag). */
484
+ hasPublicTrue: boolean;
485
+ /** Set when a container value is malformed or nested group lists share no group. */
486
+ problem?: string;
487
+ }
488
+
451
489
  /**
452
490
  * Navigation projection for imported sites. Mintlify's schema is intentionally
453
491
  * more expressive than Thally's tab/group model, so complex containers are
@@ -459,16 +497,24 @@ interface MintlifyPageReference {
459
497
  navigationId: string;
460
498
  locale?: string;
461
499
  }
462
- /** A page listed under an access-restricted navigation container. */
463
- interface MintlifyGatedReference {
500
+ /**
501
+ * One appearance of a page reference in navigation, with the access rules of
502
+ * every container above it. Thally reads access only from page frontmatter,
503
+ * so these are pushed down into the frontmatter of the pages they cover.
504
+ */
505
+ interface MintlifyReferenceAccess {
464
506
  ref: string;
465
- reason: string;
507
+ access: NavigationAccess;
466
508
  }
467
509
  interface MintlifyNavigationResult {
468
510
  docsConfig: MigrationDocsConfig;
469
511
  pageReferences: Array<MintlifyPageReference>;
470
- /** Pages under a restricted container; withheld from `docsConfig` and `pageReferences`. */
471
- gatedReferences: Array<MintlifyGatedReference>;
512
+ /**
513
+ * Access imposed by navigation containers, one entry per appearance of a
514
+ * page (open appearances included, so a page listed in several places can
515
+ * be given the strictest rule). Restricted pages stay in `docsConfig`.
516
+ */
517
+ referenceAccess: Array<MintlifyReferenceAccess>;
472
518
  /** `sourceRef` mounts under a restricted container; never resolved, so nothing of theirs is imported. */
473
519
  gatedSourceRefs: Array<string>;
474
520
  warnings: Array<MigrationWarning>;
@@ -527,6 +573,8 @@ interface FernPageDescriptor {
527
573
  navTitle?: string;
528
574
  /** Keep routable pages excluded by Fern's `hidden` navigation flag. */
529
575
  hidden?: boolean;
576
+ /** Listed under a docs.yml node that sets `viewers` or `authed`: Fern shows it only to signed-in roles. */
577
+ restricted?: boolean;
530
578
  }
531
579
  /** One `api:` navigation node, resolved to the name/tab Thally needs to bind its spec. */
532
580
  interface FernApiSection {
@@ -547,11 +595,15 @@ interface FernApiSection {
547
595
  routeSegments: Array<string>;
548
596
  }
549
597
  interface FernNavigationResult {
598
+ /** A bounded walk skipped nodes whose access rules and API bindings are unknown. */
599
+ navigationIncomplete: boolean;
550
600
  docsConfig: MigrationDocsConfig;
551
601
  descriptors: Array<FernPageDescriptor>;
552
602
  warnings: Array<MigrationWarning>;
553
603
  /** 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). */
554
604
  apiSections: Array<FernApiSection>;
605
+ /** `api:` nodes under a restricted node; their specs must not be published. */
606
+ restrictedApiSections: Array<FernApiSection>;
555
607
  /** Fern changelog tabs render their entries as a feed at the bare route. */
556
608
  changelogIndexes: Array<{
557
609
  route: string;