@thallylabs/migrate 0.2.14 → 0.2.17

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/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  statSync
15
15
  } from "fs";
16
16
  import { createRequire } from "module";
17
- import { basename as basename3, dirname as dirname4, extname as extname5, isAbsolute as isAbsolute2, relative as relative5, resolve as resolvePath, sep as sep2 } from "path";
17
+ import { basename as basename3, dirname as dirname4, extname as extname6, isAbsolute as isAbsolute2, posix as posix3, relative as relative5, resolve as resolvePath, sep as sep2 } from "path";
18
18
  import { parse as parseYaml4 } from "yaml";
19
19
  import * as ts3 from "typescript";
20
20
 
@@ -3104,13 +3104,96 @@ import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
3104
3104
 
3105
3105
  // src/docusaurus.ts
3106
3106
  import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3 } from "fs";
3107
- import { extname as extname3, posix as posix2 } from "path";
3107
+ import { extname as extname4, posix as posix2 } from "path";
3108
3108
  import JSON5 from "json5";
3109
3109
  import { parse as parseYaml2 } from "yaml";
3110
3110
 
3111
3111
  // src/navigation.ts
3112
3112
  import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
3113
- import { dirname as dirname2, extname as extname2, relative as relative3 } from "path";
3113
+ import { dirname as dirname2, extname as extname3, relative as relative3 } from "path";
3114
+
3115
+ // src/mintlify-extras.ts
3116
+ import { extname as extname2 } from "path";
3117
+ function objectValue(value) {
3118
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3119
+ }
3120
+ var PUBLIC_FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "no", "off", "0"]);
3121
+ function isPublicFalse(value) {
3122
+ return value === false || value === 0 || typeof value === "string" && PUBLIC_FALSE_STRINGS.has(value.trim().toLowerCase());
3123
+ }
3124
+ function isPublicTrue(value) {
3125
+ return value === true || typeof value === "string" && value.trim().toLowerCase() === "true";
3126
+ }
3127
+ function frontmatterGateReason(data) {
3128
+ const groups = data.groups;
3129
+ const hasGroups = Array.isArray(groups) ? groups.length > 0 : typeof groups === "string" ? groups.trim() !== "" : groups !== void 0 && groups !== null && groups !== false;
3130
+ if (hasGroups) return "frontmatter `groups` restricts it to authenticated groups";
3131
+ if (isPublicFalse(data.public)) return "frontmatter `public: false`";
3132
+ return void 0;
3133
+ }
3134
+ function navigationGateReason(node) {
3135
+ const groups = node.groups;
3136
+ const isContainer = (entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && ["group", "pages", "tab", "groups", "page", "root", "menu", "href"].some((key) => key in entry);
3137
+ const restricted = typeof groups === "string" ? groups.trim() !== "" : Array.isArray(groups) && groups.length > 0 && !groups.every(isContainer);
3138
+ if (restricted) return "its navigation container sets `groups`";
3139
+ if (isPublicFalse(node.public)) return "its navigation container sets `public: false`";
3140
+ return void 0;
3141
+ }
3142
+ var CONFIG_BASENAME = /(?:^|[.-])(?:config|rc)\.[cm]?js$|^(?:tailwind|postcss|next|eslint|prettier|babel|webpack|vite|vitest|jest|rollup|gulpfile|gruntfile|stylelint|commitlint|lint-staged|svgo|metro)[.-]/i;
3143
+ var BUILD_OUTPUT_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "coverage"]);
3144
+ var FONT_EXTENSIONS = /* @__PURE__ */ new Set([".woff", ".woff2", ".ttf", ".otf"]);
3145
+ function isMintlifyServedScriptOrStyle(relativePath) {
3146
+ const extension = extname2(relativePath).toLowerCase();
3147
+ if (extension !== ".css" && extension !== ".js") return false;
3148
+ const segments = relativePath.split("/");
3149
+ if (segments.slice(0, -1).some((segment) => BUILD_OUTPUT_SEGMENTS.has(segment.toLowerCase()))) return false;
3150
+ return extension === ".css" || !CONFIG_BASENAME.test(segments[segments.length - 1]);
3151
+ }
3152
+ function mintlifyFontSources(config) {
3153
+ const fonts = objectValue(config?.fonts);
3154
+ if (!fonts) return [];
3155
+ const found = [];
3156
+ for (const entry of [fonts, objectValue(fonts.heading), objectValue(fonts.body)]) {
3157
+ if (!entry || typeof entry.source !== "string" || !entry.source.trim()) continue;
3158
+ const source = entry.source.trim();
3159
+ const family = typeof entry.family === "string" ? entry.family : "a font";
3160
+ const remote = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(source);
3161
+ const path = remote || /^[a-z]:[\\/]/i.test(source) ? null : normalizeAssetPath(source);
3162
+ found.push({ family, source, path: path && FONT_EXTENSIONS.has(extname2(path).toLowerCase()) ? path : null, remote });
3163
+ }
3164
+ return found;
3165
+ }
3166
+ function mintlifyAppearance(config, warnings) {
3167
+ const appearance = objectValue(config.appearance);
3168
+ const legacy = objectValue(config.modeToggle);
3169
+ const result = {};
3170
+ const isMode = (value) => value === "system" || value === "light" || value === "dark";
3171
+ const skip = (field, value) => {
3172
+ warnings.push({
3173
+ code: "unsupported-config",
3174
+ message: `Ignored invalid ${field} value ${JSON.stringify(value)?.slice(0, 40)}; the appearance default was left unchanged.`
3175
+ });
3176
+ };
3177
+ if (appearance?.default !== void 0) {
3178
+ if (isMode(appearance.default)) result.default = appearance.default;
3179
+ else skip("appearance.default", appearance.default);
3180
+ } else if (legacy?.default !== void 0) {
3181
+ if (isMode(legacy.default)) result.default = legacy.default;
3182
+ else skip("modeToggle.default", legacy.default);
3183
+ }
3184
+ if (appearance?.strict !== void 0) {
3185
+ if (typeof appearance.strict === "boolean") {
3186
+ if (appearance.strict) result.showToggle = false;
3187
+ } else skip("appearance.strict", appearance.strict);
3188
+ } else if (legacy?.isHidden !== void 0) {
3189
+ if (typeof legacy.isHidden === "boolean") {
3190
+ if (legacy.isHidden) result.showToggle = false;
3191
+ } else skip("modeToggle.isHidden", legacy.isHidden);
3192
+ }
3193
+ return result;
3194
+ }
3195
+
3196
+ // src/navigation.ts
3114
3197
  var MAX_MINTLIFY_CONFIG_BYTES = 2e7;
3115
3198
  function addMintlifyDirectoryRedirects(config, pages) {
3116
3199
  const pageIds = new Set(pages.map((page) => page.id));
@@ -3210,13 +3293,13 @@ var LANGUAGE_LABELS = {
3210
3293
  uk: "Ukrainian",
3211
3294
  zh: "Chinese"
3212
3295
  };
3213
- function objectValue(value) {
3296
+ function objectValue2(value) {
3214
3297
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3215
3298
  }
3216
3299
  function withFlattenedProductGroups(container) {
3217
3300
  if (!Array.isArray(container.productGroups)) return container;
3218
3301
  const flattened = container.productGroups.flatMap((entry) => {
3219
- const group = objectValue(entry);
3302
+ const group = objectValue2(entry);
3220
3303
  return group && Array.isArray(group.products) ? group.products : [];
3221
3304
  });
3222
3305
  if (flattened.length === 0) return container;
@@ -3251,7 +3334,7 @@ function jsonPointer(root, pointer) {
3251
3334
  const index = Number(token);
3252
3335
  return Number.isSafeInteger(index) && index >= 0 ? current[index] : void 0;
3253
3336
  }
3254
- const object = objectValue(current);
3337
+ const object = objectValue2(current);
3255
3338
  if (!object) return void 0;
3256
3339
  return object[token.replace(/~1/g, "/").replace(/~0/g, "~")];
3257
3340
  }, root);
@@ -3272,7 +3355,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
3272
3355
  if (Array.isArray(value)) {
3273
3356
  return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
3274
3357
  }
3275
- const object = objectValue(value);
3358
+ const object = objectValue2(value);
3276
3359
  if (!object) return value;
3277
3360
  if (typeof object.$ref === "string") {
3278
3361
  const [filePart, pointer = ""] = object.$ref.split("#", 2);
@@ -3292,7 +3375,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
3292
3375
  resolveJsonReferences(entry, currentFile, repositoryRoot, stack)
3293
3376
  ])
3294
3377
  );
3295
- return objectValue(resolved) ? { ...resolved, ...siblings } : resolved;
3378
+ return objectValue2(resolved) ? { ...resolved, ...siblings } : resolved;
3296
3379
  }
