@thallylabs/migrate 0.2.14 → 0.2.18

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
 
@@ -326,8 +326,8 @@ function leadingPageHeading(body) {
326
326
  const root = descriptionParser.parse(body);
327
327
  const first = root.children?.find((node) => node.type !== "mdxjsEsm" && node.type !== "html");
328
328
  if (first?.type !== "heading" || first.depth !== 1) return void 0;
329
- const text = (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" ? "" : node.value ?? (node.children ?? []).map(text).join("");
330
- const title = text(first).replace(/\s+/g, " ").trim();
329
+ const text2 = (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" ? "" : node.value ?? (node.children ?? []).map(text2).join("");
330
+ const title = text2(first).replace(/\s+/g, " ").trim();
331
331
  const start = first.position?.start.offset;
332
332
  const end = first.position?.end.offset;
333
333
  return title && start !== void 0 && end !== void 0 ? { title, start, end } : void 0;
@@ -400,16 +400,16 @@ function collectEsmBindingsByPattern(source, declared) {
400
400
  var FENCE_OPEN = /^(`{3,}|~{3,})/;
401
401
  function maskInlineSpansForMath(line) {
402
402
  const blocks = [];
403
- const stash = (text) => {
404
- blocks.push(text);
403
+ const stash = (text2) => {
404
+ blocks.push(text2);
405
405
  return `\0${blocks.length - 1}\0`;
406
406
  };
407
407
  const codeMasked = line.split(/(`[^`]*`)/).map((segment, index) => index % 2 === 1 ? stash(segment) : segment).join("");
408
408
  const tagMasked = codeMasked.replace(/<\/?[a-zA-Z][^<>]*>/g, stash);
409
- const masked = [tagMasked, tagMasked].reduce((text) => text.replace(/(?<!\$)\{[^{}]*\}/g, stash), tagMasked);
409
+ const masked = [tagMasked, tagMasked].reduce((text2) => text2.replace(/(?<!\$)\{[^{}]*\}/g, stash), tagMasked);
410
410
  return {
411
411
  masked,
412
- unmask: (text) => text.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
412
+ unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
413
413
  };
414
414
  }
415
415
  var TEX_SIGNAL = /\\[a-zA-Z]|[\^_{]/;
@@ -562,11 +562,11 @@ function splitFrontmatterBlock(raw) {
562
562
  return { front: bom + source.slice(0, end), body: source.slice(end) };
563
563
  }
564
564
  var MIGRATED_COMPONENT_TAG = /^Migrated[0-9a-f]{12}$/;
565
- function isSelfClosingJsx(text) {
566
- return /\/>\s*$/.test(text);
565
+ function isSelfClosingJsx(text2) {
566
+ return /\/>\s*$/.test(text2);
567
567
  }
568
- function renameTagOccurrences(text, name, newName) {
569
- let result = text;
568
+ function renameTagOccurrences(text2, name, newName) {
569
+ let result = text2;
570
570
  const openIndex = result.indexOf(`<${name}`);
571
571
  if (openIndex >= 0) result = `${result.slice(0, openIndex + 1)}${newName}${result.slice(openIndex + 1 + name.length)}`;
572
572
  const closeIndex = result.lastIndexOf(`</${name}`);
@@ -612,9 +612,9 @@ function replaceUnknownComponents(raw, warn, options = {}) {
612
612
  if (start === void 0 || end === void 0) return "";
613
613
  const isJsx = node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
614
614
  const tagRoot = node.name ? node.name.split(".")[0] : void 0;
615
- const text = body.slice(start, end);
615
+ const text2 = body.slice(start, end);
616
616
  if (isJsx && tagRoot === "Link" && !declared.has("Link")) {
617
- if (isSelfClosingJsx(text) || !node.children?.length) return renameTagOccurrences(text, node.name, "a");
617
+ if (isSelfClosingJsx(text2) || !node.children?.length) return renameTagOccurrences(text2, node.name, "a");
618
618
  const openEnd = node.children[0].position?.start.offset ?? start;
619
619
  const closeStart = node.children.at(-1).position?.end.offset ?? end;
620
620
  return renameTagOccurrences(body.slice(start, openEnd), node.name, "a") + renderChildrenSpan(node.children, openEnd, closeStart, body, renderNode) + renameTagOccurrences(body.slice(closeStart, end), node.name, "a");
@@ -624,7 +624,7 @@ function replaceUnknownComponents(raw, warn, options = {}) {
624
624
  warned.add(node.name);
625
625
  warn(node.name);
626
626
  }
627
- if (isSelfClosingJsx(text)) return "";
627
+ if (isSelfClosingJsx(text2)) return "";
628
628
  if (!node.children?.length) return "<div></div>";
629
629
  const openEnd = node.children[0].position?.start.offset ?? start;
630
630
  const closeStart = node.children.at(-1).position?.end.offset ?? end;
@@ -635,7 +635,7 @@ ${children}
635
635
 
636
636
  </div>` : `<div>${children}</div>`;
637
637
  }
638
- if (!node.children?.length) return text;
638
+ if (!node.children?.length) return text2;
639
639
  return renderChildrenSpan(node.children, start, end, body, renderNode);
640
640
  };
641
641
  return front + renderNode(root);
@@ -643,8 +643,8 @@ ${children}
643
643
  function maskCode(body) {
644
644
  const source = body.replace(/\u0000/g, "");
645
645
  const blocks = [];
646
- const stash = (text) => {
647
- blocks.push(text);
646
+ const stash = (text2) => {
647
+ blocks.push(text2);
648
648
  return `\0${blocks.length - 1}\0`;
649
649
  };
650
650
  const lines = [];
@@ -671,7 +671,7 @@ function maskCode(body) {
671
671
  if (fenceBuffer.length) lines.push(stash(fenceBuffer.join("\n")));
672
672
  return {
673
673
  masked: lines.join("\n"),
674
- unmask: (text) => text.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
674
+ unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
675
675
  };
676
676
  }
677
677
  function replaceOutsideCode(body, transform) {
@@ -690,7 +690,7 @@ function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePat
690
690
  const route = candidates.map((candidate) => routesBySourcePath.get(candidate)).find(Boolean);
691
691
  return route ? `/${route.replace(/^\/+/, "")}${suffix}` : target;
692
692
  };
693
- return replaceOutsideCode(body, (text) => text.replace(
693
+ return replaceOutsideCode(body, (text2) => text2.replace(
694
694
  /(\]\()(<[^>\n]+>|[^\s)]+)(?=\s|\))/g,
695
695
  (_match, opening, target) => `${opening}${target.startsWith("<") && target.endsWith(">") ? `<${rewrite(target.slice(1, -1))}>` : rewrite(target)}`
696
696
  ).replace(
@@ -2165,7 +2165,7 @@ const MintlifyComponents = {
2165
2165
  }
2166
2166
  function applyReplacements(source, replacements) {
2167
2167
  return replacements.sort((a, b) => b.start - a.start).reduce(
2168
- (text, edit) => text.slice(0, edit.start) + edit.value + text.slice(edit.end),
2168
+ (text2, edit) => text2.slice(0, edit.start) + edit.value + text2.slice(edit.end),
2169
2169
  source
2170
2170
  );
2171
2171
  }
@@ -2454,14 +2454,14 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2454
2454
  }
2455
2455
  return;
2456
2456
  }
2457
- const text = content.toString("utf8");
2458
- const syntax = ts2.transpileModule(text, {
2457
+ const text2 = content.toString("utf8");
2458
+ const syntax = ts2.transpileModule(text2, {
2459
2459
  fileName: path,
2460
2460
  reportDiagnostics: true,
2461
2461
  compilerOptions: { jsx: ts2.JsxEmit.Preserve, target: ts2.ScriptTarget.ESNext, module: ts2.ModuleKind.ESNext }
2462
2462
  });
2463
2463
  if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
2464
- const ast = sourceFile(text, path);
2464
+ const ast = sourceFile(text2, path);
2465
2465
  if (ast.statements.some((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression) && statement.expression.text === "use server")) throw new Error("server-only component modules require manual migration");
2466
2466
  const edits = [];
2467
2467
  function dependency(literal, importDeclaration) {
@@ -2509,7 +2509,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2509
2509
  ts2.forEachChild(node, inspect);
2510
2510
  }
2511
2511
  inspect(ast);
2512
- const replaced = applyReplacements(text, edits);
2512
+ const replaced = applyReplacements(text2, edits);
2513
2513
  const shebangMatch = /^#!.*\r?\n/.exec(replaced);
2514
2514
  const prologue = "// @ts-nocheck\n'use client';\n\n";
2515
2515
  staged.set(destination, {
@@ -3016,10 +3016,10 @@ ${inlineSource}` });
3016
3016
  const start = node.position.start.offset;
3017
3017
  const end = node.position.end.offset;
3018
3018
  if (extracted.some((item) => start >= item.node.position.start.offset && end <= item.node.position.end.offset)) return;
3019
- const text = content.slice(start, end);
3020
- const opening = text.indexOf(`<${node.name}`);
3019
+ const text2 = content.slice(start, end);
3020
+ const opening = text2.indexOf(`<${node.name}`);
3021
3021
  if (opening >= 0) edits.push({ start: start + opening + 1, end: start + opening + 1 + node.name.length, value: replacement });
3022
- const closing = text.lastIndexOf(`</${node.name}`);
3022
+ const closing = text2.lastIndexOf(`</${node.name}`);
3023
3023
  if (closing >= 0) edits.push({ start: start + closing + 2, end: start + closing + 2 + node.name.length, value: replacement });
3024
3024
  });
3025
3025
  walk(tree, (node) => {
@@ -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;
@@ -4981,11 +5141,11 @@ function announcementMarkdown(message) {
4981
5141
  links.set(marker, destination && label ? `[${escapeText(label)}](${destination})` : escapeText(label));
4982
5142
  document(element).replaceWith(marker);
4983
5143
  });
4984
- let text = escapeText(document("body").text());
4985
- for (const [marker, link] of links) text = text.replaceAll(marker, link);
4986
- return text.trim();
5144
+ let text2 = escapeText(document("body").text());
5145
+ for (const [marker, link] of links) text2 = text2.replaceAll(marker, link);
5146
+ return text2.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();
@@ -5501,9 +5661,185 @@ function projectFernNavigation(input) {
5501
5661
  };
5502
5662
  }
5503
5663
 
5664
+ // src/analytics.ts
5665
+ var GA4_ID = /^G-[A-Z0-9]{4,20}$/;
5666
+ var GTM_ID = /^GTM-[A-Z0-9]{4,12}$/;
5667
+ var POSTHOG_KEY = /^phc_[A-Za-z0-9]{20,64}$/;
5668
+ var HOSTNAME_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
5669
+ var HOSTNAME = new RegExp(`^${HOSTNAME_LABEL}(?:\\.${HOSTNAME_LABEL})*$`);
5670
+ var SAFE_PATH = /^\/[A-Za-z0-9._~\-/]*$/;
5671
+ var MAX_PLAUSIBLE_DOMAINS = 10;
5672
+ var SUPPORTED = ["ga4", "gtm", "posthog", "plausible"];
5673
+ var LEGACY_GA4_ALIAS = "googleAnalytics";
5674
+ function own(record, key) {
5675
+ return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
5676
+ }
5677
+ function isRecord(value) {
5678
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5679
+ }
5680
+ function redact(value) {
5681
+ if (typeof value !== "string") return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
5682
+ return value.length === 0 ? "empty string" : `"${value.trim().slice(0, 4).replace(/[^\x20-\x7e]/g, "?")}..." (${value.length} chars)`;
5683
+ }
5684
+ function isPublicHostname(hostname) {
5685
+ return !/(^|\.)localhost$/.test(hostname) && !/(^|\.)\d+$/.test(hostname) && !hostname.includes(":") && !hostname.startsWith("[");
5686
+ }
5687
+ function normalizeHttpsUrl(value, allowPath) {
5688
+ if (!/^https:\/\//i.test(value) || /[?#\\]/.test(value)) return null;
5689
+ let url;
5690
+ try {
5691
+ url = new URL(value);
5692
+ } catch {
5693
+ return null;
5694
+ }
5695
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) return null;
5696
+ if (!HOSTNAME.test(url.hostname) || !isPublicHostname(url.hostname) || url.port === "0") return null;
5697
+ const fullPath = /^https:\/\/[^/]*(\/.*)?$/i.exec(value)?.[1] ?? "";
5698
+ if (fullPath.includes("//")) return null;
5699
+ const path = fullPath.replace(/\/+$/, "");
5700
+ if (path && (!allowPath || !SAFE_PATH.test(path) || path.split("/").some((s) => s === ".." || s === "."))) return null;
5701
+ return `${url.origin}${path}`;
5702
+ }
5703
+ function normalizeDomains(value) {
5704
+ const parts = value.split(",").map((part) => part.trim().toLowerCase());
5705
+ if (parts.length > MAX_PLAUSIBLE_DOMAINS) return null;
5706
+ return parts.every((part) => part.length <= 253 && HOSTNAME.test(part)) ? parts.join(",") : null;
5707
+ }
5708
+ function normalizeServer(value) {
5709
+ if (/^https:\/\//i.test(value)) {
5710
+ const origin2 = normalizeHttpsUrl(value, false);
5711
+ return origin2 ? origin2.replace(/^https:\/\//, "") : null;
5712
+ }
5713
+ const bare = /^([a-z0-9.-]+)(?::(\d{1,5}))?$/i.exec(value);
5714
+ if (!bare || bare[2] !== void 0 && (Number(bare[2]) < 1 || Number(bare[2]) > 65535)) return null;
5715
+ const origin = normalizeHttpsUrl(`https://${bare[1].toLowerCase()}${bare[2] === void 0 ? "" : `:${Number(bare[2])}`}`, false);
5716
+ return origin ? origin.replace(/^https:\/\//, "") : null;
5717
+ }
5718
+ function text(value) {
5719
+ return typeof value === "string" && value.trim() && value.length <= 2048 ? value.trim() : null;
5720
+ }
5721
+ function projectMintlifyIntegrations(config) {
5722
+ if (!isRecord(config)) return { warnings: [] };
5723
+ try {
5724
+ return project(config);
5725
+ } catch {
5726
+ return { warnings: [{ code: "unsupported-config", message: "Mintlify analytics configuration could not be read; analytics were not imported." }] };
5727
+ }
5728
+ }
5729
+ function project(config) {
5730
+ const warnings = [];
5731
+ const warn = (message) => warnings.push({ code: "unsupported-config", message });
5732
+ const sources = [];
5733
+ for (const [label, key] of [["integrations", "integrations"], ["analytics", "analytics"]]) {
5734
+ const value = own(config, key);
5735
+ if (value === void 0) continue;
5736
+ if (isRecord(value)) sources.push({ label, block: value });
5737
+ else warn(`Mintlify ${label} must be an object (found ${redact(value)}); analytics from it were not imported.`);
5738
+ }
5739
+ const integrations = {};
5740
+ const pick = (name) => {
5741
+ const found = [];
5742
+ for (const source of sources) {
5743
+ let value = own(source.block, name);
5744
+ if (value === void 0 && name === "ga4" && source.label === "analytics") value = own(source.block, LEGACY_GA4_ALIAS);
5745
+ if (value !== void 0) found.push({ label: `${source.label}.${name}`, value });
5746
+ }
5747
+ if (found.length > 1) {
5748
+ warn(`Mintlify ${found[0].label} and ${found[1].label} are both set; using ${found[0].label} for ${name}.`);
5749
+ }
5750
+ return found;
5751
+ };
5752
+ const field = (label, block, key, required) => {
5753
+ const raw = own(block, key);
5754
+ if (raw === void 0) {
5755
+ if (required) warn(`Mintlify ${label}.${key} is missing; ${label} was not imported.`);
5756
+ return required ? null : void 0;
5757
+ }
5758
+ const value = text(raw);
5759
+ if (value === null) warn(`Mintlify ${label}.${key} is not a usable string (found ${redact(raw)}); ${label} was not imported.`);
5760
+ return value;
5761
+ };
5762
+ const objectFor = (entry) => {
5763
+ if (isRecord(entry.value)) return entry.value;
5764
+ warn(`Mintlify ${entry.label} must be an object (found ${redact(entry.value)}); provider was not imported.`);
5765
+ return null;
5766
+ };
5767
+ const ga4 = pick("ga4")[0];
5768
+ const ga4Block = ga4 ? objectFor(ga4) : null;
5769
+ if (ga4 && ga4Block) {
5770
+ const id = field(ga4.label, ga4Block, "measurementId", true);
5771
+ if (id && GA4_ID.test(id)) integrations.ga4 = { measurementId: id };
5772
+ else if (id) warn(`Mintlify ${ga4.label}.measurementId ${redact(id)} is not a GA4 ID (G-XXXXXXXXXX); Universal Analytics and lowercase IDs are not imported.`);
5773
+ }
5774
+ const gtm = pick("gtm")[0];
5775
+ const gtmBlock = gtm ? objectFor(gtm) : null;
5776
+ if (gtm && gtmBlock) {
5777
+ const id = field(gtm.label, gtmBlock, "tagId", true);
5778
+ if (id && GTM_ID.test(id)) integrations.gtm = { tagId: id };
5779
+ else if (id) warn(`Mintlify ${gtm.label}.tagId ${redact(id)} is not a GTM container ID (GTM-XXXXXXX); GTM was not imported.`);
5780
+ }
5781
+ const plausible = pick("plausible")[0];
5782
+ const plausibleBlock = plausible ? objectFor(plausible) : null;
5783
+ if (plausible && plausibleBlock) {
5784
+ const domainRaw = field(plausible.label, plausibleBlock, "domain", true);
5785
+ const serverRaw = field(plausible.label, plausibleBlock, "server", false);
5786
+ const domain = domainRaw ? normalizeDomains(domainRaw) : null;
5787
+ const server = serverRaw ? normalizeServer(serverRaw) : serverRaw;
5788
+ if (domainRaw && !domain) warn(`Mintlify ${plausible.label}.domain ${redact(domainRaw)} must be hostname(s) without a protocol or path; Plausible was not imported.`);
5789
+ if (serverRaw && !server) warn(`Mintlify ${plausible.label}.server ${redact(serverRaw)} must be a hostname; Plausible was not imported.`);
5790
+ if (domain && server !== null) integrations.plausible = { domain, ...server ? { server } : {} };
5791
+ }
5792
+ const posthog = pick("posthog")[0];
5793
+ const posthogBlock = posthog ? objectFor(posthog) : null;
5794
+ if (posthog && posthogBlock) {
5795
+ const key = field(posthog.label, posthogBlock, "apiKey", true);
5796
+ const hostRaw = field(posthog.label, posthogBlock, "apiHost", false);
5797
+ const host = hostRaw ? normalizeHttpsUrl(hostRaw, true) : hostRaw;
5798
+ const recording = own(posthogBlock, "sessionRecording");
5799
+ if (recording !== void 0 && typeof recording !== "boolean") warn(`Mintlify ${posthog.label}.sessionRecording must be a boolean (found ${redact(recording)}); session recording disabled.`);
5800
+ if (key && !POSTHOG_KEY.test(key)) warn(`Mintlify ${posthog.label}.apiKey ${redact(key)} is not a PostHog project key (phc_...); PostHog was not imported.`);
5801
+ if (hostRaw && !host) warn(`Mintlify ${posthog.label}.apiHost ${redact(hostRaw)} must be an https URL without credentials, query, or fragment; PostHog was not imported.`);
5802
+ if (key && POSTHOG_KEY.test(key) && host !== null) {
5803
+ integrations.posthog = {
5804
+ apiKey: key,
5805
+ ...host ? { apiHost: host } : {},
5806
+ // Fail closed: a present non-boolean value turns recording off.
5807
+ ...recording !== void 0 ? { sessionRecording: recording === true } : {}
5808
+ };
5809
+ }
5810
+ }
5811
+ const unsupported = /* @__PURE__ */ new Set();
5812
+ for (const source of sources) {
5813
+ for (const key of Object.keys(source.block)) {
5814
+ if (SUPPORTED.includes(key)) continue;
5815
+ if (key === LEGACY_GA4_ALIAS && source.label === "analytics") continue;
5816
+ unsupported.add(/^[A-Za-z0-9_-]{1,40}$/.test(key) ? key : "(invalid name)");
5817
+ }
5818
+ }
5819
+ if (unsupported.size > 0) {
5820
+ const names = [...unsupported].slice(0, 30);
5821
+ warn(`Mintlify analytics/integration providers Thally cannot render were not imported: ${names.join(", ")}${unsupported.size > names.length ? ", ..." : ""}. Supported: ga4, gtm, posthog, plausible.`);
5822
+ }
5823
+ return { ...Object.keys(integrations).length > 0 ? { integrations } : {}, warnings };
5824
+ }
5825
+ function validateIntegrations(integrations) {
5826
+ if (integrations === void 0) return [];
5827
+ const result = projectMintlifyIntegrations({ integrations });
5828
+ const messages = result.warnings.map(
5829
+ (warning) => warning.message.replace(/^Mintlify analytics\/integration providers Thally cannot render were not imported: /, "docs.json integrations: Thally does not support these providers, so they will not be rendered: ").replace(/^Mintlify /, "docs.json ").replace(/\b(?:was|were|are) not imported\b/g, "will not be rendered")
5830
+ );
5831
+ if (result.integrations?.ga4 && result.integrations.gtm) {
5832
+ messages.push("docs.json integrations enable both ga4 and gtm; a GA4 tag inside the GTM container would double-count page views.");
5833
+ }
5834
+ return messages;
5835
+ }
5836
+
5504
5837
  // src/repository.ts
5505
5838
  var MAX_SOURCE_FILES = 5e3;
5506
5839
  var MAX_PAGE_BYTES = 2e6;
5840
+ var FRONTMATTER_HEAD_BYTES = 65536;
5841
+ var MAX_WITHHELD_SCAN_BYTES = MAX_PAGE_BYTES * 8;
5842
+ var MAX_DROPPED_GATED_WARNINGS = 20;
5507
5843
  var MAX_ASSET_BYTES = 25e6;
5508
5844
  var MAX_TOTAL_ASSET_BYTES = 5e8;
5509
5845
  var GIT_LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
@@ -5529,6 +5865,7 @@ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
5529
5865
  function isIgnoredContentDirectory(name) {
5530
5866
  return name.startsWith(".") || name === "node_modules";
5531
5867
  }
5868
+ var QUARANTINE_DIRECTORY = "migration-quarantine";
5532
5869
  var ASSET_DIRECTORIES = /* @__PURE__ */ new Set(["assets", "images", "img", "media", "public", "static"]);
5533
5870
  var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
5534
5871
  ".avif",
@@ -6144,7 +6481,7 @@ function containsMarkdown(directory, depth = 0) {
6144
6481
  for (const entry of readdirSync2(directory, { withFileTypes: true })) {
6145
6482
  if (isIgnoredDirectory(entry.name) || entry.isSymbolicLink()) continue;
6146
6483
  const path = resolveWithin(directory, entry.name);
6147
- if (entry.isFile() && [".md", ".mdx"].includes(extname5(entry.name).toLowerCase())) return true;
6484
+ if (entry.isFile() && [".md", ".mdx"].includes(extname6(entry.name).toLowerCase())) return true;
6148
6485
  if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
6149
6486
  }
6150
6487
  return false;
@@ -6156,10 +6493,10 @@ function detectRepositoryDocsDir(repositoryDir) {
6156
6493
  }
6157
6494
  return "";
6158
6495
  }
6159
- var MAX_RANKED_WALK_FILES = MAX_SOURCE_FILES * 20;
6160
- function scanFiles(root, confinementRoot = root, warnings, rank) {
6496
+ var RANKED_WALK_FACTOR = 20;
6497
+ function scanFiles(root, confinementRoot = root, warnings, rank, budget = MAX_SOURCE_FILES) {
6161
6498
  const files = [];
6162
- const walkCap = rank ? MAX_RANKED_WALK_FILES : MAX_SOURCE_FILES;
6499
+ const walkCap = rank ? budget * RANKED_WALK_FACTOR : budget;
6163
6500
  let confinementReal;
6164
6501
  try {
6165
6502
  confinementReal = realpathSync2(confinementRoot);
@@ -6229,10 +6566,10 @@ function scanFiles(root, confinementRoot = root, warnings, rank) {
6229
6566
  }
6230
6567
  return files;
6231
6568
  }
6232
- function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label) {
6233
- if (scanned.length <= MAX_SOURCE_FILES) return scanned;
6569
+ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label, budget = MAX_SOURCE_FILES) {
6570
+ if (scanned.length <= budget) return scanned;
6234
6571
  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);
6572
+ const dropped = ranked.slice(budget).map(({ file }) => file);
6236
6573
  if (warnings) {
6237
6574
  const droppedPages = label === "file" ? dropped.filter((file) => isDocumentationExtension(file.relativePath)) : dropped;
6238
6575
  const noun = label === "file" ? "page" : "image or media file";
@@ -6245,18 +6582,18 @@ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, la
6245
6582
  const rest = droppedPages.length - examples.length;
6246
6583
  warnings.push({
6247
6584
  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.")
6585
+ 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
6586
  });
6250
6587
  }
6251
- return ranked.slice(0, MAX_SOURCE_FILES).map(({ file }) => file);
6588
+ return ranked.slice(0, budget).map(({ file }) => file);
6252
6589
  }
6253
- function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes) {
6254
- const isAsset = (file) => ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase());
6590
+ function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes, budget = MAX_SOURCE_FILES) {
6591
+ const isAsset = (file) => ASSET_EXTENSIONS.has(extname6(file.relativePath).toLowerCase());
6255
6592
  const assetFiles = scanned.filter(isAsset);
6256
6593
  const otherFiles = scanned.filter((file) => !isAsset(file));
6257
6594
  return [
6258
- ...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file"),
6259
- ...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset")
6595
+ ...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file", budget),
6596
+ ...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset", budget)
6260
6597
  ];
6261
6598
  }
6262
6599
  function readMintignoreMatcher(mintlifyRoot) {
@@ -6301,6 +6638,45 @@ function normalizedReferenceKey(value) {
6301
6638
  function exactReferenceKey(value) {
6302
6639
  return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
6303
6640
  }
6641
+ function specRefMatches(ref, specPath, specFilename, isDefault) {
6642
+ const prefix = /^(?:(\S+)\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|WEBHOOK)\s/i.exec(`${ref} `)?.[1];
6643
+ if (!prefix) return isDefault;
6644
+ const wanted = prefix.replace(/^\/+/, "").toLowerCase();
6645
+ const lowerPath = specPath.toLowerCase();
6646
+ return lowerPath === wanted || lowerPath.endsWith(`/${wanted}`) || specFilename.toLowerCase() === wanted;
6647
+ }
6648
+ var TOKEN_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\/`*!?#&]+/;
6649
+ var PATH_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\`*!?#&]+/;
6650
+ function publicAssetKey(repoRelative) {
6651
+ return repoRelative.split("/", 1)[0].toLowerCase() === "public" ? repoRelative.slice("public/".length) : repoRelative;
6652
+ }
6653
+ function addPathReferences(text2, baseDir, exact, loose) {
6654
+ for (const word of text2.split(PATH_SEPARATORS)) {
6655
+ let token = word.replace(/\.+$/, "");
6656
+ if (!/\.[a-z0-9]{2,5}$/i.test(token) || token.startsWith("//")) continue;
6657
+ if (token.includes("%")) {
6658
+ try {
6659
+ token = decodeURIComponent(token);
6660
+ } catch {
6661
+ }
6662
+ }
6663
+ if (!token.includes("/")) {
6664
+ loose.add(token.toLowerCase());
6665
+ continue;
6666
+ }
6667
+ if (!token.startsWith("/") && baseDir === void 0) continue;
6668
+ const resolved = normalizeAssetPath(posix3.normalize(token.startsWith("/") ? token.slice(1) : posix3.join(baseDir ?? "", token)));
6669
+ if (!resolved) continue;
6670
+ const key = publicAssetKey(resolved);
6671
+ exact.add(key);
6672
+ loose.add(key.toLowerCase());
6673
+ }
6674
+ }
6675
+ function looselyNamed(assetPath, loose) {
6676
+ const lower = assetPath.toLowerCase();
6677
+ const name = lower.split("/").pop() ?? lower;
6678
+ return loose.has(lower) || loose.has(name) || loose.has(name.split(TOKEN_SEPARATORS).filter(Boolean).pop() ?? name);
6679
+ }
6304
6680
  function findOpenApi(files) {
6305
6681
  return files.find((file) => OPENAPI_FILENAMES.has(basename3(file.relativePath).toLowerCase()) && classifyApiSpec(file.absolutePath) === "openapi") ?? null;
6306
6682
  }
@@ -6317,8 +6693,8 @@ function classifyApiSpec(path) {
6317
6693
  } catch {
6318
6694
  return "unknown";
6319
6695
  }
6320
- const text = buffer.toString("utf8", 0, bytesRead);
6321
- const topLevel = text.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
6696
+ const text2 = buffer.toString("utf8", 0, bytesRead);
6697
+ const topLevel = text2.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text2.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
6322
6698
  if (!topLevel) return "unknown";
6323
6699
  return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
6324
6700
  }
@@ -6635,6 +7011,31 @@ function fernDefinitionExists(fernRoot, apiName) {
6635
7011
  }
6636
7012
  return candidateDirs.some((dir) => existsSync5(dir) && lstatSync5(dir).isDirectory());
6637
7013
  }
7014
+ function readFrontmatterHead(path) {
7015
+ const fd = openSync(path, "r");
7016
+ try {
7017
+ const buffer = Buffer.alloc(FRONTMATTER_HEAD_BYTES);
7018
+ const head = buffer.toString("utf8", 0, readSync(fd, buffer, 0, buffer.length, 0));
7019
+ const opening = /^\uFEFF?---([^\r\n]*)\r?\n/.exec(head);
7020
+ if (!opening || opening[1].startsWith("-")) return head;
7021
+ return /^---[ \t]*\r?\n/m.test(head.slice(opening[0].length)) ? head : void 0;
7022
+ } finally {
7023
+ closeSync(fd);
7024
+ }
7025
+ }
7026
+ function canonicalPath(path) {
7027
+ try {
7028
+ return realpathSync2.native(path);
7029
+ } catch {
7030
+ return path;
7031
+ }
7032
+ }
7033
+ function pageHeadGateReason(raw) {
7034
+ const parsed = parseFrontmatter(raw);
7035
+ 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;
7036
+ const openapi = typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0;
7037
+ return { reason: frontmatterGateReason(parsed.data) ?? unreadable, publicTrue: isPublicTrue(parsed.data.public), openapi };
7038
+ }
6638
7039
  function withoutFrontmatter(value) {
6639
7040
  return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
6640
7041
  }
@@ -6769,7 +7170,7 @@ function fernSourceSiteUrl(config) {
6769
7170
  function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized) {
6770
7171
  const rewrite = (target) => {
6771
7172
  const path = target.split(/[?#]/, 1)[0].replace(/^\//, "").replace(/\/$/, "");
6772
- if (!path || pageIds.has(path) || ASSET_EXTENSIONS.has(extname5(path).toLowerCase())) return target;
7173
+ if (!path || pageIds.has(path) || ASSET_EXTENSIONS.has(extname6(path).toLowerCase())) return target;
6773
7174
  try {
6774
7175
  const base = new URL(`${sourceSiteUrl}/`);
6775
7176
  const sourceLinkUrl = new URL(target.slice(1), base);
@@ -6781,12 +7182,12 @@ function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized)
6781
7182
  return target;
6782
7183
  }
6783
7184
  };
6784
- return replaceOutsideCode(body, (text) => text.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
7185
+ return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
6785
7186
  }
6786
7187
  function stripFernBasePathFromLinks(body, basePath) {
6787
7188
  if (!basePath) return body;
6788
7189
  const escaped = basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6789
- return replaceOutsideCode(body, (text) => text.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
7190
+ return replaceOutsideCode(body, (text2) => text2.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
6790
7191
  }
6791
7192
  function rewriteMintlifyMountedLinks(body, pageIds) {
6792
7193
  const rewrite = (target) => {
@@ -6803,7 +7204,7 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
6803
7204
  }
6804
7205
  return target;
6805
7206
  };
6806
- return replaceOutsideCode(body, (text) => text.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
7207
+ return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
6807
7208
  }
6808
7209
  function preserveLinkedAnchors(pages) {
6809
7210
  const pageById = new Map(pages.map((page) => [page.id, page]));
@@ -6827,10 +7228,10 @@ function preserveLinkedAnchors(pages) {
6827
7228
  if (id !== currentId) crossPageRequests.add(`${id}#${fragment}`);
6828
7229
  };
6829
7230
  for (const page of pages) {
6830
- replaceOutsideCode(page.body, (text) => {
6831
- for (const match of text.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
6832
- for (const match of text.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
6833
- return text;
7231
+ replaceOutsideCode(page.body, (text2) => {
7232
+ for (const match of text2.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
7233
+ for (const match of text2.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
7234
+ return text2;
6834
7235
  });
6835
7236
  }
6836
7237
  const comparable = (value) => value.replace(/&(?:lt|gt|amp|quot);/g, " ").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
@@ -6854,9 +7255,9 @@ function preserveLinkedAnchors(pages) {
6854
7255
  return match ? { index, text: match[1] } : null;
6855
7256
  }).filter((entry) => entry !== null);
6856
7257
  const existing = /* @__PURE__ */ new Set();
6857
- replaceOutsideCode(page.body, (text) => {
6858
- for (const match of text.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
6859
- return text;
7258
+ replaceOutsideCode(page.body, (text2) => {
7259
+ for (const match of text2.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
7260
+ return text2;
6860
7261
  });
6861
7262
  const candidateIndexes = /* @__PURE__ */ new Map();
6862
7263
  for (const heading of headings) {
@@ -6963,8 +7364,13 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
6963
7364
  if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
6964
7365
  }
6965
7366
  for (const file of files) {
6966
- if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) continue;
6967
- const raw = readFileSync5(file.absolutePath, "utf8");
7367
+ if (![".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase())) continue;
7368
+ let raw;
7369
+ try {
7370
+ raw = readFileSync5(file.absolutePath, "utf8");
7371
+ } catch {
7372
+ continue;
7373
+ }
6968
7374
  for (const match of raw.matchAll(SNIPPET_IMPORT_PATTERN)) {
6969
7375
  try {
6970
7376
  const componentName = match[1] ?? match[2];
@@ -6991,7 +7397,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
6991
7397
  } catch {
6992
7398
  return null;
6993
7399
  }
6994
- if (!ASSET_EXTENSIONS.has(extname5(decodedPath).toLowerCase())) return null;
7400
+ if (!ASSET_EXTENSIONS.has(extname6(decodedPath).toLowerCase())) return null;
6995
7401
  try {
6996
7402
  const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithinRoot(dirname4(currentFile), decodedPath, siteRoot);
6997
7403
  const realRoot = realpathSync2(siteRoot);
@@ -7003,7 +7409,9 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
7003
7409
  if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) return null;
7004
7410
  const normalized = normalizeAssetPath(siteRelative);
7005
7411
  if (!normalized) return null;
7006
- onReferenced?.(normalized);
7412
+ const onDiskRelative = relative5(realRoot, realpathSync2.native(candidate)).replace(/\\/g, "/");
7413
+ const onDisk = onDiskRelative.toLowerCase() === siteRelative.toLowerCase() ? normalizeAssetPath(onDiskRelative) : null;
7414
+ onReferenced?.(normalized, onDisk ?? void 0);
7007
7415
  const rewritten = `/${normalized}${suffix}`;
7008
7416
  return isBracketed ? `<${rewritten}>` : rewritten;
7009
7417
  } catch {
@@ -7011,7 +7419,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
7011
7419
  }
7012
7420
  }
7013
7421
  function rewriteFernIdRenameLinks(body, renames) {
7014
- return replaceOutsideCode(body, (text) => {
7422
+ return replaceOutsideCode(body, (text2) => {
7015
7423
  const rewriteTarget = (target) => {
7016
7424
  const suffixIndex = target.search(/[?#]/);
7017
7425
  const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "");
@@ -7019,7 +7427,7 @@ function rewriteFernIdRenameLinks(body, renames) {
7019
7427
  const renamed = renames.get(path);
7020
7428
  return renamed ? `/${renamed}${suffix}` : target;
7021
7429
  };
7022
- return text.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
7430
+ return text2.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
7023
7431
  });
7024
7432
  }
7025
7433
  function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced) {
@@ -7048,18 +7456,47 @@ function locallyDeclaredNames(raw) {
7048
7456
  }
7049
7457
  return names;
7050
7458
  }
7051
- function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map()) {
7459
+ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0, siteRoot = repositoryRoot, globalAliases = /* @__PURE__ */ new Map(), gate) {
7052
7460
  if (depth >= 8) return raw;
7053
7461
  raw = normalizeIndentedFences(raw);
7054
7462
  const snippets = /* @__PURE__ */ new Map();
7055
7463
  const preservedDeclarations = /* @__PURE__ */ new Map();
7056
- let withoutImports = replaceOutsideCode(raw, (source) => source.replace(
7464
+ const source = relative5(repositoryRoot, currentFile).replace(/\\/g, "/");
7465
+ const blockInline = (candidate, shownPath, label = shownPath) => {
7466
+ const verdict = gate?.(candidate);
7467
+ if (!verdict) return void 0;
7468
+ const shown = shownPath.replace(/\*\//g, "* /");
7469
+ if (verdict.kind === "gated") {
7470
+ warnings.push({ code: "gated-page", message: `${label} is access-restricted and was NOT inlined; it was left as a comment.`, source });
7471
+ return `{/* Access-restricted content withheld: ${shown} */}`;
7472
+ }
7473
+ if (verdict.kind === "snippet-gated") {
7474
+ warnings.push({
7475
+ code: "gated-page",
7476
+ 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.`,
7477
+ source
7478
+ });
7479
+ return `{/* Access-restricted content withheld: ${shown} */}`;
7480
+ }
7481
+ warnings.push({
7482
+ code: "skipped-file",
7483
+ 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.`,
7484
+ source
7485
+ });
7486
+ return `{/* ${verdict.kind === "oversized" ? "Oversized" : "Unreadable"} content not inlined: ${shown} */}`;
7487
+ };
7488
+ let withoutImports = replaceOutsideCode(raw, (source2) => source2.replace(
7057
7489
  SNIPPET_IMPORT_PATTERN,
7058
7490
  (_statement, namedComponent, defaultComponent, sourcePath) => {
7059
7491
  const componentName = namedComponent ?? defaultComponent;
7060
7492
  try {
7061
7493
  const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
7062
7494
  if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) throw new Error("file not found");
7495
+ const blocked = blockInline(candidate, sourcePath, `Imported ${sourcePath}`);
7496
+ if (blocked) {
7497
+ snippets.set(componentName, blocked);
7498
+ return "";
7499
+ }
7063
7500
  const nested = inlineMdxSnippets(
7064
7501
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7065
7502
  candidate,
@@ -7067,7 +7504,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7067
7504
  warnings,
7068
7505
  depth + 1,
7069
7506
  siteRoot,
7070
- globalAliases
7507
+ globalAliases,
7508
+ gate
7071
7509
  );
7072
7510
  const declaration = statefulSnippetDeclaration(nested, componentName);
7073
7511
  if (declaration) preservedDeclarations.set(componentName, declaration);
@@ -7084,7 +7522,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7084
7522
  }
7085
7523
  }
7086
7524
  ));
7087
- withoutImports = replaceOutsideCode(withoutImports, (source) => source.replace(
7525
+ withoutImports = replaceOutsideCode(withoutImports, (source2) => source2.replace(
7088
7526
  SNIPPET_VALUE_IMPORT_PATTERN,
7089
7527
  (statement, names, sourcePath) => {
7090
7528
  const bindings = names.split(",").map((name) => name.trim()).filter(Boolean).map((name) => {
@@ -7094,7 +7532,16 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7094
7532
  if (bindings.length === 0 || bindings.some((binding) => !binding)) return statement;
7095
7533
  try {
7096
7534
  const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
7097
- if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) return statement;
7535
+ if (!existsSync5(candidate) || !lstatSync5(candidate).isFile() || gate?.(candidate)?.kind === "gated") return statement;
7536
+ const blocked = blockInline(candidate, sourcePath);
7537
+ if (blocked) {
7538
+ const declarations2 = [];
7539
+ for (const binding of bindings) {
7540
+ if (/^[A-Z]/.test(binding.exported)) snippets.set(binding.local, blocked);
7541
+ else declarations2.push(`export const ${binding.local} = undefined;`);
7542
+ }
7543
+ return declarations2.join("\n");
7544
+ }
7098
7545
  const snippetSource = readFileSync5(candidate, "utf8");
7099
7546
  const values = staticNamedSnippetValues(snippetSource);
7100
7547
  const declarations = [];
@@ -7119,7 +7566,12 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7119
7566
  ));
7120
7567
  const localNames = locallyDeclaredNames(withoutImports);
7121
7568
  for (const [componentName, candidate] of globalAliases) {
7122
- if (snippets.has(componentName) || localNames.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
7569
+ if (snippets.has(componentName) || localNames.has(componentName) || gate?.(candidate)?.kind === "gated" || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
7570
+ const blocked = blockInline(candidate, relative5(repositoryRoot, candidate).replace(/\\/g, "/"));
7571
+ if (blocked) {
7572
+ snippets.set(componentName, blocked);
7573
+ continue;
7574
+ }
7123
7575
  const nested = inlineMdxSnippets(
7124
7576
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7125
7577
  candidate,
@@ -7127,7 +7579,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7127
7579
  warnings,
7128
7580
  depth + 1,
7129
7581
  siteRoot,
7130
- globalAliases
7582
+ globalAliases,
7583
+ gate
7131
7584
  );
7132
7585
  const declaration = statefulSnippetDeclaration(nested, componentName);
7133
7586
  if (declaration) preservedDeclarations.set(componentName, declaration);
@@ -7155,6 +7608,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7155
7608
  }
7156
7609
  }).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
7157
7610
  if (!candidate) throw new Error("file not found");
7611
+ const blocked = blockInline(candidate, filePath, `Snippet file="${filePath}"`);
7612
+ if (blocked) return blocked;
7158
7613
  return inlineMdxSnippets(
7159
7614
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
7160
7615
  candidate,
@@ -7162,7 +7617,8 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
7162
7617
  warnings,
7163
7618
  depth + 1,
7164
7619
  siteRoot,
7165
- globalAliases
7620
+ globalAliases,
7621
+ gate
7166
7622
  );
7167
7623
  } catch {
7168
7624
  warnings.push({
@@ -7254,6 +7710,7 @@ function migrateRepository(options) {
7254
7710
  const referenceOrder = /* @__PURE__ */ new Map();
7255
7711
  let docusaurusSidebars = null;
7256
7712
  let mintlifyConfig = null;
7713
+ const mintlifyGatedRefs = /* @__PURE__ */ new Map();
7257
7714
  let fernRawConfig = null;
7258
7715
  let fernBasePath = "";
7259
7716
  let fernVersionPath = "";
@@ -7274,6 +7731,10 @@ function migrateRepository(options) {
7274
7731
  const projected = projectMintlifyNavigation(config);
7275
7732
  docsConfig = projected.docsConfig;
7276
7733
  warnings.push(...projected.warnings);
7734
+ for (const gated of projected.gatedReferences) {
7735
+ const gatedKey = normalizedReferenceKey(gated.ref).toLowerCase();
7736
+ if (!mintlifyGatedRefs.has(gatedKey)) mintlifyGatedRefs.set(gatedKey, gated.reason);
7737
+ }
7277
7738
  for (const [index, reference] of projected.pageReferences.entries()) {
7278
7739
  const key = normalizedReferenceKey(reference.ref);
7279
7740
  if (!referenceMap.has(key)) referenceMap.set(key, reference);
@@ -7359,10 +7820,11 @@ function migrateRepository(options) {
7359
7820
  const versionCeiling = lastReferencedIndexByVersionLocale.get(blockKey);
7360
7821
  return versionCeiling !== void 0 ? versionCeiling + 1 : Number.MAX_SAFE_INTEGER;
7361
7822
  } : void 0;
7362
- const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank);
7823
+ const sourceBudget = options.maxSourceFiles ?? MAX_SOURCE_FILES;
7824
+ const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank, sourceBudget);
7363
7825
  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;
7826
+ const discoveryBudgetApplied = discoveryRank !== void 0 && mintignoreFilteredFiles.length > sourceBudget;
7827
+ const files = discoveryRank ? selectFilesWithinBudget(mintignoreFilteredFiles, discoveryRank, warnings, allVersionPrefixes, sourceBudget) : mintignoreFilteredFiles;
7366
7828
  if (platform === "fern" && fernProjectRoot) {
7367
7829
  const discovered2 = new Set(files.map((file) => file.relativePath));
7368
7830
  for (const sourcePath of fernExternalSourcePaths) {
@@ -7392,6 +7854,108 @@ function migrateRepository(options) {
7392
7854
  const docusaurusDescriptors = [];
7393
7855
  const docusaurusDocCardRoutes = /* @__PURE__ */ new Set();
7394
7856
  const seenPageIds = /* @__PURE__ */ new Set();
7857
+ const quarantinedFiles = [];
7858
+ const withheldAssetPaths = /* @__PURE__ */ new Set();
7859
+ const publishedExact = /* @__PURE__ */ new Set();
7860
+ const publishedLoose = /* @__PURE__ */ new Set();
7861
+ const withholdAsset = (assetPath, onDiskSpelling) => {
7862
+ withheldAssetPaths.add(publicAssetKey(onDiskSpelling ?? assetPath));
7863
+ };
7864
+ let sawPublicTrue = false;
7865
+ const gateByPath = /* @__PURE__ */ new Map();
7866
+ const withheldSpecRefs = [];
7867
+ const publishedSpecRefs = [];
7868
+ const withheldPaths = /* @__PURE__ */ new Set();
7869
+ const isDocFile = (file) => [".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase());
7870
+ const isGateCandidate = (file) => isDocFile(file) && !file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
7871
+ const classifyPageGate = (file) => {
7872
+ let head = { publicTrue: false };
7873
+ let unreadableGate;
7874
+ try {
7875
+ const size = lstatSync5(file.absolutePath).size;
7876
+ const raw = readFrontmatterHead(file.absolutePath) ?? (size > MAX_PAGE_BYTES ? void 0 : readFileSync5(file.absolutePath, "utf8"));
7877
+ if (raw === void 0) throw new Error("frontmatter is not terminated within the bounded read");
7878
+ head = pageHeadGateReason(raw);
7879
+ } catch {
7880
+ unreadableGate = "frontmatter could not be read";
7881
+ }
7882
+ const reason = head.reason ?? unreadableGate ?? mintlifyGatedRefs.get(normalizedReferenceKey(file.relativePath).toLowerCase());
7883
+ return { reason, publicTrue: head.publicTrue, openapi: head.openapi };
7884
+ };
7885
+ const keptFiles = new Set(files);
7886
+ const droppedGatedFiles = [];
7887
+ const withheldDocFiles = [];
7888
+ let droppedPageCount = 0;
7889
+ const scanTruncated = scannedFiles.length >= (discoveryRank ? sourceBudget * RANKED_WALK_FACTOR : sourceBudget);
7890
+ if (platform === "mintlify") {
7891
+ for (const file of files) {
7892
+ if (isDocFile(file) && !isGateCandidate(file)) {
7893
+ const snippetReason = classifyPageGate(file).reason;
7894
+ if (snippetReason) withheldDocFiles.push(file);
7895
+ continue;
7896
+ }
7897
+ if (!isGateCandidate(file)) continue;
7898
+ const verdict = classifyPageGate(file);
7899
+ gateByPath.set(file.absolutePath, { reason: verdict.reason, publicTrue: verdict.publicTrue });
7900
+ if (verdict.openapi) (verdict.reason ? withheldSpecRefs : publishedSpecRefs).push(verdict.openapi);
7901
+ if (verdict.reason) withheldPaths.add(file.absolutePath);
7902
+ }
7903
+ for (const file of mintignoreFilteredFiles) {
7904
+ if (keptFiles.has(file) || !isDocFile(file)) continue;
7905
+ droppedPageCount++;
7906
+ if (!isGateCandidate(file)) {
7907
+ const snippetReason = classifyPageGate(file).reason;
7908
+ if (snippetReason) {
7909
+ withheldDocFiles.push(file);
7910
+ 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 });
7911
+ }
7912
+ continue;
7913
+ }
7914
+ const verdict = classifyPageGate(file);
7915
+ if (!verdict.reason) continue;
7916
+ if (verdict.openapi) withheldSpecRefs.push(verdict.openapi);
7917
+ withheldPaths.add(file.absolutePath);
7918
+ droppedGatedFiles.push({ file, reason: verdict.reason });
7919
+ }
7920
+ for (const file of scannedFiles) {
7921
+ if (!mintignoreMatcher?.ignores(file.relativePath) || !isDocFile(file)) continue;
7922
+ const verdict = classifyPageGate(file);
7923
+ if (!verdict.reason) continue;
7924
+ if (verdict.openapi) withheldSpecRefs.push(verdict.openapi);
7925
+ withheldDocFiles.push(file);
7926
+ }
7927
+ }
7928
+ const pagesNotClassified = droppedPageCount > 0 || platform === "mintlify" && scanTruncated;
7929
+ const hasWithheldContent = withheldPaths.size > 0 || withheldDocFiles.length > 0;
7930
+ const trackPublishedRefs = platform === "mintlify" && (hasWithheldContent || pagesNotClassified);
7931
+ const withheldCanonical = new Set([...withheldPaths].map(canonicalPath));
7932
+ const verdictCache = /* @__PURE__ */ new Map();
7933
+ const classifyInlineCandidate = (candidate, key) => {
7934
+ if (withheldCanonical.has(key)) return { kind: "gated", reason: "access-restricted" };
7935
+ const shown = relative5(contentRoot, candidate).replace(/\\/g, "/");
7936
+ const inSnippetDirectory = shown.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
7937
+ let head;
7938
+ try {
7939
+ head = readFrontmatterHead(candidate);
7940
+ } catch {
7941
+ return { kind: "unreadable", reason: "could not be read" };
7942
+ }
7943
+ const gated = head === void 0 ? void 0 : pageHeadGateReason(head).reason;
7944
+ if (gated) return { kind: inSnippetDirectory ? "snippet-gated" : "gated", reason: gated };
7945
+ const navigationGated = inSnippetDirectory ? void 0 : mintlifyGatedRefs.get(normalizedReferenceKey(shown).toLowerCase());
7946
+ if (navigationGated) return { kind: "gated", reason: navigationGated };
7947
+ if (lstatSync5(candidate).size > MAX_PAGE_BYTES) return { kind: "oversized", reason: "over 2 MB" };
7948
+ return head === void 0 ? { kind: "unreadable", reason: "frontmatter not terminated in the bounded read" } : void 0;
7949
+ };
7950
+ const inlineGate = platform === "mintlify" ? (candidate) => {
7951
+ const key = canonicalPath(candidate);
7952
+ let verdict = verdictCache.get(key);
7953
+ if (verdict === void 0) {
7954
+ verdict = classifyInlineCandidate(candidate, key) ?? null;
7955
+ verdictCache.set(key, verdict);
7956
+ }
7957
+ return verdict ?? void 0;
7958
+ } : void 0;
7395
7959
  const fernIdRenames = /* @__PURE__ */ new Map();
7396
7960
  let skipped = 0;
7397
7961
  let discovered = files.length;
@@ -7404,6 +7968,47 @@ function migrateRepository(options) {
7404
7968
  return leftOrder - rightOrder || left.relativePath.localeCompare(right.relativePath);
7405
7969
  });
7406
7970
  const snippetAliases = platform === "mintlify" && mintlifyProjectRoot ? globalSnippetAliases(files, repositoryDir, mintlifyProjectRoot) : /* @__PURE__ */ new Map();
7971
+ droppedGatedFiles.forEach(({ file, reason }, index) => {
7972
+ if (index < MAX_DROPPED_GATED_WARNINGS) {
7973
+ warnings.push({
7974
+ code: "gated-page",
7975
+ 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.`,
7976
+ source: file.relativePath
7977
+ });
7978
+ }
7979
+ if (!mintlifyProjectRoot) return;
7980
+ try {
7981
+ if (lstatSync5(file.absolutePath).size > MAX_WITHHELD_SCAN_BYTES) throw new Error("too large to scan");
7982
+ rewriteRepositoryAssetLinks(
7983
+ inlineMdxSnippets(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, [], 0, mintlifyProjectRoot, snippetAliases, inlineGate),
7984
+ file.absolutePath,
7985
+ mintlifyProjectRoot,
7986
+ withholdAsset
7987
+ );
7988
+ } catch {
7989
+ }
7990
+ });
7991
+ for (const file of withheldDocFiles) {
7992
+ try {
7993
+ if (!mintlifyProjectRoot) continue;
7994
+ if (lstatSync5(file.absolutePath).size > MAX_WITHHELD_SCAN_BYTES) {
7995
+ warnings.push({
7996
+ code: "gated-page",
7997
+ 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.",
7998
+ source: file.relativePath
7999
+ });
8000
+ continue;
8001
+ }
8002
+ rewriteRepositoryAssetLinks(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, mintlifyProjectRoot, withholdAsset);
8003
+ } catch {
8004
+ }
8005
+ }
8006
+ if (droppedGatedFiles.length > MAX_DROPPED_GATED_WARNINGS) {
8007
+ warnings.push({
8008
+ code: "gated-page",
8009
+ 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.`
8010
+ });
8011
+ }
7407
8012
  for (const file of pageFiles) {
7408
8013
  if (!isDocumentationExtension(file.relativePath)) continue;
7409
8014
  if (platform === "fern" && !exactReferenceMap.has(exactReferenceKey(file.relativePath)) && !referenceMap.has(normalizedReferenceKey(file.relativePath))) {
@@ -7420,7 +8025,7 @@ function migrateRepository(options) {
7420
8025
  skipped++;
7421
8026
  continue;
7422
8027
  }
7423
- if (![".md", ".mdx"].includes(extname5(file.relativePath).toLowerCase())) {
8028
+ if (![".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase())) {
7424
8029
  skipped++;
7425
8030
  warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
7426
8031
  continue;
@@ -7429,9 +8034,64 @@ function migrateRepository(options) {
7429
8034
  if (size > MAX_PAGE_BYTES) {
7430
8035
  skipped++;
7431
8036
  warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
8037
+ const oversizedGate = gateByPath.get(file.absolutePath)?.reason;
8038
+ if (oversizedGate) {
8039
+ warnings.push({
8040
+ code: "gated-page",
8041
+ 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.`,
8042
+ source: file.relativePath
8043
+ });
8044
+ }
8045
+ if (oversizedGate && mintlifyProjectRoot) {
8046
+ if (size > MAX_WITHHELD_SCAN_BYTES) {
8047
+ warnings.push({
8048
+ code: "gated-page",
8049
+ 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.",
8050
+ source: file.relativePath
8051
+ });
8052
+ } else {
8053
+ rewriteRepositoryAssetLinks(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, mintlifyProjectRoot, withholdAsset);
8054
+ }
8055
+ }
7432
8056
  continue;
7433
8057
  }
7434
8058
  const key = normalizedReferenceKey(file.relativePath);
8059
+ if (platform === "mintlify") {
8060
+ const gate = gateByPath.get(file.absolutePath);
8061
+ if (gate?.publicTrue) sawPublicTrue = true;
8062
+ const gateReason = gate?.reason;
8063
+ if (gateReason) {
8064
+ skipped++;
8065
+ if (file.relativePath.split("/").some((segment) => segment === "..")) {
8066
+ 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 });
8067
+ continue;
8068
+ }
8069
+ try {
8070
+ if (mintlifyProjectRoot) {
8071
+ rewriteRepositoryAssetLinks(
8072
+ inlineMdxSnippets(readFileSync5(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, [], 0, mintlifyProjectRoot, snippetAliases, inlineGate),
8073
+ file.absolutePath,
8074
+ mintlifyProjectRoot,
8075
+ withholdAsset
8076
+ );
8077
+ }
8078
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/${file.relativePath}`, content: readFileSync5(file.absolutePath) });
8079
+ } catch {
8080
+ warnings.push({
8081
+ code: "gated-page",
8082
+ 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.`,
8083
+ source: file.relativePath
8084
+ });
8085
+ continue;
8086
+ }
8087
+ warnings.push({
8088
+ code: "gated-page",
8089
+ 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.`,
8090
+ source: file.relativePath
8091
+ });
8092
+ continue;
8093
+ }
8094
+ }
7435
8095
  const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
7436
8096
  let locale = referenced?.locale;
7437
8097
  let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
@@ -7458,8 +8118,11 @@ function migrateRepository(options) {
7458
8118
  warnings,
7459
8119
  0,
7460
8120
  mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir,
7461
- snippetAliases
8121
+ snippetAliases,
8122
+ inlineGate
7462
8123
  );
8124
+ const publishedText = trackPublishedRefs ? raw : void 0;
8125
+ const pageAssetReferences = [];
7463
8126
  if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
7464
8127
  raw = normalizeExplicitHeadingIds(raw);
7465
8128
  }
@@ -7551,8 +8214,8 @@ function migrateRepository(options) {
7551
8214
  }
7552
8215
  if (platform === "fern" && fernHiddenIds.has(navigationId)) page.hidden = true;
7553
8216
  if (platform === "mintlify" && mintlifyProjectRoot) {
7554
- page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath) => {
7555
- addAssetReference(assetPath, file.relativePath);
8217
+ page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath, onDiskSpelling) => {
8218
+ pageAssetReferences.push(publicAssetKey(onDiskSpelling ?? assetPath));
7556
8219
  });
7557
8220
  }
7558
8221
  if (platform === "fern" && fernProjectRoot) {
@@ -7616,6 +8279,8 @@ function migrateRepository(options) {
7616
8279
  }
7617
8280
  seenPageIds.add(page.id);
7618
8281
  pages.push(page);
8282
+ for (const assetPath of pageAssetReferences) addAssetReference(assetPath, file.relativePath);
8283
+ if (publishedText !== void 0) addPathReferences(publishedText, posix3.dirname(file.relativePath).replace(/^\.$/, ""), publishedExact, publishedLoose);
7619
8284
  if (hasDocusaurusDocCardList) {
7620
8285
  docusaurusDocCardRoutes.add(page.navigationId);
7621
8286
  }
@@ -7658,7 +8323,7 @@ function migrateRepository(options) {
7658
8323
  const withinFern = relative5(fernProjectRoot, absolutePath);
7659
8324
  const assetRelative = withinFern !== ".." && !withinFern.startsWith(`..${sep2}`) && !isAbsolute2(withinFern) ? withinFern : relative5(repositoryDir, absolutePath);
7660
8325
  const assetPath = normalizeAssetPath(assetRelative.replace(/\\/g, "/"));
7661
- if (!assetPath || !ASSET_EXTENSIONS.has(extname5(assetPath).toLowerCase())) return;
8326
+ if (!assetPath || !ASSET_EXTENSIONS.has(extname6(assetPath).toLowerCase())) return;
7662
8327
  if (source) {
7663
8328
  fernBrandAssetPaths.set(source, assetPath);
7664
8329
  addAssetReference(assetPath, "fern/docs.yml");
@@ -7689,24 +8354,59 @@ function migrateRepository(options) {
7689
8354
  }));
7690
8355
  }) : [];
7691
8356
  const assetCandidates = [];
8357
+ const mintlifyFonts = platform === "mintlify" ? mintlifyFontSources(mintlifyConfig) : [];
8358
+ const fontRelativePaths = new Set(mintlifyFonts.flatMap((font) => font.path ? [font.path] : []));
8359
+ const importedComponentSources = new Set((componentMigrator?.files() ?? []).flatMap((file) => {
8360
+ const source = /^src\/mdx\/migrated\/[^/]+\/source\/(.+)$/.exec(file.path)?.[1];
8361
+ return source ? [resolvePath(repositoryDir, source)] : [];
8362
+ }));
8363
+ const siteAssetPaths = /* @__PURE__ */ new Map();
8364
+ const fontAssetByRelative = /* @__PURE__ */ new Map();
7692
8365
  for (const file of [...files, ...repositoryAssets, ...fernReferencedAssets]) {
7693
8366
  const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
7694
- if (!ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase())) continue;
8367
+ 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;
8368
+ if (!siteKind && !ASSET_EXTENSIONS.has(extname6(file.relativePath).toLowerCase())) continue;
7695
8369
  if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment) && !(platform === "fern" && referencedAssetPaths.has(file.relativePath))) continue;
7696
8370
  const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
7697
8371
  const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
7698
8372
  if (!assetPath) continue;
7699
- assetCandidates.push({ file, assetPath, size: lstatSync5(file.absolutePath).size });
8373
+ if (siteKind) {
8374
+ siteAssetPaths.set(assetPath, siteKind);
8375
+ if (siteKind === "font") fontAssetByRelative.set(file.relativePath, assetPath);
8376
+ }
8377
+ const candidateSize = lstatSync5(file.absolutePath).size;
8378
+ if (siteKind === "style" || siteKind === "script") {
8379
+ if (trackPublishedRefs && candidateSize <= MAX_PAGE_BYTES) addPathReferences(readFileSync5(file.absolutePath, "utf8"), posix3.dirname(file.relativePath).replace(/^\.$/, ""), publishedExact, publishedLoose);
8380
+ }
8381
+ assetCandidates.push({ file, assetPath, size: candidateSize });
7700
8382
  }
7701
8383
  const isReferenced = (candidate) => referencedAssetPaths.has(candidate.assetPath);
7702
8384
  const orderedAssetCandidates = [
7703
8385
  ...assetCandidates.filter((candidate) => isReferenced(candidate)),
7704
8386
  ...assetCandidates.filter((candidate) => !isReferenced(candidate))
7705
8387
  ];
8388
+ const quarantinedPageCount = quarantinedFiles.length;
8389
+ const destinationCounts = /* @__PURE__ */ new Map();
8390
+ for (const { assetPath } of assetCandidates) destinationCounts.set(assetPath, (destinationCounts.get(assetPath) ?? 0) + 1);
8391
+ if (trackPublishedRefs) {
8392
+ const publishedConfig = JSON.stringify(mintlifyConfig ?? {}, (_key, value) => value && typeof value === "object" && !Array.isArray(value) && navigationGateReason(value) ? void 0 : value);
8393
+ addPathReferences(publishedConfig, "", publishedExact, publishedLoose);
8394
+ }
8395
+ for (const file of componentMigrator?.files() ?? []) {
8396
+ if (!trackPublishedRefs || typeof file.content !== "string") continue;
8397
+ const original = /^src\/mdx\/migrated\/[^/]+\/source\/(.+)$/.exec(file.path)?.[1];
8398
+ addPathReferences(file.content, original === void 0 ? "" : posix3.dirname(original).replace(/^\.$/, ""), publishedExact, publishedLoose);
8399
+ }
8400
+ const failClosedAssets = trackPublishedRefs;
8401
+ const isPublicReachable = (assetPath) => referencedAssetPaths.has(assetPath) || publishedExact.has(assetPath);
8402
+ const isQuarantinedAsset = (assetPath) => (withheldAssetPaths.has(assetPath) || failClosedAssets && !siteAssetPaths.has(assetPath)) && platform === "mintlify" && !(isPublicReachable(assetPath) && !(failClosedAssets && (destinationCounts.get(assetPath) ?? 0) > 1));
8403
+ let withheldAssetCount = 0;
8404
+ let unreferencedAssetCount = 0;
7706
8405
  let totalAssetBytes = 0;
7707
8406
  const overBudgetAssets = [];
7708
8407
  let overBudgetReferenced = 0;
7709
8408
  const lfsPointerAssets = [];
8409
+ const ambiguouslyNamedAssets = [];
7710
8410
  for (const { file, assetPath, size } of orderedAssetCandidates) {
7711
8411
  if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
7712
8412
  overBudgetAssets.push(file.relativePath);
@@ -7718,9 +8418,53 @@ function migrateRepository(options) {
7718
8418
  lfsPointerAssets.push(file.relativePath);
7719
8419
  continue;
7720
8420
  }
8421
+ if (isQuarantinedAsset(assetPath)) {
8422
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${assetPath}`, content });
8423
+ if (looselyNamed(assetPath, publishedLoose) || (destinationCounts.get(assetPath) ?? 0) > 1) ambiguouslyNamedAssets.push(assetPath);
8424
+ if (withheldAssetPaths.has(assetPath)) withheldAssetCount++;
8425
+ else unreferencedAssetCount++;
8426
+ continue;
8427
+ }
7721
8428
  assets.push({ path: assetPath, content });
7722
8429
  totalAssetBytes += size;
7723
8430
  }
8431
+ if (ambiguouslyNamedAssets.length > 0) {
8432
+ warnings.push({
8433
+ code: "gated-page",
8434
+ 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.`
8435
+ });
8436
+ }
8437
+ if (platform === "mintlify") {
8438
+ const copiedSite = assets.flatMap((asset) => siteAssetPaths.has(asset.path) ? [{ path: asset.path, kind: siteAssetPaths.get(asset.path) }] : []);
8439
+ const scripts = copiedSite.filter((entry) => entry.kind === "script");
8440
+ if (scripts.length > 0) {
8441
+ docsConfig = {
8442
+ ...docsConfig,
8443
+ customScripts: [...docsConfig.customScripts ?? [], ...scripts.map((entry) => ({ src: `/${entry.path.split("/").map(encodeURIComponent).join("/")}`, strategy: "afterInteractive" }))]
8444
+ };
8445
+ warnings.push({
8446
+ code: "unsupported-config",
8447
+ 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.`
8448
+ });
8449
+ }
8450
+ for (const entry of copiedSite.filter((item) => item.kind === "style")) {
8451
+ warnings.push({
8452
+ code: "unsupported-config",
8453
+ 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.`,
8454
+ source: entry.path
8455
+ });
8456
+ }
8457
+ for (const font of mintlifyFonts) {
8458
+ const copiedPath = font.path ? fontAssetByRelative.get(font.path) : void 0;
8459
+ const copied = copiedPath !== void 0 && assets.some((asset) => asset.path === copiedPath);
8460
+ 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;
8461
+ if (detail === null) continue;
8462
+ warnings.push({
8463
+ code: "unsupported-config",
8464
+ 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.`
8465
+ });
8466
+ }
8467
+ }
7724
8468
  if (overBudgetAssets.length > 0) {
7725
8469
  warnings.push({
7726
8470
  code: "limit-reached",
@@ -7733,8 +8477,8 @@ function migrateRepository(options) {
7733
8477
  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
8478
  });
7735
8479
  }
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.` });
8480
+ if (!discoveryBudgetApplied && files.length >= sourceBudget) {
8481
+ 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
8482
  }
7739
8483
  if (platform === "docusaurus") {
7740
8484
  const projected = projectDocusaurusNavigation({
@@ -7861,11 +8605,19 @@ function migrateRepository(options) {
7861
8605
  rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
7862
8606
  }
7863
8607
  } else if (platform === "mintlify") {
8608
+ const warnSharedSpec = (path) => {
8609
+ warnings.push({
8610
+ code: "gated-page",
8611
+ message: `OpenAPI spec ${path} is shared with access-restricted pages and is published; it may describe restricted endpoints. Review it before publishing.`,
8612
+ source: path
8613
+ });
8614
+ };
7864
8615
  const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
7865
8616
  for (const spec of resolvedSpecs) {
7866
8617
  if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
7867
8618
  assets.push(specAsset(spec.filename, spec.content));
7868
8619
  }
8620
+ if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath);
7869
8621
  }
7870
8622
  if (resolvedSpecs.length > 0) {
7871
8623
  docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
@@ -7878,10 +8630,17 @@ function migrateRepository(options) {
7878
8630
  const fallback = findOpenApi(files);
7879
8631
  if (fallback) {
7880
8632
  const filename = basename3(fallback.relativePath);
7881
- if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
7882
- assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
8633
+ const matches = (ref) => specRefMatches(ref, fallback.relativePath, filename, true);
8634
+ if (withheldSpecRefs.some(matches) && !publishedSpecRefs.some(matches)) {
8635
+ quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${specAssetPath(filename)}`, content: readFileSync5(fallback.absolutePath) });
8636
+ withheldAssetCount++;
8637
+ } else {
8638
+ if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
8639
+ assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
8640
+ }
8641
+ docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
8642
+ if (withheldSpecRefs.some(matches)) warnSharedSpec(fallback.relativePath);
7883
8643
  }
7884
- docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
7885
8644
  }
7886
8645
  }
7887
8646
  }
@@ -8192,10 +8951,12 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8192
8951
  docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
8193
8952
  }
8194
8953
  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 } };
8954
+ const analytics = projectMintlifyIntegrations(mintlifyConfig);
8955
+ warnings.push(...analytics.warnings);
8956
+ if (analytics.integrations) docsConfig = { ...docsConfig, integrations: analytics.integrations };
8957
+ const appearance = mintlifyAppearance(mintlifyConfig, warnings);
8958
+ if (Object.keys(appearance).length > 0) {
8959
+ docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, ...appearance } };
8199
8960
  }
8200
8961
  const background = mintlifyConfig.background && typeof mintlifyConfig.background === "object" ? mintlifyConfig.background : null;
8201
8962
  if (background) {
@@ -8228,6 +8989,25 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8228
8989
  code: "unsupported-config",
8229
8990
  message: `Brand asset(s) were referenced but not imported: ${[...new Set(missingBrand)].join(", ")}. Add these files under public/ or update docs.json.`
8230
8991
  });
8992
+ if (platform === "mintlify") {
8993
+ docsConfig = { ...docsConfig, markdown: { enabled: true } };
8994
+ if (quarantinedPageCount > 0 || withheldAssetCount > 0 || unreferencedAssetCount > 0) {
8995
+ const assetNote = withheldAssetCount > 0 ? `${withheldAssetCount} file(s) used only by access-restricted pages were kept out of public/ and saved under ${QUARANTINE_DIRECTORY}/assets/. ` : "";
8996
+ const unreferencedReason = [
8997
+ ...hasWithheldContent ? ["this site has access-restricted content"] : [],
8998
+ ...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"] : []
8999
+ ].join(" and ");
9000
+ 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. ` : "";
9001
+ warnings.push({
9002
+ code: "gated-page",
9003
+ 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."
9004
+ });
9005
+ }
9006
+ warnings.push({
9007
+ code: "gated-page",
9008
+ 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."
9009
+ });
9010
+ }
8231
9011
  if (fernRawConfig?.logo && !logoLight && !logo.light) warnings.push({
8232
9012
  code: "unsupported-config",
8233
9013
  message: "Fern supplied a logo through its global theme without a local asset; add a logo path to docs.json after import."
@@ -8240,6 +9020,8 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8240
9020
  assets,
8241
9021
  ...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
8242
9022
  ...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
9023
+ ...quarantinedFiles.length > 0 ? { quarantinedFiles } : {},
9024
+ ...droppedGatedFiles.length > 0 ? { droppedGatedPages: droppedGatedFiles.length } : {},
8243
9025
  docsConfig,
8244
9026
  ...mintlifyConfig ? {
8245
9027
  site: {
@@ -8290,6 +9072,9 @@ function renderPage(bundle, page) {
8290
9072
  ""
8291
9073
  ].filter((line) => line !== null).join("\n");
8292
9074
  }
9075
+ function isIntegrationsObject(value) {
9076
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9077
+ }
8293
9078
  function mergeMigrationConfig(existing, incoming) {
8294
9079
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
8295
9080
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
@@ -8360,7 +9145,15 @@ function mergeMigrationConfig(existing, incoming) {
8360
9145
  // Collection presentation belongs to the imported information
8361
9146
  // architecture; starter defaults must not coerce source dropdowns to tabs.
8362
9147
  navigation: incoming.navigation ?? existing.navigation,
8363
- ...i18n ? { i18n } : {}
9148
+ ...i18n ? { i18n } : {},
9149
+ // Existing providers win per provider; imported ones fill the gaps.
9150
+ // A malformed (string/array) existing value must not spread into index keys.
9151
+ ...isIntegrationsObject(existing.integrations) || isIntegrationsObject(incoming.integrations) ? {
9152
+ integrations: {
9153
+ ...isIntegrationsObject(incoming.integrations) ? incoming.integrations : {},
9154
+ ...isIntegrationsObject(existing.integrations) ? existing.integrations : {}
9155
+ }
9156
+ } : {}
8364
9157
  };
8365
9158
  }
8366
9159
  function renderMigrationFiles(bundle, options = {}) {
@@ -8372,6 +9165,7 @@ function renderMigrationFiles(bundle, options = {}) {
8372
9165
  })),
8373
9166
  ...bundle.assets.map((asset) => ({ path: asset.projectRelative ? asset.path : `public/${asset.path}`, content: asset.content })),
8374
9167
  ...(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]),
9168
+ ...bundle.quarantinedFiles ?? [],
8375
9169
  { path: "docs.json", content: `${JSON.stringify(config, null, 2)}
8376
9170
  ` }
8377
9171
  ];
@@ -9350,7 +10144,7 @@ ${fence}
9350
10144
  }
9351
10145
  });
9352
10146
  const escape = turndown.escape.bind(turndown);
9353
- turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
10147
+ turndown.escape = (text2) => escape(text2).replace(/[<{]/g, (match) => `\\${match}`);
9354
10148
  const body = turndown.turndown(root.html() ?? "").trim();
9355
10149
  if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
9356
10150
  return {
@@ -9903,6 +10697,11 @@ async function migrateUrl(options) {
9903
10697
  topLevelNavigation
9904
10698
  });
9905
10699
  if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
10700
+ if (sourceMintlifyConfig) {
10701
+ const analytics = projectMintlifyIntegrations(sourceMintlifyConfig);
10702
+ warnings.push(...analytics.warnings);
10703
+ if (analytics.integrations) docsConfig.integrations = analytics.integrations;
10704
+ }
9906
10705
  if (docusaurusRedirects.length > 0) {
9907
10706
  docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
9908
10707
  `${redirect.source}:${redirect.destination}`,
@@ -10051,8 +10850,8 @@ async function downloadSpec(value, fetcher) {
10051
10850
  throw new Error("Remote OpenAPI spec redirected too many times.");
10052
10851
  }
10053
10852
  function parseOpenApi(content, extension) {
10054
- const text = content.toString("utf8");
10055
- const parsed = extension === "yaml" || !text.trimStart().startsWith("{") ? parseYaml6(text) : JSON.parse(text);
10853
+ const text2 = content.toString("utf8");
10854
+ const parsed = extension === "yaml" || !text2.trimStart().startsWith("{") ? parseYaml6(text2) : JSON.parse(text2);
10056
10855
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
10057
10856
  const document = parsed;
10058
10857
  if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
@@ -10162,11 +10961,13 @@ export {
10162
10961
  parseGitHubRepositoryUrl,
10163
10962
  parseMarkdownPage,
10164
10963
  projectFernNavigation,
10964
+ projectMintlifyIntegrations,
10165
10965
  projectMintlifyNavigation,
10166
10966
  pruneMissingNavigationPages,
10167
10967
  readFernConfig,
10168
10968
  readMintlifyConfig,
10169
10969
  renderMigrationFiles,
10970
+ validateIntegrations,
10170
10971
  validateMigrationUrl
10171
10972
  };
10172
10973
  //# sourceMappingURL=index.js.map