3297
3380
  return Object.fromEntries(
3298
3381
  Object.entries(object).map(([key, entry]) => [
@@ -3308,19 +3391,19 @@ function readMintlifyConfig(repositoryRoot) {
3308
3391
  return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
3309
3392
  }
3310
3393
  function specSource(value) {
3311
- const source = typeof value === "string" ? value : objectValue(value)?.source;
3394
+ const source = typeof value === "string" ? value : objectValue2(value)?.source;
3312
3395
  return typeof source === "string" && source.trim() ? source.trim() : void 0;
3313
3396
  }
3314
3397
  var hasNavigationChildren = (node) => ["groups", "pages"].some((key) => Array.isArray(node[key]) && node[key].length > 0);
3315
3398
  function mintlifyNavigationApiReferences(config) {
3316
- const navigation = objectValue(config.navigation) ?? config;
3399
+ const navigation = objectValue2(config.navigation) ?? config;
3317
3400
  const references = [];
3318
3401
  const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus", "languages"];
3319
3402
  const nestedKeys = ["groups", "pages"];
3320
3403
  function collect(node, tabLabel, sibling) {
3321
3404
  for (const kind of ["openapi", "asyncapi"]) {
3322
3405
  const value = specSource(node[kind]);
3323
- const directory = objectValue(node[kind])?.directory;
3406
+ const directory = objectValue2(node[kind])?.directory;
3324
3407
  if (value) {
3325
3408
  references.push({
3326
3409
  value,
@@ -3345,7 +3428,7 @@ function mintlifyNavigationApiReferences(config) {
3345
3428
  const entries = node[key];
3346
3429
  if (!Array.isArray(entries)) continue;
3347
3430
  for (const entry of entries) {
3348
- const object = objectValue(entry);
3431
+ const object = objectValue2(entry);
3349
3432
  if (!object) continue;
3350
3433
  visit(object, labelFor(object, tabLabel ?? "Documentation"));
3351
3434
  }
@@ -3355,14 +3438,14 @@ function mintlifyNavigationApiReferences(config) {
3355
3438
  if (!Array.isArray(entries)) continue;
3356
3439
  for (const entry of entries) {
3357
3440
  if (typeof entry === "string") continue;
3358
- const object = objectValue(entry);
3441
+ const object = objectValue2(entry);
3359
3442
  if (object) visit(object, tabLabel);
3360
3443
  }
3361
3444
  }
3362
3445
  if (Array.isArray(node.menu)) {
3363
3446
  const tab = tabLabel ?? "Documentation";
3364
3447
  const items = node.menu.flatMap((entry) => {
3365
- const object = objectValue(entry);
3448
+ const object = objectValue2(entry);
3366
3449
  return object ? [object] : [];
3367
3450
  });
3368
3451
  const apiItems = items.filter((item) => specSource(item.openapi) || specSource(item.asyncapi));
@@ -3408,6 +3491,23 @@ function normalizePageRef(value, pathPrefix = "") {
3408
3491
  ref = ref.replace(/\.(?:mdx?|rst|txt)$/i, "");
3409
3492
  return pageIdFromReference(ref, true);
3410
3493
  }
3494
+ function collectGatedReferences(node, gate, pathPrefix, out, depth = 0) {
3495
+ if (depth > 32) return;
3496
+ if (Array.isArray(node)) {
3497
+ for (const item of node) collectGatedReferences(item, gate, pathPrefix, out, depth + 1);
3498
+ return;
3499
+ }
3500
+ const object = objectValue2(node);
3501
+ if (!object) return;
3502
+ const reason = gate ?? navigationGateReason(object);
3503
+ if (reason) {
3504
+ const refs = [object.page, object.root, ...Array.isArray(object.pages) ? object.pages : []];
3505
+ for (const ref of refs) {
3506
+ if (typeof ref === "string" && normalizePageRef(ref, pathPrefix)) out.push({ ref, reason });
3507
+ }
3508
+ }
3509
+ for (const value of Object.values(object)) collectGatedReferences(value, reason, pathPrefix, out, depth + 1);
3510
+ }
3411
3511
  function warnOnce(context, key, message) {
3412
3512
  if (context.warningKeys.has(key)) return;
3413
3513
  context.warningKeys.add(key);
@@ -3415,7 +3515,7 @@ function warnOnce(context, key, message) {
3415
3515
  }
3416
3516
  function iconName(value) {
3417
3517
  if (typeof value === "string" && value) return value;
3418
- const icon = objectValue(value);
3518
+ const icon = objectValue2(value);
3419
3519
  return typeof icon?.name === "string" && icon.name ? icon.name : void 0;
3420
3520
  }
3421
3521
  function containerPresentation(value) {
@@ -3425,6 +3525,10 @@ function containerPresentation(value) {
3425
3525
  };
3426
3526
  }
3427
3527
  function registerReference(value, context) {
3528
+ if (context.gateReason) {
3529
+ if (normalizePageRef(value, context.pathPrefix)) context.gated.push({ ref: value, reason: context.gateReason });
3530
+ return null;
3531
+ }
3428
3532
  const localizedValue = context.locale ? mintlifyLocalizedReference(value, context.locale, context.defaultPageIds) : value;
3429
3533
  const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
3430
3534
  if (!navigationId) return null;
@@ -3448,13 +3552,24 @@ function convertPage(value, context) {
3448
3552
  }
3449
3553
  return registerReference(value, context);
3450
3554
  }
3451
- const object = objectValue(value);
3555
+ const object = objectValue2(value);
3452
3556
  if (!object) return null;
3557
+ const outerGate = context.gateReason;
3558
+ const gate = navigationGateReason(object);
3559
+ if (gate && !outerGate) context.gateReason = gate;
3560
+ try {
3561
+ return convertPageObject(object, context);
3562
+ } finally {
3563
+ context.gateReason = outerGate;
3564
+ }
3565
+ }
3566
+ function convertPageObject(object, context) {
3453
3567
  if (typeof object.page === "string") return registerReference(object.page, context);
3454
3568
  const href = projectedHref(object.href);
3455
3569
  if (href && !object.pages && !object.groups) {
3456
3570
  const page = registerReference(href, context);
3457
3571
  if (page) return page;
3572
+ if (context.gateReason) return null;
3458
3573
  warnOnce(
3459
3574
  context,
3460
3575
  "external-page-link",
@@ -3499,7 +3614,7 @@ var KNOWN_NAVIGATION_KEYS = /* @__PURE__ */ new Set([
3499
3614
  ]);
3500
3615
  function convertMenuItems(items, context) {
3501
3616
  return items.flatMap((value, index) => {
3502
- const item = objectValue(value);
3617
+ const item = objectValue2(value);
3503
3618
  if (!item) return [];
3504
3619
  const label = labelFor(item, `Menu item ${index + 1}`);
3505
3620
  if (typeof item.description === "string" && item.description.trim()) {
@@ -3549,14 +3664,14 @@ function convertNavigationValues(values, context) {
3549
3664
  });
3550
3665
  }
3551
3666
  function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
3552
- const rawContainer = objectValue(containerValue);
3667
+ const rawContainer = objectValue2(containerValue);
3553
3668
  if (!rawContainer) return [];
3554
3669
  const container = withFlattenedProductGroups(rawContainer);
3555
3670
  const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
3556
3671
  for (const key of Object.keys(container)) {
3557
3672
  if (KNOWN_NAVIGATION_KEYS.has(key)) continue;
3558
3673
  const entries = container[key];
3559
- if (Array.isArray(entries) && entries.some((entry) => objectValue(entry))) {
3674
+ if (Array.isArray(entries) && entries.some((entry) => objectValue2(entry))) {
3560
3675
  warnOnce(context, `unknown-container:${key}`, `Mintlify navigation key "${key}" is not supported and its contents were not projected.`);
3561
3676
  }
3562
3677
  }
@@ -3564,10 +3679,10 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
3564
3679
  if (!Array.isArray(container[key])) continue;
3565
3680
  const entries = [...container[key]];
3566
3681
  if (key === "versions") {
3567
- entries.sort((left, right) => Number(objectValue(right)?.default === true) - Number(objectValue(left)?.default === true));
3682
+ entries.sort((left, right) => Number(objectValue2(right)?.default === true) - Number(objectValue2(left)?.default === true));
3568
3683
  }
3569
3684
  const tabs = entries.flatMap((value, index) => {
3570
- const object = objectValue(value);
3685
+ const object = objectValue2(value);
3571
3686
  if (!object) return [];
3572
3687
  const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
3573
3688
  const href = projectedHref(object.href);
@@ -3580,7 +3695,15 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
3580
3695
  ...object.hidden === true ? { hidden: true } : {}
3581
3696
  }];
3582
3697
  }
3583
- const nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
3698
+ const outerGate = context.gateReason;
3699
+ const gate = navigationGateReason(object);
3700
+ if (gate && !outerGate) context.gateReason = gate;
3701
+ let nested;
3702
+ try {
3703
+ nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
3704
+ } finally {
3705
+ context.gateReason = outerGate;
3706
+ }
3584
3707
  if (nested.length > 0) {
3585
3708
  if (nested.length === 1) {
3586
3709
  return [{
@@ -3639,17 +3762,17 @@ function projectedTheme(value) {
3639
3762
  return typeof value === "string" ? "default" : void 0;
3640
3763
  }
3641
3764
  function projectedNavbar(value) {
3642
- const navbar = objectValue(value);
3765
+ const navbar = objectValue2(value);
3643
3766
  if (!navbar) return void 0;
3644
3767
  const links = Array.isArray(navbar.links) ? navbar.links.flatMap((entry) => {
3645
- const link = objectValue(entry);
3768
+ const link = objectValue2(entry);
3646
3769
  const href = projectedHref(link?.href);
3647
3770
  if (!link || !href) return [];
3648
3771
  const label = typeof link.label === "string" ? link.label : typeof link.name === "string" ? link.name : "";
3649
3772
  if (!label) return [];
3650
3773
  return [{ label, href, ...link.type === "github" ? { type: "github" } : {} }];
3651
3774
  }) : [];
3652
- const primaryValue = objectValue(navbar.primary);
3775
+ const primaryValue = objectValue2(navbar.primary);
3653
3776
  const primaryHref = projectedHref(primaryValue?.href);
3654
3777
  const primary = primaryValue && primaryHref ? {
3655
3778
  label: typeof primaryValue.label === "string" ? primaryValue.label : primaryValue.type === "github" ? "GitHub" : "Get started",
@@ -3657,8 +3780,22 @@ function projectedNavbar(value) {
3657
3780
  } : void 0;
3658
3781
  return links.length > 0 || primary ? { ...links.length > 0 ? { links } : {}, ...primary ? { primary } : {} } : void 0;
3659
3782
  }
3783
+ function legacyTopbarNavbar(config) {
3784
+ const links = Array.isArray(config.topbarLinks) ? config.topbarLinks.flatMap((entry) => {
3785
+ const link = objectValue2(entry);
3786
+ if (!link) return [];
3787
+ return [{
3788
+ label: link.name ?? link.label ?? (link.type === "github" ? "GitHub" : void 0),
3789
+ href: link.url ?? link.href,
3790
+ ...link.type === "github" ? { type: "github" } : {}
3791
+ }];
3792
+ }) : [];
3793
+ const cta = objectValue2(config.topbarCtaButton);
3794
+ const primary = cta ? { label: cta.name ?? cta.label, href: cta.url ?? cta.href, type: cta.type } : void 0;
3795
+ return links.length > 0 || primary ? { ...links.length > 0 ? { links } : {}, ...primary ? { primary } : {} } : void 0;
3796
+ }
3660
3797
  function projectedGlobalNavigationLinks(value) {
3661
- const global = objectValue(value);
3798
+ const global = objectValue2(value);
3662
3799
  if (!global) return [];
3663
3800
  const links = [];
3664
3801
  const visit = (entry) => {
@@ -3666,7 +3803,7 @@ function projectedGlobalNavigationLinks(value) {
3666
3803
  entry.forEach(visit);
3667
3804
  return;
3668
3805
  }
3669
- const item = objectValue(entry);
3806
+ const item = objectValue2(entry);
3670
3807
  if (!item) return;
3671
3808
  const href = projectedHref(item.href);
3672
3809
  if (href) {
@@ -3681,19 +3818,19 @@ function projectedGlobalNavigationLinks(value) {
3681
3818
  return links;
3682
3819
  }
3683
3820
  function projectedFooter(value) {
3684
- const footer = objectValue(value);
3821
+ const footer = objectValue2(value);
3685
3822
  if (!footer) return void 0;
3686
- const socials = objectValue(footer.socials);
3823
+ const socials = objectValue2(footer.socials);
3687
3824
  const socialLinks = socials ? Object.fromEntries(Object.entries(socials).flatMap(([key, value2]) => {
3688
3825
  const href = projectedHref(value2);
3689
3826
  return href ? [[key, href]] : [];
3690
3827
  })) : void 0;
3691
3828
  const links = Array.isArray(footer.links) ? footer.links.flatMap((entry) => {
3692
- const column = objectValue(entry);
3829
+ const column = objectValue2(entry);
3693
3830
  if (!column || !Array.isArray(column.items)) return [];
3694
3831
  const heading = typeof column.heading === "string" ? column.heading : typeof column.header === "string" ? column.header : "";
3695
3832
  const items = column.items.flatMap((item) => {
3696
- const link = objectValue(item);
3833
+ const link = objectValue2(item);
3697
3834
  const href = projectedHref(link?.href);
3698
3835
  return link && typeof link.label === "string" && href ? [{ label: link.label, href }] : [];
3699
3836
  });
@@ -3703,26 +3840,45 @@ function projectedFooter(value) {
3703
3840
  }
3704
3841
  function projectedFont(value) {
3705
3842
  if (typeof value === "string" && value) return { family: value };
3706
- const font = objectValue(value);
3843
+ const font = objectValue2(value);
3707
3844
  if (!font) return void 0;
3708
3845
  const family = typeof font.family === "string" ? font.family : typeof font.name === "string" ? font.name : void 0;
3709
3846
  if (!family) return void 0;
3710
3847
  const weight = Array.isArray(font.weight) ? font.weight.map(String) : Array.isArray(font.weights) ? font.weights.map(String) : void 0;
3711
3848
  return { family, ...weight && weight.length > 0 ? { weight } : {} };
3712
3849
  }
3713
- function projectedCompatibleConfig(config) {
3714
- const banner = objectValue(config.banner);
3715
- const bannerContent = banner && (typeof banner.content === "string" || objectValue(banner.content)) ? banner.content : void 0;
3850
+ function projectedCompatibleConfig(config, warnings) {
3851
+ const banner = objectValue2(config.banner);
3852
+ const bannerContent = banner && (typeof banner.content === "string" || objectValue2(banner.content)) ? banner.content : void 0;
3716
3853
  const bannerType = banner && ["info", "warning", "critical"].includes(String(banner.type ?? banner.variant)) ? String(banner.type ?? banner.variant) : void 0;
3717
- const bannerColor = objectValue(banner?.color);
3718
- const bodyFont = projectedFont(objectValue(config.fonts)?.body ?? config.fonts);
3719
- const headingFont = projectedFont(objectValue(config.fonts)?.heading);
3720
- const feedback = objectValue(config.feedback);
3721
- const seo = objectValue(config.seo);
3722
- const iconLibrary = objectValue(config.icons)?.library;
3854
+ const bannerColor = objectValue2(banner?.color);
3855
+ const bodyFont = projectedFont(objectValue2(config.fonts)?.body ?? config.fonts);
3856
+ const headingFont = projectedFont(objectValue2(config.fonts)?.heading);
3857
+ const feedback = objectValue2(config.feedback);
3858
+ const seo = objectValue2(config.seo);
3859
+ const iconLibrary = objectValue2(config.icons)?.library;
3723
3860
  const projectedIconLibrary = ["lucide", "fontawesome", "tabler"].includes(String(iconLibrary)) ? String(iconLibrary) : void 0;
3724
- const navbar = projectedNavbar(config.navbar);
3725
- const globalLinks = projectedGlobalNavigationLinks(objectValue(config.navigation)?.global);
3861
+ const docsNavbar = projectedNavbar(config.navbar);
3862
+ const legacyRaw = legacyTopbarNavbar(config);
3863
+ const legacyNavbar = projectedNavbar(legacyRaw);
3864
+ const legacyLinkCount = Array.isArray(legacyRaw?.links) ? legacyRaw.links.length : 0;
3865
+ if (legacyNavbar?.links && legacyNavbar.links.length < legacyLinkCount) {
3866
+ warnings.push({ code: "unsupported-config", message: `${legacyLinkCount - legacyNavbar.links.length} mint.json topbarLinks entr${legacyLinkCount - legacyNavbar.links.length === 1 ? "y" : "ies"} without a valid name and url were skipped.` });
3867
+ }
3868
+ if (docsNavbar?.links && legacyNavbar?.links) {
3869
+ warnings.push({ code: "unsupported-config", message: "Both docs.json navbar.links and mint.json topbarLinks are set; the docs.json navbar.links were used and topbarLinks ignored." });
3870
+ }
3871
+ if (docsNavbar?.primary && legacyNavbar?.primary) {
3872
+ warnings.push({ code: "unsupported-config", message: "Both docs.json navbar.primary and mint.json topbarCtaButton are set; navbar.primary was used and topbarCtaButton ignored." });
3873
+ }
3874
+ if (legacyRaw?.primary && !legacyNavbar?.primary) {
3875
+ warnings.push({ code: "unsupported-config", message: "mint.json topbarCtaButton has no valid url and was skipped." });
3876
+ }
3877
+ const navbar = docsNavbar || legacyNavbar ? {
3878
+ ...docsNavbar?.links ?? legacyNavbar?.links ? { links: docsNavbar?.links ?? legacyNavbar?.links } : {},
3879
+ ...docsNavbar?.primary ?? legacyNavbar?.primary ? { primary: docsNavbar?.primary ?? legacyNavbar?.primary } : {}
3880
+ } : void 0;
3881
+ const globalLinks = projectedGlobalNavigationLinks(objectValue2(config.navigation)?.global);
3726
3882
  const navbarLinks = [...new Map(
3727
3883
  [...navbar?.links ?? [], ...globalLinks].map((link) => [`${link.label}:${link.href}`, link])
3728
3884
  ).values()];
@@ -3789,11 +3945,11 @@ function isRedirectPathSafe(rawSource, rawDestination) {
3789
3945
  return true;
3790
3946
  }
3791
3947
  function mintlifyDefaultVersionPrefixes(config) {
3792
- const navigation = objectValue(config.navigation) ?? config;
3948
+ const navigation = objectValue2(config.navigation) ?? config;
3793
3949
  const prefixes = /* @__PURE__ */ new Set();
3794
3950
  function collect(container) {
3795
3951
  if (Array.isArray(container.versions)) {
3796
- const entries = container.versions.map((value) => objectValue(value)).filter((value) => value !== null);
3952
+ const entries = container.versions.map((value) => objectValue2(value)).filter((value) => value !== null);
3797
3953
  const defaultEntry = entries.find((entry) => entry.default === true) ?? entries[0];
3798
3954
  if (defaultEntry && typeof defaultEntry.version === "string" && defaultEntry.version.trim()) {
3799
3955
  prefixes.add(defaultEntry.version.trim());
@@ -3801,7 +3957,7 @@ function mintlifyDefaultVersionPrefixes(config) {
3801
3957
  }
3802
3958
  if (Array.isArray(container.languages)) {
3803
3959
  for (const value of container.languages) {
3804
- const language = objectValue(value);
3960
+ const language = objectValue2(value);
3805
3961
  if (language) collect(language);
3806
3962
  }
3807
3963
  }
@@ -3810,18 +3966,18 @@ function mintlifyDefaultVersionPrefixes(config) {
3810
3966
  return prefixes;
3811
3967
  }
3812
3968
  function mintlifyAllVersionPrefixes(config) {
3813
- const navigation = objectValue(config.navigation) ?? config;
3969
+ const navigation = objectValue2(config.navigation) ?? config;
3814
3970
  const prefixes = /* @__PURE__ */ new Set();
3815
3971
  function collect(container) {
3816
3972
  if (Array.isArray(container.versions)) {
3817
3973
  for (const value of container.versions) {
3818
- const entry = objectValue(value);
3974
+ const entry = objectValue2(value);
3819
3975
  if (entry && typeof entry.version === "string" && entry.version.trim()) prefixes.add(entry.version.trim());
3820
3976
  }
3821
3977
  }
3822
3978
  if (Array.isArray(container.languages)) {
3823
3979
  for (const value of container.languages) {
3824
- const language = objectValue(value);
3980
+ const language = objectValue2(value);
3825
3981
  if (language) collect(language);
3826
3982
  }
3827
3983
  }
@@ -3834,9 +3990,10 @@ function projectMintlifyNavigation(config, options = {}) {
3834
3990
  const references = [];
3835
3991
  const seenReferences = /* @__PURE__ */ new Set();
3836
3992
  const warningKeys = /* @__PURE__ */ new Set();
3837
- const navigation = objectValue(config.navigation) ?? config;
3993
+ const gated = [];
3994
+ const navigation = objectValue2(config.navigation) ?? config;
3838
3995
  const languages = Array.isArray(navigation.languages) ? navigation.languages.flatMap((value) => {
3839
- const language = objectValue(value);
3996
+ const language = objectValue2(value);
3840
3997
  if (!language) return [];
3841
3998
  try {
3842
3999
  const code = Intl.getCanonicalLocales(String(language.language ?? language.locale ?? "en"))[0];
@@ -3873,7 +4030,8 @@ function projectMintlifyNavigation(config, options = {}) {
3873
4030
  references,
3874
4031
  seenReferences,
3875
4032
  warnings,
3876
- warningKeys
4033
+ warningKeys,
4034
+ gated
3877
4035
  };
3878
4036
  const languageTabs = convertContainerToTabs(
3879
4037
  language,
@@ -3890,7 +4048,7 @@ function projectMintlifyNavigation(config, options = {}) {
3890
4048
  }
3891
4049
  if (Object.keys(localizedNavigation).length > 0) i18n.navigation = localizedNavigation;
3892
4050
  } else {
3893
- const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
4051
+ const context = { references, seenReferences, warnings, warningKeys, gated, pathPrefix: options.pathPrefix };
3894
4052
  tabs = convertContainerToTabs(navigation, context, "Documentation", projectionTrace);
3895
4053
  if (tabs.length === 0 && Array.isArray(config.navigation)) {
3896
4054
  const children = convertNavigationValues(config.navigation, context);
@@ -3910,7 +4068,7 @@ function projectMintlifyNavigation(config, options = {}) {
3910
4068
  });
3911
4069
  }
3912
4070
  const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
3913
- const redirect = objectValue(value);
4071
+ const redirect = objectValue2(value);
3914
4072
  if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
3915
4073
  const rawSource = redirect.source.trim();
3916
4074
  const rawDestination = redirect.destination.trim();
@@ -3929,15 +4087,17 @@ function projectMintlifyNavigation(config, options = {}) {
3929
4087
  ...typeof redirect.permanent === "boolean" ? { permanent: redirect.permanent } : {}
3930
4088
  }];
3931
4089
  }) : [];
4090
+ collectGatedReferences(navigation, void 0, options.pathPrefix, gated);
3932
4091
  return {
3933
4092
  docsConfig: {
3934
4093
  tabs,
3935
4094
  ...projectionTrace.rootContainerKind === "dropdowns" ? { navigation: { display: "dropdown" } } : {},
3936
- ...projectedCompatibleConfig(config),
4095
+ ...projectedCompatibleConfig(config, warnings),
3937
4096
  ...i18n ? { i18n } : {},
3938
4097
  ...redirects.length > 0 ? { redirects } : {}
3939
4098
  },
3940
4099
  pageReferences: references,
4100
+ gatedReferences: gated,
3941
4101
  warnings
3942
4102
  };
3943
4103
  }
@@ -4043,7 +4203,7 @@ function buildNavigationFromPages(pages, options = {}) {
4043
4203
  return { tabs: [{ tab: "Documentation", groups }] };
4044
4204
  }
4045
4205
  function isDocumentationExtension(filename) {
4046
- return [".md", ".mdx", ".rst", ".txt"].includes(extname2(filename).toLowerCase());
4206
+ return [".md", ".mdx", ".rst", ".txt"].includes(extname3(filename).toLowerCase());
4047
4207
  }
4048
4208
  function pruneMissingNavigationPages(config, availableIds) {
4049
4209
  const pruneNodes = (nodes) => {
@@ -4083,7 +4243,7 @@ var SIDEBAR_FILENAMES = [
4083
4243
  "sidebars.mjs",
4084
4244
  "sidebars.ts"
4085
4245
  ];
4086
- function objectValue2(value) {
4246
+ function objectValue3(value) {
4087
4247
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4088
4248
  }
4089
4249
  function stripNumberPrefix(value) {
@@ -4405,7 +4565,7 @@ function parseStaticSidebarModule(source) {
4405
4565
  literal
4406
4566
  );
4407
4567
  const parsed = JSON5.parse(substituted);
4408
- const object = objectValue2(parsed);
4568
+ const object = objectValue3(parsed);
4409
4569
  if (!object) continue;
4410
4570
  const bindingName = candidate[0].match(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/)?.[1];
4411
4571
  if (bindingName) {
@@ -4510,7 +4670,7 @@ function readDocusaurusSidebars(repositoryRoot, versionedSidebarPath) {
4510
4670
  if (!sourcePath) return null;
4511
4671
  const absolutePath = resolveWithin(repositoryRoot, sourcePath);
4512
4672
  const source = readBoundedText(absolutePath);
4513
- const parsed = extname3(sourcePath).toLowerCase() === ".json" ? objectValue2(JSON5.parse(source)) : parseStaticSidebarModule(source);
4673
+ const parsed = extname4(sourcePath).toLowerCase() === ".json" ? objectValue3(JSON5.parse(source)) : parseStaticSidebarModule(source);
4514
4674
  if (!parsed) throw new Error("Docusaurus sidebar config must export an object.");
4515
4675
  return { config: parsed, sourcePath };
4516
4676
  }
@@ -4536,7 +4696,7 @@ function redirectEntriesFromObjectLiteralText(objectLiteral) {
4536
4696
  if (!Array.isArray(parsed)) return [];
4537
4697
  const entries = [];
4538
4698
  for (const raw of parsed) {
4539
- const entry = objectValue2(raw);
4699
+ const entry = objectValue3(raw);
4540
4700
  if (!entry || typeof entry.to !== "string") continue;
4541
4701
  const froms = Array.isArray(entry.from) ? entry.from : [entry.from];
4542
4702
  for (const from of froms) {
@@ -4588,7 +4748,7 @@ function readCategoryMetadata(contentRoot, directory) {
4588
4748
  try {
4589
4749
  const raw = readBoundedText(path);
4590
4750
  const parsed = filename.endsWith(".json") ? JSON5.parse(raw) : parseYaml2(raw);
4591
- const object = objectValue2(parsed);
4751
+ const object = objectValue3(parsed);
4592
4752
  if (!object) return {};
4593
4753
  return {
4594
4754
  ...typeof object.label === "string" ? { label: object.label } : {},
@@ -4622,7 +4782,7 @@ function registerDoc(docId, context) {
4622
4782
  return descriptor.navigationId;
4623
4783
  }
4624
4784
  function generatedIndexPage(label, linkValue, context) {
4625
- const link = objectValue2(linkValue);
4785
+ const link = objectValue3(linkValue);
4626
4786
  if (!link) return null;
4627
4787
  if (link.type === "doc" && typeof link.id === "string") return registerDoc(link.id, context);
4628
4788
  if (link.type !== "generated-index") return null;
@@ -4889,7 +5049,7 @@ function convertItems(value, context) {
4889
5049
  return page ? [page] : [];
4890
5050
  }
4891
5051
  if (Array.isArray(value)) return value.flatMap((entry) => convertItems(entry, context));
4892
- const object = objectValue2(value);
5052
+ const object = objectValue3(value);
4893
5053
  if (!object) return [];
4894
5054
  if (object.type === "doc" || object.type === "ref") {
4895
5055
  const page = typeof object.id === "string" ? registerDoc(object.id, context) : null;
@@ -4955,7 +5115,7 @@ function projectDocusaurusNavigation(input) {
4955
5115
 
4956
5116
  // src/fern.ts
4957
5117
  import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync4, readdirSync } from "fs";
4958
- import { basename as basename2, dirname as dirname3, extname as extname4, relative as relative4 } from "path";
5118
+ import { basename as basename2, dirname as dirname3, extname as extname5, relative as relative4 } from "path";
4959
5119
  import { load } from "cheerio";
4960
5120
  import { parse as parseYaml3 } from "yaml";
4961
5121
  var MAX_CONFIG_BYTES2 = 2e7;
@@ -4985,7 +5145,7 @@ function announcementMarkdown(message) {
4985
5145
  for (const [marker, link] of links) text = text.replaceAll(marker, link);
4986
5146
  return text.trim();
4987
5147
  }
4988
- function objectValue3(value) {
5148
+ function objectValue4(value) {
4989
5149
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4990
5150
  }
4991
5151
  function titleCase3(value) {
@@ -5001,7 +5161,7 @@ function readFernConfig(fernRoot) {
5001
5161
  const sourcePath = "docs.yml";
5002
5162
  const path = resolveWithin(fernRoot, sourcePath);
5003
5163
  if (!existsSync4(path) || !lstatSync4(path).isFile()) return null;
5004
- const config = objectValue3(readBoundedYaml(path));
5164
+ const config = objectValue4(readBoundedYaml(path));
5005
5165
  if (!config) throw new Error("fern/docs.yml must contain an object.");
5006
5166
  return { config, sourcePath };
5007
5167
  }
@@ -5108,8 +5268,8 @@ function registerFolder(object, parentSegments, context) {
5108
5268
  if (entry.isDirectory()) {
5109
5269
  const nested = visit(path, [...routeSegments, fernBasicSlug(entry.name)], depth + 1);
5110
5270
  if (nested.length) pages2.push({ group: titleCase3(entry.name), pages: nested });
5111
- } else if (entry.isFile() && [".md", ".mdx"].includes(extname4(entry.name).toLowerCase())) {
5112
- const stem = basename2(entry.name, extname4(entry.name));
5271
+ } else if (entry.isFile() && [".md", ".mdx"].includes(extname5(entry.name).toLowerCase())) {
5272
+ const stem = basename2(entry.name, extname5(entry.name));
5113
5273
  const pageSegment = stem.toLowerCase() === "index" ? null : fernBasicSlug(stem);
5114
5274
  const route = [...routeSegments, ...pageSegment ? [pageSegment] : []].join("/");
5115
5275
  const source = lstatSync4(path).size <= MAX_CONFIG_BYTES2 ? readFileSync4(path, "utf8") : "";
@@ -5134,7 +5294,7 @@ function registerPage(object, parentSegments, context) {
5134
5294
  return registerPageAt(object.path, base, context, label, object.hidden === true) || null;
5135
5295
  }
5136
5296
  function convertNode(node, parentSegments, context) {
5137
- const object = objectValue3(node);
5297
+ const object = objectValue4(node);
5138
5298
  if (!object) return null;
5139
5299
  if (typeof object.page === "string") {
5140
5300
  const page = registerPage(object, parentSegments, context);
@@ -5222,7 +5382,7 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
5222
5382
  }
5223
5383
  if (!Array.isArray(config.versions)) return config;
5224
5384
  const versions = config.versions.flatMap((entry) => {
5225
- const version = objectValue3(entry);
5385
+ const version = objectValue4(entry);
5226
5386
  return version ? [version] : [];
5227
5387
  });
5228
5388
  const chosen = versions.find((version) => version.default === true) ?? versions[0];
@@ -5241,7 +5401,7 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
5241
5401
  warnOnce2(context, "fern-version-not-file", `Fern version file "${chosen.path}" is not a regular file and was skipped.`);
5242
5402
  return config;
5243
5403
  }
5244
- const versionConfig = objectValue3(readBoundedYaml(versionPath));
5404
+ const versionConfig = objectValue4(readBoundedYaml(versionPath));
5245
5405
  if (!versionConfig) return config;
5246
5406
  context.pathPrefix = relative4(fernRoot, dirname3(versionPath)).replace(/\\/g, "/");
5247
5407
  return { ...config, navigation: versionConfig.navigation, tabs: versionConfig.tabs ?? config.tabs };
@@ -5256,13 +5416,13 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
5256
5416
  }
5257
5417
  function buildTabsFromConfig(rawConfig, configDir, fernRoot, routePrefix, fallbackTabLabel, context) {
5258
5418
  const config = resolveVersionedNavigation(rawConfig, configDir, fernRoot, context);
5259
- const tabsMeta = objectValue3(config.tabs) ?? {};
5419
+ const tabsMeta = objectValue4(config.tabs) ?? {};
5260
5420
  const navigation = config.navigation;
5261
- const isTabbedNavigation = Array.isArray(navigation) && navigation.length > 0 && navigation.every((entry) => typeof objectValue3(entry)?.tab === "string");
5421
+ const isTabbedNavigation = Array.isArray(navigation) && navigation.length > 0 && navigation.every((entry) => typeof objectValue4(entry)?.tab === "string");
5262
5422
  if (isTabbedNavigation) {
5263
5423
  return navigation.flatMap((entry) => {
5264
5424
  const id = String(entry.tab);
5265
- const meta = objectValue3(tabsMeta[id]) ?? {};
5425
+ const meta = objectValue4(tabsMeta[id]) ?? {};
5266
5426
  const label = typeof meta["display-name"] === "string" ? meta["display-name"] : titleCase3(id);
5267
5427
  const tabSegment = segmentFor(meta, label, context);
5268
5428
  const segments = [...routePrefix, ...tabSegment ? [tabSegment] : []];
@@ -5284,7 +5444,7 @@ function buildTabsFromConfig(rawConfig, configDir, fernRoot, routePrefix, fallba
5284
5444
  const index = visiblePages.find((page) => {
5285
5445
  const descriptor = descriptors.get(page);
5286
5446
  if (!descriptor) return false;
5287
- const stem = basename2(descriptor.sourcePath, extname4(descriptor.sourcePath));
5447
+ const stem = basename2(descriptor.sourcePath, extname5(descriptor.sourcePath));
5288
5448
  return page === indexRoute || stem === "overview" || stem === "index";
5289
5449
  });
5290
5450
  const entries = visiblePages.filter((page) => page !== index).sort((left, right) => {
@@ -5333,7 +5493,7 @@ function buildTabsFromConfig(rawConfig, configDir, fernRoot, routePrefix, fallba
5333
5493
  }
5334
5494
  function projectFernProducts(rawProducts, fernRoot, context) {
5335
5495
  return rawProducts.flatMap((entry) => {
5336
- const product = objectValue3(entry);
5496
+ const product = objectValue4(entry);
5337
5497
  const rawPath = typeof product?.path === "string" ? product.path.trim() : "";
5338
5498
  if (!product || !rawPath) return [];
5339
5499
  const label = typeof product["display-name"] === "string" && product["display-name"].trim() ? product["display-name"].trim() : "Product";
@@ -5350,7 +5510,7 @@ function projectFernProducts(rawProducts, fernRoot, context) {
5350
5510
  }
5351
5511
  let productConfig;
5352
5512
  try {
5353
- productConfig = objectValue3(readBoundedYaml(productPath));
5513
+ productConfig = objectValue4(readBoundedYaml(productPath));
5354
5514
  } catch (error) {
5355
5515
  warnOnce2(
5356
5516
  context,
@@ -5404,7 +5564,7 @@ function projectFernNavigation(input) {
5404
5564
  });
5405
5565
  }
5406
5566
  const navbarLinks = Array.isArray(config["navbar-links"]) ? config["navbar-links"].flatMap((entry) => {
5407
- const link = objectValue3(entry);
5567
+ const link = objectValue4(entry);
5408
5568
  const href = typeof link?.url === "string" ? link.url : typeof link?.href === "string" ? link.href : typeof link?.value === "string" ? link.value : void 0;
5409
5569
  const label = typeof link?.text === "string" ? link.text : typeof link?.label === "string" ? link.label : link?.type === "github" ? "GitHub" : void 0;
5410
5570
  return href && label ? [{ label, href, ...link?.type === "github" ? { type: "github" } : {} }] : [];
@@ -5414,11 +5574,11 @@ function projectFernNavigation(input) {
5414
5574
  navbarLinks.push(link);
5415
5575
  }
5416
5576
  }
5417
- const announcement = objectValue3(config.announcement);
5577
+ const announcement = objectValue4(config.announcement);
5418
5578
  const announcementMessage = typeof announcement?.message === "string" ? announcement.message.trim() : "";
5419
5579
  const bannerContent = announcementMarkdown(announcementMessage);
5420
5580
  const redirects = Array.isArray(config.redirects) ? config.redirects.flatMap((value) => {
5421
- const redirect = objectValue3(value);
5581
+ const redirect = objectValue4(value);
5422
5582
  if (!redirect || typeof redirect.source !== "string" || typeof redirect.destination !== "string") return [];
5423
5583
  const rawSource = redirect.source.trim();
5424
5584
  const rawDestination = redirect.destination.trim();
@@ -5504,6 +5664,9 @@ function projectFernNavigation(input) {
5504
5664
  // src/repository.ts
5505
5665
  var MAX_SOURCE_FILES = 5e3;
5506
5666
  var MAX_PAGE_BYTES = 2e6;
5667
+ var FRONTMATTER_HEAD_BYTES = 65536;
5668
+ var MAX_WITHHELD_SCAN_BYTES = MAX_PAGE_BYTES * 8;
5669
+ var MAX_DROPPED_GATED_WARNINGS = 20;
5507
5670
  var MAX_ASSET_BYTES = 25e6;
5508
5671
  var MAX_TOTAL_ASSET_BYTES = 5e8;
5509
5672
  var GIT_LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
@@ -5529,6 +5692,7 @@ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
5529
5692
  function isIgnoredContentDirectory(name) {
5530
5693
  return name.startsWith(".") || name === "node_modules";
5531
5694
  }
5695
+ var QUARANTINE_DIRECTORY = "migration-quarantine";
5532
5696
  var ASSET_DIRECTORIES = /* @__PURE__ */ new Set(["assets", "images", "img", "media", "public", "static"]);
5533
5697
  var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
5534
5698
  ".avif",
@@ -6144,7 +6308,7 @@ function containsMarkdown(directory, depth = 0) {
6144
6308
  for (const entry of readdirSync2(directory, { withFileTypes: true })) {
6145
6309
  if (isIgnoredDirectory(entry.name) || entry.isSymbolicLink()) continue;
6146
6310
  const path = resolveWithin(directory, entry.name);
6147
- if (entry.isFile() && [".md", ".mdx"].includes(extname5(entry.name).toLowerCase())) return true;
6311
+ if (entry.isFile() && [".md", ".mdx"].includes(extname6(entry.name).toLowerCase())) return true;
6148
6312
  if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
6149
6313
  }
6150
6314
  return false;
@@ -6156,10 +6320,10 @@ function detectRepositoryDocsDir(repositoryDir) {
6156
6320
  }
6157
6321
  return "";
6158
6322
  }
6159
- var MAX_RANKED_WALK_FILES = MAX_SOURCE_FILES * 20;
6160
- function scanFiles(root, confinementRoot = root, warnings, rank) {
6323
+ var RANKED_WALK_FACTOR = 20;
6324
+ function scanFiles(root, confinementRoot = root, warnings, rank, budget = MAX_SOURCE_FILES) {
6161
6325
  const files = [];
6162
- const walkCap = rank ? MAX_RANKED_WALK_FILES : MAX_SOURCE_FILES;
6326
+ const walkCap = rank ? budget * RANKED_WALK_FACTOR : budget;
6163
6327
  let confinementReal;
6164
6328
  try {
6165
6329
  confinementReal = realpathSync2(confinementRoot);
@@ -6229,10 +6393,10 @@ function scanFiles(root, confinementRoot = root, warnings, rank) {
6229
6393
  }
6230
6394
  return files;
6231
6395
  }
6232
- function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label) {
6233
- if (scanned.length <= MAX_SOURCE_FILES) return scanned;
6396
+ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label, budget = MAX_SOURCE_FILES) {
6397
+ if (scanned.length <= budget) return scanned;
6234
6398
  const ranked = scanned.map((file) => ({ file, priority: rank(file.relativePath) })).sort((left, right) => left.priority - right.priority || (left.file.relativePath < right.file.relativePath ? -1 : left.file.relativePath > right.file.relativePath ? 1 : 0));
6235
- const dropped = ranked.slice(MAX_SOURCE_FILES).map(({ file }) => file);
6399
+ const dropped = ranked.slice(budget).map(({ file }) => file);
6236
6400
  if (warnings) {
6237
6401
  const droppedPages = label === "file" ? dropped.filter((file) => isDocumentationExtension(file.relativePath)) : dropped;
6238
6402
  const noun = label === "file" ? "page" : "image or media file";
@@ -6245,18 +6409,18 @@ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, la
6245
6409
  const rest = droppedPages.length - examples.length;
6246
6410
  warnings.push({
6247
6411
  code: "limit-reached",
6248
- message: `This repository has more than ${MAX_SOURCE_FILES} ${label === "file" ? "files" : "assets"}, so only the first ${MAX_SOURCE_FILES} (in navigation order, default version first) were migrated. ${droppedPages.length} ${noun}(s) were left out` + (examples.length > 0 ? `: ${examples.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}` : "") + (droppedVersions.size > 0 ? ` (versions: ${[...droppedVersions].slice(0, 5).join(", ")}${droppedVersions.size > 5 ? `, and ${droppedVersions.size - 5} more` : ""})` : "") + (label === "file" ? ". To include them, run the migration on a smaller part of the repository with --docs-dir." : ". Copy them into public/ manually if your pages use them.")
6412
+ message: `This repository has more than ${budget} ${label === "file" ? "files" : "assets"}, so only the first ${budget} (in navigation order, default version first) were migrated. ${droppedPages.length} ${noun}(s) were left out` + (examples.length > 0 ? `: ${examples.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}` : "") + (droppedVersions.size > 0 ? ` (versions: ${[...droppedVersions].slice(0, 5).join(", ")}${droppedVersions.size > 5 ? `, and ${droppedVersions.size - 5} more` : ""})` : "") + (label === "file" ? ". To include them, run the migration on a smaller part of the repository with --docs-dir." : ". Copy them into public/ manually if your pages use them.")
6249
6413
  });
6250
6414
  }
6251
- return ranked.slice(0, MAX_SOURCE_FILES).map(({ file }) => file);
6415
+ return ranked.slice(0, budget).map(({ file }) => file);
6252
6416
  }
6253
- function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes) {
6254
- const isAsset = (file) => ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase());
6417
+ function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes, budget = MAX_SOURCE_FILES) {
6418
+ const isAsset = (file) => ASSET_EXTENSIONS.has(extname6(file.relativePath).toLowerCase());
6255
6419
  const assetFiles = scanned.filter(isAsset);
6256
6420
  const otherFiles = scanned.filter((file) => !isAsset(file));
6257
6421
  return [
6258
- ...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file"),
6259
- ...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset")
6422
+ ...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file", budget),
6423
+ ...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset", budget)
6260
6424
  ];
6261
6425
  }
6262
6426
  function readMintignoreMatcher(mintlifyRoot) {
@@ -6301,6 +6465,45 @@ function normalizedReferenceKey(value) {
6301
6465
  function exactReferenceKey(value) {
6302
6466
  return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
6303
6467
  }
6468
+ function specRefMatches(ref, specPath, specFilename, isDefault) {
6469
+ const prefix = /^(?:(\S+)\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|WEBHOOK)\s/i.exec(`${ref} `)?.[1];
6470
+ if (!prefix) return isDefault;
6471
+ const wanted = prefix.replace(/^\/+/, "").toLowerCase();
6472
+ const lowerPath = specPath.toLowerCase();
6473
+ return lowerPath === wanted || lowerPath.endsWith(`/${wanted}`) || specFilename.toLowerCase() === wanted;
6474
+ }
6475
+ var TOKEN_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\/`*!?#&]+/;
6476
+ var PATH_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\`*!?#&]+/;
6477
+ function publicAssetKey(repoRelative) {
6478
+ return repoRelative.split("/", 1)[0].toLowerCase() === "public" ? repoRelative.slice("public/".length) : repoRelative;
6479
+ }
6480
+ function addPathReferences(text, baseDir, exact, loose) {
6481
+ for (const word of text.split(PATH_SEPARATORS)) {
6482
+ let token = word.replace(/\.+$/, "");
6483
+ if (!/\.[a-z0-9]{2,5}$/i.test(token) || token.startsWith("//")) continue;
6484
+ if (token.includes("%")) {
6485
+ try {
6486
+ token = decodeURIComponent(token);
6487
+ } catch {
6488
+ }
6489
+ }
6490
+ if (!token.includes("/")) {
6491
+ loose.add(token.toLowerCase());
6492
+ continue;
6493
+ }
6494
+ if (!token.startsWith("/") && baseDir === void 0) continue;
6495
+ const resolved = normalizeAssetPath(posix3.normalize(token.startsWith("/") ? token.slice(1) : posix3.join(baseDir ?? "", token)));
6496
+ if (!resolved) continue;
6497
+ const key = publicAssetKey(resolved);
6498
+ exact.add(key);
6499
+ loose.add(key.toLowerCase());
6500
+ }
6501
+ }
6502
+ function looselyNamed(assetPath, loose) {
6503
+ const lower = assetPath.toLowerCase();
6504
+ const name = lower.split("/").pop() ?? lower;
6505
+ return loose.has(lower) || loose.has(name) || loose.has(name.split(TOKEN_SEPARATORS).filter(Boolean).pop() ?? name);
6506
+ }
6304
6507
  function findOpenApi(files) {
6305
6508
  return files.find((file) => OPENAPI_FILENAMES.has(basename3(file.relativePath).toLowerCase()) && classifyApiSpec(file.absolutePath) === "openapi") ?? null;
6306
6509
  }
@@ -6635,6 +6838,31 @@ function fernDefinitionExists(fernRoot, apiName) {
6635
6838
  }
6636
6839
  return candidateDirs.some((dir) => existsSync5(dir) && lstatSync5(dir).isDirectory());
6637
6840
  }
6841
+ function readFrontmatterHead(path) {
6842
+ const fd = openSync(path, "r");
6843
+ try {
6844
+ const buffer = Buffer.alloc(FRONTMATTER_HEAD_BYTES);
6845
+ const head = buffer.toString("utf8", 0, readSync(fd, buffer, 0, buffer.length, 0));
6846
+ const opening = /^\uFEFF?---([^\r\n]*)\r?\n/.exec(head);
6847
+ if (!opening || opening[1].startsWith("-")) return head;
6848
+ return /^---[ \t]*\r?\n/m.test(head.slice(opening[0].length)) ? head : void 0;
6849
+ } finally {
6850
+ closeSync(fd);
6851
+ }
6852
+ }
6853
+ function canonicalPath(path) {
6854
+ try {
6855
+ return realpathSync2.native(path);
6856
+ } catch {
6857
+ return path;
6858
+ }
6859
+ }
6860
+ function pageHeadGateReason(raw) {
6861
+ const parsed = parseFrontmatter(raw);
6862
+ const unreadable = parsed.error && /^(groups|public)\s*:/m.test(raw.slice(0, raw.length - parsed.content.length)) ? "frontmatter could not be parsed and declares `groups` or `public`" : void 0;
6863
+ const openapi = typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0;
6864
+ return { reason: frontmatterGateReason(parsed.data) ?? unreadable, publicTrue: isPublicTrue(parsed.data.public), openapi };
6865
+ }
6638
6866
  function withoutFrontmatter(value) {
6639
6867
  return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
6640
6868
  }
@@ -6769,7 +6997,7 @@ function fernSourceSiteUrl(config) {
6769
6997
  function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized) {
6770
6998
  const rewrite = (target) => {
6771
6999
  const path = target.split(/[?#]/, 1)[0].replace(/^\//, "").replace(/\/$/, "");
6772
- if (!path || pageIds.has(path) || ASSET_EXTENSIONS.has(extname5(path).toLowerCase())) return target;
7000
+ if (!path || pageIds.has(path) || ASSET_EXTENSIONS.has(extname6(path).toLowerCase())) return target;
6773
7001
  try {
6774
7002
  const base = new URL(`${sourceSiteUrl}/`);
6775
7003
  const sourceLinkUrl = new URL(target.slice(1), base);
@@ -6963,8 +7191,13 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
6963
7191
  if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
6964
7192
  }
6965
7193
  for (const file of files) {
6966
- if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) continue;
6967
- const raw = readFileSync5(file.absolutePath, "utf8");
7194
+ if (![".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase())) continue;
7195
+ let raw;
7196
+ try {
7197
+ raw = readFileSync5(file.absolutePath, "utf8");
7198
+ } catch {
7199
+ continue;
7200
+ }
6968
7201
  for (const match of raw.matchAll(SNIPPET_IMPORT_PATTERN)) {
6969
7202
  try {
6970
7203
  const componentName = match[1] ?? match[2];
@@ -6991,7 +7224,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
6991
7224
  } catch {
6992
7225
  return null;
6993
7226
  }
6994
- if (!ASSET_EXTENSIONS.has(extname5(decodedPath).toLowerCase())) return null;
7227
+ if (!ASSET_EXTENSIONS.has(extname6(decodedPath).toLowerCase())) return null;
6995
7228
  try {
6996
7229
  const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithinRoot(dirname4(currentFile), decodedPath, siteRoot);
6997
7230
  const realRoot = realpathSync2(siteRoot);
@@ -7003,7 +7236,9 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
7003
7236
  if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) return null;
7004
7237
  const normalized = normalizeAssetPath(siteRelative);
7005
7238
  if (!normalized) return null;
7006
- onReferenced?.(normalized);
7239
+ const onDiskRelative = relative5(realRoot, realpathSync2.native(candidate)).replace(/\\/g, "/");
7240
+ const onDisk = onDiskRelative.toLowerCase() === siteRelative.toLowerCase() ? normalizeAssetPath(onDiskRelative) : null;
7241
+ onReferenced?.(normalized, onDisk ?? void 0);
7007
7242
  const rewritten = `/${normalized}${suffix}`;
7008
7243
  return isBracketed ? `<${rewritten}>` : rewritten;
7009
7244
  } catch {
@@ -7048,18 +7283,47 @@ function locallyDeclaredNames(raw) {
7048
7283
  }
7049
7284
  return names;
7050
7285
  }
7051
- function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map()) {
7286
+ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map(), gate) {
7052
7287
  if (depth >= 8) return raw;
7053
7288
  raw = normalizeIndentedFences(raw);
7054
7289
  const snippets = /* @__PURE__ */ new Map();
7055
7290
  const preservedDeclarations = /* @__PURE__ */ new Map();
7056
- let withoutImports = replaceOutsideCode(raw, (source) => source.replace(
7291
+ const source = relative5(repositoryRoot, currentFile).replace(/\\/g, "/");
7292
+ const blockInline = (candidate, shownPath, label = shownPath) => {
7293
+ const verdict = gate?.(candidate);
7294
+ if (!verdict) return void 0;
7295
+ const shown = shownPath.replace(/\*\//g, "* /");
7296
+ if (verdict.kind === "gated") {
7297
+ warnings.push({ code: "gated-page", message: `${label} is access-restricted and was NOT inlined; it was left as a comment.`, source });
7298
+ return `{/* Access-restricted content withheld: ${shown} */}`;
7299
+ }
7300
+ if (verdict.kind === "snippet-gated") {
7301
+ warnings.push({
7302
+ code: "gated-page",
7303
+ message: `${label} declares access rules (${verdict.reason}). Mintlify does not enforce groups on snippets, but it was NOT inlined to be safe; it was left as a comment.`,
7304
+ source
7305
+ });
7306
+ return `{/* Access-restricted content withheld: ${shown} */}`;
7307
+ }
7308
+ warnings.push({
7309
+ code: "skipped-file",
7310
+ message: `${label} ${verdict.kind === "oversized" ? "is too large to inline (over 2 MB)" : "has frontmatter that could not be read"} and was NOT inlined; it was left as a comment.`,
7311
+ source
7312
+ });
7313
+ return `{/* ${verdict.kind === "oversized" ? "Oversized" : "Unreadable"} content not inlined: ${shown} */}`;
7314
+ };
7315
+ let withoutImports = replaceOutsideCode(raw, (source2) => source2.replace(
7057
7316
  SNIPPET_IMPORT_PATTERN,
7058
7317
  (_statement, namedComponent, defaultComponent, sourcePath) => {
7059
7318
  const componentName = namedComponent ?? defaultComponent;
7060
7319
  try {
7061
7320
  const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
7062
7321
  if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) throw new Error("file not found");
7322
+ const blocked = blockInline(candidate, sourcePath, `Imported ${sourcePath}`);
7323
+ if (blocked) {
7324
+ snippets.set(componentName, blocked);
7325
+ return "";
7326
+ }
7063
7327
  const nested = inlineMdxSnippets(
7064
7328
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7065
7329
  candidate,
@@ -7067,7 +7331,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7067
7331
  warnings,
7068
7332
  depth + 1,
7069
7333
  siteRoot,
7070
- globalAliases
7334
+ globalAliases,
7335
+ gate
7071
7336
  );
7072
7337
  const declaration = statefulSnippetDeclaration(nested, componentName);
7073
7338
  if (declaration) preservedDeclarations.set(componentName, declaration);
@@ -7084,7 +7349,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7084
7349
  }
7085
7350
  }
7086
7351
  ));
7087
- withoutImports = replaceOutsideCode(withoutImports, (source) => source.replace(
7352
+ withoutImports = replaceOutsideCode(withoutImports, (source2) => source2.replace(
7088
7353
  SNIPPET_VALUE_IMPORT_PATTERN,
7089
7354
  (statement, names, sourcePath) => {
7090
7355
  const bindings = names.split(",").map((name) => name.trim()).filter(Boolean).map((name) => {
@@ -7094,7 +7359,16 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7094
7359
  if (bindings.length === 0 || bindings.some((binding) => !binding)) return statement;
7095
7360
  try {
7096
7361
  const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
7097
- if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) return statement;
7362
+ if (!existsSync5(candidate) || !lstatSync5(candidate).isFile() || gate?.(candidate)?.kind === "gated") return statement;
7363
+ const blocked = blockInline(candidate, sourcePath);
7364
+ if (blocked) {
7365
+ const declarations2 = [];
7366
+ for (const binding of bindings) {
7367
+ if (/^[A-Z]/.test(binding.exported)) snippets.set(binding.local, blocked);
7368
+ else declarations2.push(`export const ${binding.local} = undefined;`);
7369
+ }
7370
+ return declarations2.join("\n");
7371
+ }
7098
7372
  const snippetSource = readFileSync5(candidate, "utf8");
7099
7373
  const values = staticNamedSnippetValues(snippetSource);
7100
7374
  const declarations = [];
@@ -7119,7 +7393,12 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7119
7393
  ));
7120
7394
  const localNames = locallyDeclaredNames(withoutImports);
7121
7395
  for (const [componentName, candidate] of globalAliases) {
7122
- if (snippets.has(componentName) || localNames.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
7396
+ if (snippets.has(componentName) || localNames.has(componentName) || gate?.(candidate)?.kind === "gated" || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
7397
+ const blocked = blockInline(candidate, relative5(repositoryRoot, candidate).replace(/\\/g, "/"));
7398
+ if (blocked) {
7399
+ snippets.set(componentName, blocked);
7400
+ continue;
7401
+ }
7123
7402
  const nested = inlineMdxSnippets(
7124
7403
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7125
7404
  candidate,
@@ -7127,7 +7406,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7127
7406
  warnings,
7128
7407
  depth + 1,
7129
7408
  siteRoot,
7130
- globalAliases
7409
+ globalAliases,
7410
+ gate
7131
7411
  );
7132
7412
  const declaration = statefulSnippetDeclaration(nested, componentName);
7133
7413
  if (declaration) preservedDeclarations.set(componentName, declaration);
@@ -7155,6 +7435,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7155
7435
  }
7156
7436
  }).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
7157
7437
  if (!candidate) throw new Error("file not found");
7438
+ const blocked = blockInline(candidate, filePath, `Snippet file="${filePath}"`);
7439
+ if (blocked) return blocked;
7158
7440
  return inlineMdxSnippets(
7159
7441
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7160
7442
  candidate,
@@ -7162,7 +7444,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7162
7444
  warnings,
7163
7445
  depth + 1,
7164
7446
  siteRoot,
7165
- globalAliases
7447
+ globalAliases,
7448
+ gate
7166
7449
  );
7167
7450
  } catch {
7168
7451
  warnings.push({
@@ -7254,6 +7537,7 @@ function migrateRepository(options) {
7254
7537
  const referenceOrder = /* @__PURE__ */ new Map();
7255
7538
  let docusaurusSidebars = null;
7256
7539
  let mintlifyConfig = null;
7540
+ const mintlifyGatedRefs = /* @__PURE__ */ new Map();
7257
7541
  let fernRawConfig = null;
7258
7542
  let fernBasePath = "";
7259
7543
  let fernVersionPath = "";
@@ -7274,6 +7558,10 @@ function migrateRepository(options) {
7274
7558
  const projected = projectMintlifyNavigation(config);
7275
7559
  docsConfig = projected.docsConfig;
7276
7560
  warnings.push(...projected.warnings);
7561
+ for (const gated of projected.gatedReferences) {
7562
+ const gatedKey = normalizedReferenceKey(gated.ref).toLowerCase();
7563
+ if (!mintlifyGatedRefs.has(gatedKey)) mintlifyGatedRefs.set(gatedKey, gated.reason);
7564
+ }
7277
7565
  for (const [index, reference] of projected.pageReferences.entries()) {
7278
7566
  const key = normalizedReferenceKey(reference.ref);
7279
7567
  if (!referenceMap.has(key)) referenceMap.set(key, reference);
@@ -7359,10 +7647,11 @@ function migrateRepository(options) {
7359
7647
  const versionCeiling = lastReferencedIndexByVersionLocale.get(blockKey);
7360
7648
  return versionCeiling !== void 0 ? versionCeiling + 1 : Number.MAX_SAFE_INTEGER;
7361
7649
  } : void 0;
7362
- const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank);
7650
+ const sourceBudget = options.maxSourceFiles ?? MAX_SOURCE_FILES;
7651
+ const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank, sourceBudget);
7363
7652
  const mintignoreFilteredFiles = mintignoreMatcher ? scannedFiles.filter((file) => !mintignoreMatcher.ignores(file.relativePath)) : scannedFiles;
7364
- const discoveryBudgetApplied = discoveryRank !== void 0 && mintignoreFilteredFiles.length > MAX_SOURCE_FILES;
7365
- const files = discoveryRank ? selectFilesWithinBudget(mintignoreFilteredFiles, discoveryRank, warnings, allVersionPrefixes) : mintignoreFilteredFiles;
7653
+ const discoveryBudgetApplied = discoveryRank !== void 0 && mintignoreFilteredFiles.length > sourceBudget;
7654
+ const files = discoveryRank ? selectFilesWithinBudget(mintignoreFilteredFiles, discoveryRank, warnings, allVersionPrefixes, sourceBudget) : mintignoreFilteredFiles;
7366
7655
  if (platform === "fern" && fernProjectRoot) {
7367
7656
  const discovered2 = new Set(files.map((file) => file.relativePath));
7368
7657
  for (const sourcePath of fernExternalSourcePaths) {
@@ -7392,6 +7681,108 @@ function migrateRepository(options) {
7392
7681
  const docusaurusDescriptors = [];
7393
7682
  const docusaurusDocCardRoutes = /* @__PURE__ */ new Set();
7394
7683
  const seenPageIds = /* @__PURE__ */ new Set();
7684
+ const quarantinedFiles = [];
7685
+ const withheldAssetPaths = /* @__PURE__ */ new Set();
7686
+ const publishedExact = /* @__PURE__ */ new Set();
7687
+ const publishedLoose = /* @__PURE__ */ new Set();
7688
+ const withholdAsset = (assetPath, onDiskSpelling) => {
7689
+ withheldAssetPaths.add(publicAssetKey(onDiskSpelling ?? assetPath));
7690
+ };
7691
+ let sawPublicTrue = false;
7692
+ const gateByPath = /* @__PURE__ */ new Map();
7693
+ const withheldSpecRefs = [];
7694
+ const publishedSpecRefs = [];
7695
+ const withheldPaths = /* @__PURE__ */ new Set();
7696
+ const isDocFile = (file) => [".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase());
7697
+ const isGateCandidate = (file) => isDocFile(file) && !file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
7698
+ const classifyPageGate = (file) => {
7699
+ let head = { publicTrue: false };
7700
+ let unreadableGate;
7701
+ try {
7702
+ const size = lstatSync5(file.absolutePath).size;
7703
+ const raw = readFrontmatterHead(file.absolutePath) ?? (size > MAX_PAGE_BYTES ? void 0 : readFileSync5(file.absolutePath, "utf8"));
7704
+ if (raw === void 0) throw new Error("frontmatter is not terminated within the bounded read");
7705
+ head = pageHeadGateReason(raw);
7706
+ } catch {
7707
+ unreadableGate = "frontmatter could not be read";
7708
+ }
7709
+ const reason = head.reason ?? unreadableGate ?? mintlifyGatedRefs.get(normalizedReferenceKey(file.relativePath).toLowerCase());
7710
+ return { reason, publicTrue: head.publicTrue, openapi: head.openapi };
7711
+ };
7712
+ const keptFiles = new Set(files);
7713
+ const droppedGatedFiles = [];
7714
+ const withheldDocFiles = [];
7715
+ let droppedPageCount = 0;
7716
+ const scanTruncated = scannedFiles.length >= (discoveryRank ? sourceBudget * RANKED_WALK_FACTOR : sourceBudget);
7717
+ if (platform === "mintlify") {
7718
+ for (const file of files) {
7719
+ if (isDocFile(file) && !isGateCandidate(file)) {
7720
+ const snippetReason = classifyPageGate(file).reason;
7721
+ if (snippetReason) withheldDocFiles.push(file);
7722
+ continue;
7723
+ }
7724
+ if (!isGateCandidate(file)) continue;
7725
+ const verdict = classifyPageGate(file);
7726
+ gateByPath.set(file.absolutePath, { reason: verdict.reason, publicTrue: verdict.publicTrue });
7727
+ if (verdict.openapi) (verdict.reason ? withheldSpecRefs : publishedSpecRefs).push(verdict.openapi);
7728
+ if (verdict.reason) withheldPaths.add(file.absolutePath);
7729
+ }
7730
+ for (const file of mintignoreFilteredFiles) {
7731
+ if (keptFiles.has(file) || !isDocFile(file)) continue;
7732
+ droppedPageCount++;
7733
+ if (!isGateCandidate(file)) {
7734
+ const snippetReason = classifyPageGate(file).reason;
7735
+ if (snippetReason) {
7736
+ withheldDocFiles.push(file);
7737
+ warnings.push({ code: "gated-page", message: `Access-restricted snippet (${snippetReason}) was dropped by the file limit; its assets were kept out of public/.`, source: file.relativePath });
7738
+ }
7739
+ continue;
7740
+ }
7741
+ const verdict = classifyPageGate(file);
7742
+ if (!verdict.reason) continue;
7743
+ if (verdict.openapi) withheldSpecRefs.push(verdict.openapi);
7744
+ withheldPaths.add(file.absolutePath);
7745
+ droppedGatedFiles.push({ file, reason: verdict.reason });
7746
+ }
7747
+ for (const file of scannedFiles) {
7748
+ if (!mintignoreMatcher?.ignores(file.relativePath) || !isDocFile(file)) continue;
7749
+ const verdict = classifyPageGate(file);
7750
+ if (!verdict.reason) continue;
7751
+ if (verdict.openapi) withheldSpecRefs.push(verdict.openapi);
7752
+ withheldDocFiles.push(file);
7753
+ }
7754
+ }
7755
+ const pagesNotClassified = droppedPageCount > 0 || platform === "mintlify" && scanTruncated;
7756
+ const hasWithheldContent = withheldPaths.size > 0 || withheldDocFiles.length > 0;
7757
+ const trackPublishedRefs = platform === "mintlify" && (hasWithheldContent || pagesNotClassified);
7758
+ const withheldCanonical = new Set([...withheldPaths].map(canonicalPath));
7759
+ const verdictCache = /* @__PURE__ */ new Map();
7760
+ const classifyInlineCandidate = (candidate, key) => {
7761
+ if (withheldCanonical.has(key)) return { kind: "gated", reason: "access-restricted" };
7762
+ const shown = relative5(contentRoot, candidate).replace(/\\/g, "/");
7763
+ const inSnippetDirectory = shown.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
7764
+ let head;
7765
+ try {
7766
+ head = readFrontmatterHead(candidate);
7767
+ } catch {
7768
+ return { kind: "unreadable", reason: "could not be read" };
7769
+ }
7770
+ const gated = head === void 0 ? void 0 : pageHeadGateReason(head).reason;
7771
+ if (gated) return { kind: inSnippetDirectory ? "snippet-gated" : "gated", reason: gated };
7772
+ const navigationGated = inSnippetDirectory ? void 0 : mintlifyGatedRefs.get(normalizedReferenceKey(shown).toLowerCase());
7773
+ if (navigationGated) return { kind: "gated", reason: navigationGated };
7774
+ if (lstatSync5(candidate).size > MAX_PAGE_BYTES) return { kind: "oversized", reason: "over 2 MB" };
7775
+ return head === void 0 ? { kind: "unreadable", reason: "frontmatter not terminated in the bounded read" } : void 0;
7776
+ };
7777
+ const inlineGate = platform === "mintlify" ? (candidate) => {
7778
+ const key = canonicalPath(candidate);
7779
+ let verdict = verdictCache.get(key);
7780
+ if (verdict === void 0) {
7781
+ verdict = classifyInlineCandidate(candidate, key) ?? null;
7782
+ verdictCache.set(key, verdict);
7783
+ }
7784
+ return verdict ?? void 0;
7785
+ } : void 0;
7395
7786
  const fernIdRenames = /* @__PURE__ */ new Map();
7396
7787
  let skipped = 0;
7397
7788
  let discovered = files.length;
@@ -7404,6 +7795,47 @@ function migrateRepository(options) {
7404
7795
  return leftOrder - rightOrder || left.relativePath.localeCompare(right.relativePath);
7405
7796
  });
7406
7797
  const snippetAliases = platform === "mintlify" && mintlifyProjectRoot ? globalSnippetAliases(files, repositoryDir, mintlifyProjectRoot) : /* @__PURE__ */ new Map();
7798
+ droppedGatedFiles.forEach(({ file, reason }, index) => {
7799
+ if (index < MAX_DROPPED_GATED_WARNINGS) {
7800
+ warnings.push({
7801
+ code: "gated-page",
7802
+ message: `Access-restricted page (${reason}) was dropped by the file limit, so it was NOT published and was not saved under ${QUARANTINE_DIRECTORY}/; recover it from the source repository.`,
7803
+ source: file.relativePath
7804
+ });
7805
+ }
7806
+ if (!mintlifyProjectRoot) return;
7807
+ try {
7808
+ if (lstatSync5(file.absolutePath).size > MAX_WITHHELD_SCAN_BYTES) throw new Error("too large to scan");
7809
+ rewriteRepositoryAssetLinks(
7810
+ inlineMdxSnippets(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, [], 0, mintlifyProjectRoot, snippetAliases, inlineGate),
7811
+ file.absolutePath,
7812
+ mintlifyProjectRoot,
7813
+ withholdAsset
7814
+ );
7815
+ } catch {
7816
+ }
7817
+ });
7818
+ for (const file of withheldDocFiles) {
7819
+ try {
7820
+ if (!mintlifyProjectRoot) continue;
7821
+ if (lstatSync5(file.absolutePath).size > MAX_WITHHELD_SCAN_BYTES) {
7822
+ warnings.push({
7823
+ code: "gated-page",
7824
+ message: "Access-restricted file is too large to scan, so the assets it uses could not be checked and may have been copied to public/. Review them before publishing.",
7825
+ source: file.relativePath
7826
+ });
7827
+ continue;
7828
+ }
7829
+ rewriteRepositoryAssetLinks(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, mintlifyProjectRoot, withholdAsset);
7830
+ } catch {
7831
+ }
7832
+ }
7833
+ if (droppedGatedFiles.length > MAX_DROPPED_GATED_WARNINGS) {
7834
+ warnings.push({
7835
+ code: "gated-page",
7836
+ message: `${droppedGatedFiles.length - MAX_DROPPED_GATED_WARNINGS} more access-restricted page(s) were dropped by the file limit and NOT published; recover them from the source repository.`
7837
+ });
7838
+ }
7407
7839
  for (const file of pageFiles) {
7408
7840
  if (!isDocumentationExtension(file.relativePath)) continue;
7409
7841
  if (platform === "fern" && !exactReferenceMap.has(exactReferenceKey(file.relativePath)) && !referenceMap.has(normalizedReferenceKey(file.relativePath))) {
@@ -7420,7 +7852,7 @@ function migrateRepository(options) {
7420
7852
  skipped++;
7421
7853
  continue;
7422
7854
  }
7423
- if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) {
7855
+ if (![".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase())) {
7424
7856
  skipped++;
7425
7857
  warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
7426
7858
  continue;
@@ -7429,9 +7861,64 @@ function migrateRepository(options) {
7429
7861
  if (size > MAX_PAGE_BYTES) {
7430
7862
  skipped++;
7431
7863
  warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
7864
+ const oversizedGate = gateByPath.get(file.absolutePath)?.reason;
7865
+ if (oversizedGate) {
7866
+ warnings.push({
7867
+ code: "gated-page",
7868
+ message: `Access-restricted page (${oversizedGate}) is too large to migrate (over 2 MB), so it was NOT published and was not saved under ${QUARANTINE_DIRECTORY}/; recover it from the source repository.`,
7869
+ source: file.relativePath
7870
+ });
7871
+ }
7872
+ if (oversizedGate && mintlifyProjectRoot) {
7873
+ if (size > MAX_WITHHELD_SCAN_BYTES) {
7874
+ warnings.push({
7875
+ code: "gated-page",
7876
+ message: "Access-restricted page is too large to scan, so the assets it uses could not be checked and may have been copied to public/. Review them before publishing.",
7877
+ source: file.relativePath
7878
+ });
7879
+ } else {
7880
+ rewriteRepositoryAssetLinks(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, mintlifyProjectRoot, withholdAsset);
7881
+ }
7882
+ }
7432
7883
  continue;
7433
7884
  }
7434
7885
  const key = normalizedReferenceKey(file.relativePath);
7886
+ if (platform === "mintlify") {
7887
+ const gate = gateByPath.get(file.absolutePath);
7888
+ if (gate?.publicTrue) sawPublicTrue = true;
7889
+ const gateReason = gate?.reason;
7890
+ if (gateReason) {
7891
+ skipped++;
7892
+ if (file.relativePath.split("/").some((segment) => segment === "..")) {
7893
+ warnings.push({ code: "gated-page", message: `Access-restricted page (${gateReason}) was withheld from the site, but its path could not be preserved safely; recover it from the source repository.`, source: file.relativePath });
7894
+ continue;
7895
+ }
7896
+ try {
7897
+ if (mintlifyProjectRoot) {
7898
+ rewriteRepositoryAssetLinks(
7899
+ inlineMdxSnippets(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, [], 0, mintlifyProjectRoot, snippetAliases, inlineGate),
7900
+ file.absolutePath,
7901
+ mintlifyProjectRoot,
7902
+ withholdAsset
7903
+ );
7904
+ }
7905
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/${file.relativePath}`, content: readFileSync5(file.absolutePath) });
7906
+ } catch {
7907
+ warnings.push({
7908
+ code: "gated-page",
7909
+ message: `Access-restricted page (${gateReason}) was NOT published, but it could not be read, so it was not copied to ${QUARANTINE_DIRECTORY}/; recover it from the source repository.`,
7910
+ source: file.relativePath
7911
+ });
7912
+ continue;
7913
+ }
7914
+ warnings.push({
7915
+ code: "gated-page",
7916
+ message: `Access-restricted on the source site (${gateReason}), so it was NOT published. The original is saved at ${QUARANTINE_DIRECTORY}/${file.relativePath}; links from other pages to it will break.`,
7917
+ source: file.relativePath
7918
+ });
7919
+ continue;
7920
+ }
7921
+ }
7435
7922
  const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
7436
7923
  let locale = referenced?.locale;
7437
7924
  let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
@@ -7458,8 +7945,11 @@ function migrateRepository(options) {
7458
7945
  warnings,
7459
7946
  0,
7460
7947
  mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir,
7461
- snippetAliases
7948
+ snippetAliases,
7949
+ inlineGate
7462
7950
  );
7951
+ const publishedText = trackPublishedRefs ? raw : void 0;
7952
+ const pageAssetReferences = [];
7463
7953
  if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
7464
7954
  raw = normalizeExplicitHeadingIds(raw);
7465
7955
  }
@@ -7551,8 +8041,8 @@ function migrateRepository(options) {
7551
8041
  }
7552
8042
  if (platform === "fern" && fernHiddenIds.has(navigationId)) page.hidden = true;
7553
8043
  if (platform === "mintlify" && mintlifyProjectRoot) {
7554
- page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath) => {
7555
- addAssetReference(assetPath, file.relativePath);
8044
+ page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath, onDiskSpelling) => {
8045
+ pageAssetReferences.push(publicAssetKey(onDiskSpelling ?? assetPath));
7556
8046
  });
7557
8047
  }
7558
8048
  if (platform === "fern" && fernProjectRoot) {
@@ -7616,6 +8106,8 @@ function migrateRepository(options) {
7616
8106
  }
7617
8107
  seenPageIds.add(page.id);
7618
8108
  pages.push(page);
8109
+ for (const assetPath of pageAssetReferences) addAssetReference(assetPath, file.relativePath);
8110
+ if (publishedText !== void 0) addPathReferences(publishedText, posix3.dirname(file.relativePath).replace(/^\.$/, ""), publishedExact, publishedLoose);
7619
8111
  if (hasDocusaurusDocCardList) {
7620
8112
  docusaurusDocCardRoutes.add(page.navigationId);
7621
8113
  }
@@ -7658,7 +8150,7 @@ function migrateRepository(options) {
7658
8150
  const withinFern = relative5(fernProjectRoot, absolutePath);
7659
8151
  const assetRelative = withinFern !== ".." && !withinFern.startsWith(`..${sep2}`) && !isAbsolute2(withinFern) ? withinFern : relative5(repositoryDir, absolutePath);
7660
8152
  const assetPath = normalizeAssetPath(assetRelative.replace(/\\/g, "/"));
7661
- if (!assetPath || !ASSET_EXTENSIONS.has(extname5(assetPath).toLowerCase())) return;
8153
+ if (!assetPath || !ASSET_EXTENSIONS.has(extname6(assetPath).toLowerCase())) return;
7662
8154
  if (source) {
7663
8155
  fernBrandAssetPaths.set(source, assetPath);
7664
8156
  addAssetReference(assetPath, "fern/docs.yml");
@@ -7689,24 +8181,59 @@ function migrateRepository(options) {
7689
8181
  }));
7690
8182
  }) : [];
7691
8183
  const assetCandidates = [];
8184
+ const mintlifyFonts = platform === "mintlify" ? mintlifyFontSources(mintlifyConfig) : [];
8185
+ const fontRelativePaths = new Set(mintlifyFonts.flatMap((font) => font.path ? [font.path] : []));
8186
+ const importedComponentSources = new Set((componentMigrator?.files() ?? []).flatMap((file) => {
8187
+ const source = /^src\/mdx\/migrated\/[^/]+\/source\/(.+)$/.exec(file.path)?.[1];
8188
+ return source ? [resolvePath(repositoryDir, source)] : [];
8189
+ }));
8190
+ const siteAssetPaths = /* @__PURE__ */ new Map();
8191
+ const fontAssetByRelative = /* @__PURE__ */ new Map();
7692
8192
  for (const file of [...files, ...repositoryAssets, ...fernReferencedAssets]) {
7693
8193
  const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
7694
- if (!ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase())) continue;
8194
+ const siteKind = platform !== "mintlify" ? void 0 : fontRelativePaths.has(file.relativePath) ? "font" : isMintlifyServedScriptOrStyle(file.relativePath) && !importedComponentSources.has(file.absolutePath) ? extname6(file.relativePath).toLowerCase() === ".css" ? "style" : "script" : void 0;
8195
+ if (!siteKind && !ASSET_EXTENSIONS.has(extname6(file.relativePath).toLowerCase())) continue;
7695
8196
  if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment) && !(platform === "fern" && referencedAssetPaths.has(file.relativePath))) continue;
7696
8197
  const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
7697
8198
  const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
7698
8199
  if (!assetPath) continue;
7699
- assetCandidates.push({ file, assetPath, size: lstatSync5(file.absolutePath).size });
8200
+ if (siteKind) {
8201
+ siteAssetPaths.set(assetPath, siteKind);
8202
+ if (siteKind === "font") fontAssetByRelative.set(file.relativePath, assetPath);
8203
+ }
8204
+ const candidateSize = lstatSync5(file.absolutePath).size;
8205
+ if (siteKind === "style" || siteKind === "script") {
8206
+ if (trackPublishedRefs && candidateSize <= MAX_PAGE_BYTES) addPathReferences(readFileSync5(file.absolutePath, "utf8"), posix3.dirname(file.relativePath).replace(/^\.$/, ""), publishedExact, publishedLoose);
8207
+ }
8208
+ assetCandidates.push({ file, assetPath, size: candidateSize });
7700
8209
  }
7701
8210
  const isReferenced = (candidate) => referencedAssetPaths.has(candidate.assetPath);
7702
8211
  const orderedAssetCandidates = [
7703
8212
  ...assetCandidates.filter((candidate) => isReferenced(candidate)),
7704
8213
  ...assetCandidates.filter((candidate) => !isReferenced(candidate))
7705
8214
  ];
8215
+ const quarantinedPageCount = quarantinedFiles.length;
8216
+ const destinationCounts = /* @__PURE__ */ new Map();
8217
+ for (const { assetPath } of assetCandidates) destinationCounts.set(assetPath, (destinationCounts.get(assetPath) ?? 0) + 1);
8218
+ if (trackPublishedRefs) {
8219
+ const publishedConfig = JSON.stringify(mintlifyConfig ?? {}, (_key, value) => value && typeof value === "object" && !Array.isArray(value) && navigationGateReason(value) ? void 0 : value);
8220
+ addPathReferences(publishedConfig, "", publishedExact, publishedLoose);
8221
+ }
8222
+ for (const file of componentMigrator?.files() ?? []) {
8223
+ if (!trackPublishedRefs || typeof file.content !== "string") continue;
8224
+ const original = /^src\/mdx\/migrated\/[^/]+\/source\/(.+)$/.exec(file.path)?.[1];
8225
+ addPathReferences(file.content, original === void 0 ? "" : posix3.dirname(original).replace(/^\.$/, ""), publishedExact, publishedLoose);
8226
+ }
8227
+ const failClosedAssets = trackPublishedRefs;
8228
+ const isPublicReachable = (assetPath) => referencedAssetPaths.has(assetPath) || publishedExact.has(assetPath);
8229
+ const isQuarantinedAsset = (assetPath) => (withheldAssetPaths.has(assetPath) || failClosedAssets && !siteAssetPaths.has(assetPath)) && platform === "mintlify" && !(isPublicReachable(assetPath) && !(failClosedAssets && (destinationCounts.get(assetPath) ?? 0) > 1));
8230
+ let withheldAssetCount = 0;
8231
+ let unreferencedAssetCount = 0;
7706
8232
  let totalAssetBytes = 0;
7707
8233
  const overBudgetAssets = [];
7708
8234
  let overBudgetReferenced = 0;
7709
8235
  const lfsPointerAssets = [];
8236
+ const ambiguouslyNamedAssets = [];
7710
8237
  for (const { file, assetPath, size } of orderedAssetCandidates) {
7711
8238
  if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
7712
8239
  overBudgetAssets.push(file.relativePath);
@@ -7718,9 +8245,53 @@ function migrateRepository(options) {
7718
8245
  lfsPointerAssets.push(file.relativePath);
7719
8246
  continue;
7720
8247
  }
8248
+ if (isQuarantinedAsset(assetPath)) {
8249
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${assetPath}`, content });
8250
+ if (looselyNamed(assetPath, publishedLoose) || (destinationCounts.get(assetPath) ?? 0) > 1) ambiguouslyNamedAssets.push(assetPath);
8251
+ if (withheldAssetPaths.has(assetPath)) withheldAssetCount++;
8252
+ else unreferencedAssetCount++;
8253
+ continue;
8254
+ }
7721
8255
  assets.push({ path: assetPath, content });
7722
8256
  totalAssetBytes += size;
7723
8257
  }
8258
+ if (ambiguouslyNamedAssets.length > 0) {
8259
+ warnings.push({
8260
+ code: "gated-page",
8261
+ message: `${ambiguouslyNamedAssets.length} asset(s) were kept out of public/ because a published page names a file like ${ambiguouslyNamedAssets.length === 1 ? "it" : "them"} in a way that does not give its folder (or differs in letter case), so it cannot be matched to one file (or two files share one public path): ${listAssetPaths(ambiguouslyNamedAssets)}. Copy any that published pages need from ${QUARANTINE_DIRECTORY}/assets/ into public/ by hand.`
8262
+ });
8263
+ }
8264
+ if (platform === "mintlify") {
8265
+ const copiedSite = assets.flatMap((asset) => siteAssetPaths.has(asset.path) ? [{ path: asset.path, kind: siteAssetPaths.get(asset.path) }] : []);
8266
+ const scripts = copiedSite.filter((entry) => entry.kind === "script");
8267
+ if (scripts.length > 0) {
8268
+ docsConfig = {
8269
+ ...docsConfig,
8270
+ customScripts: [...docsConfig.customScripts ?? [], ...scripts.map((entry) => ({ src: `/${entry.path.split("/").map(encodeURIComponent).join("/")}`, strategy: "afterInteractive" }))]
8271
+ };
8272
+ warnings.push({
8273
+ code: "unsupported-config",
8274
+ message: `${scripts.length} script(s) from the content directory (${scripts.map((entry) => entry.path).join(", ")}) now load on every page via docs.json customScripts, as they did on Mintlify. They may target Mintlify's page structure; review them.`
8275
+ });
8276
+ }
8277
+ for (const entry of copiedSite.filter((item) => item.kind === "style")) {
8278
+ warnings.push({
8279
+ code: "unsupported-config",
8280
+ message: `Migration gap: custom stylesheet ${entry.path} is NOT applied. It was copied to public/${entry.path} but no page loads it, Thally cannot load a stylesheet from docs.json, and Mintlify-specific selectors will not match Thally's markup. Port the styles you need by hand into src/app/globals.css.`,
8281
+ source: entry.path
8282
+ });
8283
+ }
8284
+ for (const font of mintlifyFonts) {
8285
+ const copiedPath = font.path ? fontAssetByRelative.get(font.path) : void 0;
8286
+ const copied = copiedPath !== void 0 && assets.some((asset) => asset.path === copiedPath);
8287
+ const detail = font.remote ? `its source ${font.source} is a remote URL and was not downloaded` : !font.path ? `its source ${font.source} is not a safe local .woff/.woff2/.ttf/.otf path inside the docs directory and was not imported` : !copiedPath ? `its source file ${font.source} was not found in the docs directory` : copied ? `its file was copied to public/${copiedPath}` : null;
8288
+ if (detail === null) continue;
8289
+ warnings.push({
8290
+ code: "unsupported-config",
8291
+ message: `Self-hosted font "${font.family}" is not applied: Thally's fonts setting loads Google Fonts by family only; ${detail}. The family name was still mapped, which loads a Google font of that name if one exists.`
8292
+ });
8293
+ }
8294
+ }
7724
8295
  if (overBudgetAssets.length > 0) {
7725
8296
  warnings.push({
7726
8297
  code: "limit-reached",
@@ -7733,8 +8304,8 @@ function migrateRepository(options) {
7733
8304
  message: `${lfsPointerAssets.length} asset file${lfsPointerAssets.length === 1 ? " is a Git LFS pointer" : "s are Git LFS pointers"}, not real content, and ${lfsPointerAssets.length === 1 ? "was" : "were"} not copied (Git LFS was skipped during clone because the host has no git-lfs binary): ${listAssetPaths(lfsPointerAssets)}. Install git-lfs and re-run the migration, or copy the real files into public/ manually.`
7734
8305
  });
7735
8306
  }
7736
- if (!discoveryBudgetApplied && files.length >= MAX_SOURCE_FILES) {
7737
- warnings.push({ code: "limit-reached", message: `Stopped scanning after ${MAX_SOURCE_FILES} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.` });
8307
+ if (!discoveryBudgetApplied && files.length >= sourceBudget) {
8308
+ warnings.push({ code: "limit-reached", message: `Stopped scanning after ${sourceBudget} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.` });
7738
8309
  }
7739
8310
  if (platform === "docusaurus") {
7740
8311
  const projected = projectDocusaurusNavigation({
@@ -7861,11 +8432,19 @@ function migrateRepository(options) {
7861
8432
  rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
7862
8433
  }
7863
8434
  } else if (platform === "mintlify") {
8435
+ const warnSharedSpec = (path) => {
8436
+ warnings.push({
8437
+ code: "gated-page",
8438
+ message: `OpenAPI spec ${path} is shared with access-restricted pages and is published; it may describe restricted endpoints. Review it before publishing.`,
8439
+ source: path
8440
+ });
8441
+ };
7864
8442
  const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
7865
8443
  for (const spec of resolvedSpecs) {
7866
8444
  if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
7867
8445
  assets.push(specAsset(spec.filename, spec.content));
7868
8446
  }
8447
+ if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath);
7869
8448
  }
7870
8449
  if (resolvedSpecs.length > 0) {
7871
8450
  docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
@@ -7878,10 +8457,17 @@ function migrateRepository(options) {
7878
8457
  const fallback = findOpenApi(files);
7879
8458
  if (fallback) {
7880
8459
  const filename = basename3(fallback.relativePath);
7881
- if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
7882
- assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
8460
+ const matches = (ref) => specRefMatches(ref, fallback.relativePath, filename, true);
8461
+ if (withheldSpecRefs.some(matches) && !publishedSpecRefs.some(matches)) {
8462
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${specAssetPath(filename)}`, content: readFileSync5(fallback.absolutePath) });
8463
+ withheldAssetCount++;
8464
+ } else {
8465
+ if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
8466
+ assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
8467
+ }
8468
+ docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
8469
+ if (withheldSpecRefs.some(matches)) warnSharedSpec(fallback.relativePath);
7883
8470
  }
7884
- docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
7885
8471
  }
7886
8472
  }
7887
8473
  }
@@ -8192,10 +8778,9 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8192
8778
  docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
8193
8779
  }
8194
8780
  if (mintlifyConfig) {
8195
- const appearance = mintlifyConfig.appearance && typeof mintlifyConfig.appearance === "object" ? mintlifyConfig.appearance : null;
8196
- const defaultMode = appearance?.default;
8197
- if (defaultMode === "light" || defaultMode === "dark" || defaultMode === "system") {
8198
- docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, default: defaultMode } };
8781
+ const appearance = mintlifyAppearance(mintlifyConfig, warnings);
8782
+ if (Object.keys(appearance).length > 0) {
8783
+ docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, ...appearance } };
8199
8784
  }
8200
8785
  const background = mintlifyConfig.background && typeof mintlifyConfig.background === "object" ? mintlifyConfig.background : null;
8201
8786
  if (background) {
@@ -8228,6 +8813,25 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8228
8813
  code: "unsupported-config",
8229
8814
  message: `Brand asset(s) were referenced but not imported: ${[...new Set(missingBrand)].join(", ")}. Add these files under public/ or update docs.json.`
8230
8815
  });
8816
+ if (platform === "mintlify") {
8817
+ docsConfig = { ...docsConfig, markdown: { enabled: true } };
8818
+ if (quarantinedPageCount > 0 || withheldAssetCount > 0 || unreferencedAssetCount > 0) {
8819
+ const assetNote = withheldAssetCount > 0 ? `${withheldAssetCount} file(s) used only by access-restricted pages were kept out of public/ and saved under ${QUARANTINE_DIRECTORY}/assets/. ` : "";
8820
+ const unreferencedReason = [
8821
+ ...hasWithheldContent ? ["this site has access-restricted content"] : [],
8822
+ ...pagesNotClassified ? ["pages were dropped by the file limit (or the file scan stopped at its cap) and could not all be checked for access restrictions"] : []
8823
+ ].join(" and ");
8824
+ const unreferencedNote = unreferencedAssetCount > 0 ? `${unreferencedAssetCount} unreferenced asset(s) were kept out of public/ because ${unreferencedReason}; review ${QUARANTINE_DIRECTORY}/assets/ and copy any that published pages need. ` : "";
8825
+ warnings.push({
8826
+ code: "gated-page",
8827
+ message: (quarantinedPageCount > 0 ? `${quarantinedPageCount} access-restricted page(s) were withheld from the published site and saved under ${QUARANTINE_DIRECTORY}/ (local only: git-ignored, never served or deployed). ` : "") + assetNote + unreferencedNote + "Assets that published pages also use are still copied to public/. Review them before deciding how to publish or protect that content."
8828
+ });
8829
+ }
8830
+ warnings.push({
8831
+ code: "gated-page",
8832
+ message: (sawPublicTrue ? "Some pages set `public: true`, which means the source site used Mintlify authentication and every page WITHOUT it was private. " : "") + "Access control set in the Mintlify dashboard is not visible in the repository. Check the source site's dashboard access settings before publishing: Thally will publish ALL imported pages publicly. Confirm nothing here was meant to stay private before deploying."
8833
+ });
8834
+ }
8231
8835
  if (fernRawConfig?.logo && !logoLight && !logo.light) warnings.push({
8232
8836
  code: "unsupported-config",
8233
8837
  message: "Fern supplied a logo through its global theme without a local asset; add a logo path to docs.json after import."
@@ -8240,6 +8844,8 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8240
8844
  assets,
8241
8845
  ...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
8242
8846
  ...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
8847
+ ...quarantinedFiles.length > 0 ? { quarantinedFiles } : {},
8848
+ ...droppedGatedFiles.length > 0 ? { droppedGatedPages: droppedGatedFiles.length } : {},
8243
8849
  docsConfig,
8244
8850
  ...mintlifyConfig ? {
8245
8851
  site: {
@@ -8372,6 +8978,7 @@ function renderMigrationFiles(bundle, options = {}) {
8372
8978
  })),
8373
8979
  ...bundle.assets.map((asset) => ({ path: asset.projectRelative ? asset.path : `public/${asset.path}`, content: asset.content })),
8374
8980
  ...(bundle.componentFiles ?? []).flatMap((file) => file.path === "src/mdx/custom-components.tsx" && options.existingComponentRegistry !== void 0 && typeof file.content === "string" ? mergeComponentRegistry(options.existingComponentRegistry, file.content) : [file]),
8981
+ ...bundle.quarantinedFiles ?? [],
8375
8982
  { path: "docs.json", content: `${JSON.stringify(config, null, 2)}
8376
8983
  ` }
8377
8984
  ];