@thallylabs/migrate 0.2.47 → 0.2.48

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
@@ -419,7 +419,7 @@ function analyzeUnit(index, source, reactGlobals) {
419
419
  const statement = sf.statements[0];
420
420
  const blockers = [];
421
421
  if (!statement || sf.statements.length !== 1) {
422
- return { index, source, names: [], refs: [], assigned: [], hooks: false, clientOnly: false, componentLike: false, blockers: ["is not a single declaration"] };
422
+ return { index, source, names: [], refs: [], assigned: [], hooks: false, clientOnly: false, handlers: false, componentLike: false, blockers: ["is not a single declaration"] };
423
423
  }
424
424
  const { names, componentLike, defaultExport } = declaredNames(statement);
425
425
  if (defaultExport) blockers.push("is a default export");
@@ -447,6 +447,7 @@ function analyzeUnit(index, source, reactGlobals) {
447
447
  assigned,
448
448
  hooks: refs.some(({ name }) => reactGlobals.has(name)) || /\bReact\.(?:use[A-Z]\w*|createContext|forwardRef|memo)\b/.test(source),
449
449
  clientOnly: handlers || refs.some(({ name }) => BROWSER_IDENTIFIERS.has(name)),
450
+ handlers,
450
451
  componentLike,
451
452
  blockers: [...new Set(blockers)]
452
453
  };
@@ -462,9 +463,9 @@ function importLocals(statementText) {
462
463
  if (!ts.isImportDeclaration(statement) || !statement.importClause) continue;
463
464
  const clause = statement.importClause;
464
465
  if (clause.name) locals.push(clause.name.text);
465
- const named = clause.namedBindings;
466
- if (named && ts.isNamespaceImport(named)) locals.push(named.name.text);
467
- if (named && ts.isNamedImports(named)) for (const element of named.elements) locals.push(element.name.text);
466
+ const named2 = clause.namedBindings;
467
+ if (named2 && ts.isNamespaceImport(named2)) locals.push(named2.name.text);
468
+ if (named2 && ts.isNamedImports(named2)) for (const element of named2.elements) locals.push(element.name.text);
468
469
  }
469
470
  return locals;
470
471
  }
@@ -497,6 +498,38 @@ function bodyReferences(tree) {
497
498
  visit(tree);
498
499
  return { mdastTags, refs };
499
500
  }
501
+ function gateOnMount(source, helper) {
502
+ const sf = parse(source);
503
+ const [statement] = sf.statements;
504
+ if (sf.statements.length !== 1) return void 0;
505
+ if (ts.isVariableStatement(statement)) {
506
+ const [declaration, ...others] = statement.declarationList.declarations;
507
+ const value = declaration.initializer && unwrap(declaration.initializer);
508
+ if (others.length > 0 || !ts.isIdentifier(declaration.name) || !declaration.initializer || !value || !(ts.isArrowFunction(value) || ts.isFunctionExpression(value))) return void 0;
509
+ const start = declaration.initializer.getStart(sf);
510
+ return `${source.slice(0, start)}${helper}(${source.slice(start, declaration.initializer.end)})${source.slice(declaration.initializer.end)}`;
511
+ }
512
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body && !statement.asteriskToken) {
513
+ const start = statement.getStart(sf);
514
+ const header = /^(export\s+)?function\b/.exec(source.slice(start));
515
+ if (!header) return void 0;
516
+ const keyword = start + (header[1]?.length ?? 0);
517
+ return `${source.slice(0, keyword)}const ${statement.name.text} = ${helper}(${source.slice(keyword, statement.end)});${source.slice(statement.end)}`;
518
+ }
519
+ return void 0;
520
+ }
521
+ function mountGateHelper(name) {
522
+ return [
523
+ "// Renders nothing on the server or on the first client render, so a component that reads a browser global never disagrees with the server HTML.",
524
+ `function ${name}(Component) {`,
525
+ " return function AfterMount(props) {",
526
+ " const [mounted, setMounted] = useState(false)",
527
+ " useEffect(() => { setMounted(true) }, [])",
528
+ " return mounted ? <Component {...props} /> : null",
529
+ " }",
530
+ "}"
531
+ ].join("\n");
532
+ }
500
533
  function planInlineExtraction(input) {
501
534
  const units = input.declarations.map((declaration, index) => analyzeUnit(index, declaration.source, input.reactGlobals));
502
535
  const owner = /* @__PURE__ */ new Map();
@@ -550,12 +583,17 @@ function planInlineExtraction(input) {
550
583
  }
551
584
  return void 0;
552
585
  };
553
- const roots = units.filter((unit) => unit.hooks && unit.names.some((name) => /^[A-Z]/.test(name))).map((unit) => unit.index);
586
+ const readsBrowserGlobal = (unit) => unit.componentLike && !unit.hooks && !unit.handlers && unit.refs.some(({ name, kind }) => BROWSER_IDENTIFIERS.has(name) && kind !== "tag" && kind !== "member" && !owner.has(name) && !importByLocal.has(name) && !helperByName.has(name)) && gateOnMount(unit.source, "gate") !== void 0;
587
+ const roots = units.filter((unit) => (unit.hooks || readsBrowserGlobal(unit)) && unit.names.some((name) => /^[A-Z]/.test(name))).map((unit) => unit.index);
554
588
  const blocked = [];
555
589
  const active = new Set(roots);
556
590
  const block = (rootIndex, reason) => {
557
591
  active.delete(rootIndex);
558
- blocked.push({ name: units[rootIndex].names.find((name) => /^[A-Z]/.test(name)) ?? units[rootIndex].names[0], reason });
592
+ blocked.push({
593
+ name: units[rootIndex].names.find((name) => /^[A-Z]/.test(name)) ?? units[rootIndex].names[0],
594
+ reason,
595
+ browser: readsBrowserGlobal(units[rootIndex])
596
+ });
559
597
  };
560
598
  for (const rootIndex of roots) {
561
599
  for (const member of closureOf(rootIndex)) {
@@ -644,6 +682,7 @@ function planInlineExtraction(input) {
644
682
  continue;
645
683
  }
646
684
  const movedNames = [...moved].flatMap((index) => units[index].names);
685
+ const gated = [...moved].filter((index) => readsBrowserGlobal(units[index])).sort((a, b) => a - b);
647
686
  const registryNames = movedNames.filter((name) => body.mdastTags.has(name));
648
687
  const importNames = movedNames.filter((name) => pageRefs.some((ref) => ref.name === name));
649
688
  const moduleMembers = [...inModule].sort((a, b) => a - b);
@@ -666,6 +705,7 @@ function planInlineExtraction(input) {
666
705
  blocked,
667
706
  moved: [...moved].sort((a, b) => a - b),
668
707
  copied: [...kept].sort((a, b) => a - b),
708
+ gated,
669
709
  movedNames,
670
710
  moduleImports: usedImports,
671
711
  moduleHelpers: usedHelpers,
@@ -675,7 +715,7 @@ function planInlineExtraction(input) {
675
715
  pageReferences
676
716
  };
677
717
  }
678
- return { blocked, moved: [], copied: [], movedNames: [], moduleImports: [], moduleHelpers: [], registryNames: [], pageImportNames: [], moduleNames: /* @__PURE__ */ new Set(), pageReferences: /* @__PURE__ */ new Set() };
718
+ return { blocked, moved: [], copied: [], gated: [], movedNames: [], moduleImports: [], moduleHelpers: [], registryNames: [], pageImportNames: [], moduleNames: /* @__PURE__ */ new Set(), pageReferences: /* @__PURE__ */ new Set() };
679
719
  }
680
720
  function unboundTags(moduleSource, boundNames) {
681
721
  const sf = parse(moduleSource);
@@ -801,6 +841,7 @@ import { existsSync, lstatSync, readFileSync } from "fs";
801
841
  import { basename, dirname, extname as extname2, posix as posix2, relative as relative2, resolve as resolve2 } from "path";
802
842
  import postcss2 from "postcss";
803
843
  import selectorParser2 from "postcss-selector-parser";
844
+ import remarkGfm2 from "remark-gfm";
804
845
  import remarkMdx2 from "remark-mdx";
805
846
  import remarkParse3 from "remark-parse";
806
847
  import ts3 from "typescript";
@@ -822,6 +863,50 @@ function salvageFrontmatterLines(matter) {
822
863
  }
823
864
  return data;
824
865
  }
866
+ var KEY_LINE = /^[A-Za-z_][\w-]*:(?:\s|$)/;
867
+ var QUOTED_KEY_LINE = /^[A-Za-z_][\w-]*:[ \t]*(["'])/;
868
+ function closingQuote(line, from, quote) {
869
+ for (let index = from; index < line.length; index++) {
870
+ if (quote === '"' && line[index] === "\\") index++;
871
+ else if (line[index] === quote) {
872
+ if (quote === "'" && line[index + 1] === "'") index++;
873
+ else return index;
874
+ }
875
+ }
876
+ return -1;
877
+ }
878
+ function foldQuotedLineBreaks(matter) {
879
+ const lines = matter.split(/\r?\n/);
880
+ const folded = [];
881
+ let changed = false;
882
+ for (let start = 0; start < lines.length; start++) {
883
+ const opening = QUOTED_KEY_LINE.exec(lines[start]);
884
+ if (!opening || closingQuote(lines[start], opening[0].length, opening[1]) !== -1) {
885
+ folded.push(lines[start]);
886
+ continue;
887
+ }
888
+ const quote = opening[1];
889
+ let value = lines[start].slice(opening[0].length).trimEnd();
890
+ let end = start + 1;
891
+ for (; end < lines.length; end++) {
892
+ const line = lines[end];
893
+ if (line.trim() === "" || KEY_LINE.test(line)) return void 0;
894
+ const close = closingQuote(line, 0, quote);
895
+ if (close === -1) {
896
+ value += ` ${line.trim()}`;
897
+ continue;
898
+ }
899
+ if (!/^\s*(?:#.*)?$/.test(line.slice(close + 1))) return void 0;
900
+ value += ` ${line.slice(0, close).trimStart()}`;
901
+ break;
902
+ }
903
+ if (end === lines.length) return void 0;
904
+ folded.push(`${lines[start].slice(0, opening[0].length - 1)}${quote}${value}${quote}`);
905
+ changed = true;
906
+ start = end;
907
+ }
908
+ return changed ? folded.join("\n") : void 0;
909
+ }
825
910
  function parseFrontmatter(raw) {
826
911
  const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
827
912
  const opening = /^---([^\r\n]*)\r?\n/.exec(source);
@@ -843,6 +928,14 @@ function parseFrontmatter(raw) {
843
928
  data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
844
929
  };
845
930
  } catch (err) {
931
+ const foldedMatter = foldQuotedLineBreaks(matter);
932
+ if (foldedMatter !== void 0) {
933
+ try {
934
+ const parsed = parseYaml(foldedMatter);
935
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { content, data: parsed };
936
+ } catch {
937
+ }
938
+ }
846
939
  return {
847
940
  content,
848
941
  data: salvageFrontmatterLines(matter),
@@ -1402,13 +1495,13 @@ function projectAuthoredStyles(css, contents, modules, namespace) {
1402
1495
  });
1403
1496
  const definitionsNamed = /* @__PURE__ */ new Map();
1404
1497
  for (const rule of definitions) pushTo(definitionsNamed, rule.params.trim().toLowerCase(), rule);
1405
- const named = /* @__PURE__ */ new Set();
1498
+ const named2 = /* @__PURE__ */ new Set();
1406
1499
  const unread = [];
1407
1500
  const nameWords = (decl) => {
1408
1501
  for (const [word] of decl.value.matchAll(/[^\s,()"']+/g)) {
1409
1502
  const name = word.toLowerCase();
1410
- if (!named.has(name)) {
1411
- named.add(name);
1503
+ if (!named2.has(name)) {
1504
+ named2.add(name);
1412
1505
  unread.push(name);
1413
1506
  }
1414
1507
  }
@@ -1424,7 +1517,7 @@ function projectAuthoredStyles(css, contents, modules, namespace) {
1424
1517
  const name = rule.name.toLowerCase();
1425
1518
  if (name === "font-face") return;
1426
1519
  if (definitions.has(rule)) {
1427
- if (named.has(rule.params.trim().toLowerCase())) return;
1520
+ if (named2.has(rule.params.trim().toLowerCase())) return;
1428
1521
  omittedAtRules.push(describe(rule));
1429
1522
  rule.remove();
1430
1523
  return;
@@ -1549,6 +1642,7 @@ function normalizeMintlifyCodeFences(body, warn) {
1549
1642
  }
1550
1643
 
1551
1644
  // src/mdx.ts
1645
+ var MDX_COMPILE_OPTIONS = { outputFormat: "program", remarkPlugins: [remarkGfm] };
1552
1646
  function titleFromId(id) {
1553
1647
  const value = id.split("/").at(-1) ?? id;
1554
1648
  if (value === "introduction") return "Introduction";
@@ -1830,6 +1924,7 @@ function mintlifyHeadingSlug(text4) {
1830
1924
  let id = "";
1831
1925
  let afterDash = false;
1832
1926
  let endsWithDash = false;
1927
+ let softDash = false;
1833
1928
  chars.forEach((char, index) => {
1834
1929
  const before = chars[index - 1];
1835
1930
  const after = chars[index + 1];
@@ -1837,7 +1932,9 @@ function mintlifyHeadingSlug(text4) {
1837
1932
  if (isWord(char)) {
1838
1933
  id += char;
1839
1934
  afterDash = false;
1935
+ softDash = false;
1840
1936
  } else if (/[\s-]/.test(char) || char === "." && (isWord(after) || after === ",") || char === "(" && isWord(before) && (isWord(after) || after === "{") || char === ")" && after !== void 0 && !/\s/.test(after)) {
1937
+ softDash = /[\s-]/.test(char) ? false : id !== "" && !afterDash || softDash;
1841
1938
  if (id && !afterDash) id += "-";
1842
1939
  afterDash = true;
1843
1940
  endsWithDash = true;
@@ -1845,24 +1942,28 @@ function mintlifyHeadingSlug(text4) {
1845
1942
  afterDash = false;
1846
1943
  }
1847
1944
  });
1848
- return endsWithDash ? id.replace(/-$/, "") : id;
1945
+ return endsWithDash || softDash ? id.replace(/-$/, "") : id;
1849
1946
  }
1850
1947
  function thallyHeadingSlug(text4) {
1851
1948
  return text4.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/(^-|-$)/g, "");
1852
1949
  }
1950
+ var LINK_DESTINATION = String.raw`\]\((?:[^()]|\([^()]*\))*\)`;
1951
+ var TYPESET_PROTECTED = new RegExp(`(\`[^\`]*\`|<[^>]*>|${LINK_DESTINATION})`);
1853
1952
  function typesetQuotes(text4) {
1854
1953
  const opensClosed = text4.startsWith('"') && !text4.trimEnd().endsWith('"');
1855
- return text4.split(/(`[^`]*`|<[^>]*>|\]\([^)]*\))/).map((part, index) => {
1954
+ return text4.split(TYPESET_PROTECTED).map((part, index) => {
1856
1955
  if (index % 2) return part;
1857
1956
  return part.replace(/(^|[\s([{\u2014\u2013])"/g, (match, before) => opensClosed && index === 0 && before === "" ? match : `${before}\u201C`).replace(/"/g, "\u201D").replace(/(^|[\s([{\u2014\u2013\u201c])'(?=\S)(?!&lt;|<)/g, "$1\u2018").replace(/'/g, "\u2019");
1858
1957
  }).join("");
1859
1958
  }
1860
1959
  function typesetEllipses(text4) {
1861
- return text4.split(/(`[^`]*`|<[^>]*>|\]\([^)]*\))/).map((part, index) => index % 2 ? part : part.replace(/\.{3}/g, "\u2026")).join("");
1960
+ return text4.split(TYPESET_PROTECTED).map((part, index) => index % 2 ? part : part.replace(/\.{3}/g, "\u2026")).join("");
1862
1961
  }
1962
+ var LINK_HOLDING_CODE = new RegExp(`(\`[^\`]*\`)|!?\\[((?:\`[^\`]*\`|[^\\]\`])*)${LINK_DESTINATION}`, "g");
1963
+ var PLAIN_LINK = new RegExp(`!?\\[([^\\]]*)${LINK_DESTINATION}`, "g");
1863
1964
  function plainHeadingText(source) {
1864
- const unlinked = !source.includes("`") ? source : source.replace(/(`[^`]*`)|!?\[((?:`[^`]*`|[^\]`])*)\]\([^)]*\)/g, (_match, code, text4 = "") => code ?? text4);
1865
- return unlinked.split(/(`[^`]*`)/).map((part, index) => index % 2 ? part.slice(1, -1) : part.replace(/\{\/\*[\s\S]*?\*\/\}/g, " ").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/(?<!\\)(\*\*|__|~~|\*)/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1")).join("");
1965
+ const unlinked = !source.includes("`") ? source : source.replace(LINK_HOLDING_CODE, (_match, code, text4 = "") => code ?? text4);
1966
+ return unlinked.split(/(`[^`]*`)/).map((part, index) => index % 2 ? part.slice(1, -1) : part.replace(/\{\/\*[\s\S]*?\*\/\}/g, " ").replace(PLAIN_LINK, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/(?<!\\)(\*\*|__|~~|\*)/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1")).join("");
1866
1967
  }
1867
1968
  function staticAttributeString(value) {
1868
1969
  if (typeof value === "string") return value;
@@ -1992,8 +2093,8 @@ function foldStatic(node, literals) {
1992
2093
  }
1993
2094
  case "Identifier":
1994
2095
  case "MemberExpression": {
1995
- const bound = literals.get(`${node.start}:${node.end}`);
1996
- return bound === void 0 ? void 0 : { value: bound };
2096
+ const span = `${node.start}:${node.end}`;
2097
+ return literals.has(span) ? { value: literals.get(span) } : void 0;
1997
2098
  }
1998
2099
  case "JSXElement":
1999
2100
  case "JSXFragment":
@@ -2032,8 +2133,8 @@ function foldStatic(node, literals) {
2032
2133
  }
2033
2134
  }
2034
2135
  var PLAIN_TEXT = new RegExp("^\\p{L}(?:[\\p{L}\\p{N} ,.;'?%$=+-]*[\\p{L}\\p{N}.])?$", "u");
2035
- function bindSnippetProps(body, properties) {
2036
- if (properties.size === 0 || !body.includes("props") && ![...properties.keys()].some((name) => body.includes(name))) return body;
2136
+ function bindSnippetProps(body, properties, unset = /* @__PURE__ */ new Set()) {
2137
+ if (properties.size === 0 && unset.size === 0 || !body.includes("props") && ![...properties.keys(), ...unset].some((name) => body.includes(name))) return body;
2037
2138
  const isJsx = /<[A-Za-z][A-Za-z0-9]*/.test(body);
2038
2139
  let tree;
2039
2140
  try {
@@ -2075,6 +2176,14 @@ function bindSnippetProps(body, properties) {
2075
2176
  }
2076
2177
  }
2077
2178
  const bound = properties.get(name);
2179
+ if (variables && bound === void 0 && unset.has(name)) {
2180
+ if (parent?.type === "Property" && parent.shorthand) edits.push({ start: parent.start, end: parent.end, value: `${name}: undefined` });
2181
+ else {
2182
+ edits.push({ start, end, value: "undefined" });
2183
+ literals.set(`${start}:${end}`, void 0);
2184
+ }
2185
+ continue;
2186
+ }
2078
2187
  if (!variables || bound === void 0) continue;
2079
2188
  if (name !== "children") {
2080
2189
  if (parent?.type === "Property" && parent.shorthand) edits.push({ start: parent.start, end: parent.end, value: `${name}: ${JSON.stringify(bound)}` });
@@ -2123,8 +2232,8 @@ function bindSnippetProps(body, properties) {
2123
2232
  const { value } = folded;
2124
2233
  const place = (text4) => flow ? asBlock(text4, start, end) : { start, end, value: text4 };
2125
2234
  if (typeof value === "object" && value !== null) return inAttribute ? void 0 : place(keepJsx(value.jsx, bound, literals));
2126
- if (inAttribute) return { start, end, value: `{${JSON.stringify(value)}}` };
2127
- if (typeof value === "boolean" || value === null || value === "") return place("");
2235
+ if (inAttribute) return { start, end, value: `{${value === void 0 ? "undefined" : JSON.stringify(value)}}` };
2236
+ if (typeof value === "boolean" || value === null || value === void 0 || value === "") return place("");
2128
2237
  return place(typeof value === "string" && PLAIN_TEXT.test(value) ? value : `{${JSON.stringify(value)}}`);
2129
2238
  };
2130
2239
  const foldBound = (program, bound, literals, span, inAttribute, flow) => {
@@ -2143,9 +2252,10 @@ function bindSnippetProps(body, properties) {
2143
2252
  const lone = node.type !== "mdxjsEsm" && program.body.length === 1 && statement.type === "ExpressionStatement" && statement.expression.type === "Identifier" ? statement.expression : void 0;
2144
2253
  if (lone) {
2145
2254
  const bound = moduleNames.has(lone.name) ? void 0 : properties.get(lone.name);
2146
- if (bound === void 0) {
2147
- } else if (span || isJsx && lone.name !== "children") edits.push({ start: lone.start, end: lone.end, value: JSON.stringify(bound) });
2148
- else if (start !== void 0 && end !== void 0) edits.push(node.type === "mdxFlowExpression" ? asBlock(bound, start, end) : { start, end, value: bound });
2255
+ const missing = bound === void 0 && !moduleNames.has(lone.name) && unset.has(lone.name);
2256
+ if (bound === void 0 && !missing) {
2257
+ } else if (span || isJsx && lone.name !== "children") edits.push({ start: lone.start, end: lone.end, value: missing ? "undefined" : JSON.stringify(bound) });
2258
+ else if (start !== void 0 && end !== void 0) edits.push(node.type === "mdxFlowExpression" ? asBlock(bound ?? "", start, end) : { start, end, value: bound ?? "" });
2149
2259
  } else {
2150
2260
  const first = edits.length;
2151
2261
  const literals = /* @__PURE__ */ new Map();
@@ -2371,11 +2481,11 @@ function protectMathBlocks(raw) {
2371
2481
  const protectedBody = front + output.join("\n");
2372
2482
  if (!converted) return { body: protectedBody, converted };
2373
2483
  try {
2374
- compileSync(protectedBody, { outputFormat: "program" });
2484
+ compileSync(protectedBody, MDX_COMPILE_OPTIONS);
2375
2485
  return { body: protectedBody, converted };
2376
2486
  } catch {
2377
2487
  try {
2378
- compileSync(raw, { outputFormat: "program" });
2488
+ compileSync(raw, MDX_COMPILE_OPTIONS);
2379
2489
  return { body: raw, converted: false, guardTriggered: true };
2380
2490
  } catch {
2381
2491
  return { body: protectedBody, converted };
@@ -2533,7 +2643,7 @@ function maskCode(body) {
2533
2643
  }
2534
2644
  continue;
2535
2645
  }
2536
- if (fenceMatch) {
2646
+ if (fenceMatch && !(fenceMatch[1][0] === "`" && line.slice(fenceMatch[0].length).includes("`"))) {
2537
2647
  codeFence = fenceMatch[1];
2538
2648
  fenceBuffer = [line];
2539
2649
  continue;
@@ -2582,6 +2692,10 @@ function indentSnippetLines(snippet, indent) {
2582
2692
  return indent + line;
2583
2693
  }).join("\n");
2584
2694
  }
2695
+ function maskedOutsideCode(body) {
2696
+ const { masked, openFence } = maskCode(body);
2697
+ return { text: masked, openFence: openFence !== null };
2698
+ }
2585
2699
  function replaceOutsideCode(body, transform) {
2586
2700
  const { masked, unmask } = maskCode(body);
2587
2701
  return unmask(transform(masked));
@@ -2640,6 +2754,63 @@ function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePat
2640
2754
  (_match, opening, quote, target2) => `${opening}${quote}${rewrite(target2)}${quote}`
2641
2755
  ));
2642
2756
  }
2757
+ function rewriteRelativeJsxHrefs(body, pagePath) {
2758
+ const rewrite = (target2) => {
2759
+ if (!/^\.\.?\//.test(target2) || target2.includes("\\") || /[\u0000-\u001f\u007f]/.test(target2)) return target2;
2760
+ const suffixAt = target2.search(/[?#]/);
2761
+ const pathname = suffixAt < 0 ? target2 : target2.slice(0, suffixAt);
2762
+ if (/%2e|%2f|%5c/i.test(pathname)) return target2;
2763
+ const segments = pagePath.split("/").slice(0, -1);
2764
+ for (const part of pathname.split("/")) {
2765
+ if (part === "..") {
2766
+ segments.pop();
2767
+ } else if (part !== "." && part !== "") {
2768
+ segments.push(part);
2769
+ }
2770
+ }
2771
+ return `/${segments.join("/")}${pathname.endsWith("/") && segments.length > 0 ? "/" : ""}${suffixAt < 0 ? "" : target2.slice(suffixAt)}`;
2772
+ };
2773
+ return replaceOutsideCodeAndComments(body, (text4) => text4.replace(
2774
+ /((?<![\w-])href\s*=\s*)(["'])([^"'\n]+)\2/g,
2775
+ (_match, opening, quote, target2) => `${opening}${quote}${rewrite(target2)}${quote}`
2776
+ ).replace(
2777
+ /((?<![\w-])href\s*=\s*\{\s*)(["'])([^"'\n]+)\2(?=\s*\})/g,
2778
+ (_match, opening, quote, target2) => `${opening}${quote}${rewrite(target2)}${quote}`
2779
+ ));
2780
+ }
2781
+ function templateLiteralRanges(body) {
2782
+ let tree;
2783
+ try {
2784
+ tree = descriptionParser.parse(body);
2785
+ } catch {
2786
+ return null;
2787
+ }
2788
+ const ranges = [];
2789
+ const scan = (value) => {
2790
+ if (!value || typeof value !== "object") return;
2791
+ const node = value;
2792
+ if (node.type === "TemplateLiteral" && typeof node.start === "number" && typeof node.end === "number") ranges.push([node.start, node.end]);
2793
+ for (const child of Object.values(node)) scan(child);
2794
+ };
2795
+ scan(tree);
2796
+ return ranges.sort((left, right) => left[0] - right[0]);
2797
+ }
2798
+ function esmTemplateLiteralRanges(body) {
2799
+ const text4 = body.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, (frontmatter) => frontmatter.replace(/[^\r\n]/g, " "));
2800
+ const direct = templateLiteralRanges(text4);
2801
+ if (direct) return direct;
2802
+ const blanked = text4.replace(/<!--[\s\S]*?-->/g, (comment) => comment.replace(/[^\r\n]/g, " "));
2803
+ const repaired = escapeUnparseableExpressions(escapeUnmatchedClosingTags(blanked).body);
2804
+ const ranges = templateLiteralRanges(repaired.body);
2805
+ if (!ranges) return [];
2806
+ const inserted = [];
2807
+ for (let at = 0, source = 0; at < repaired.body.length; at++) {
2808
+ if (repaired.body[at] === blanked[source]) source++;
2809
+ else inserted.push(at);
2810
+ }
2811
+ const original = (offset) => offset - inserted.filter((at) => at < offset).length;
2812
+ return ranges.map(([start, end]) => [original(start), original(end)]);
2813
+ }
2643
2814
  function normalizeFernFileTrees(body) {
2644
2815
  return replaceOutsideCode(body, (whole) => whole.replace(
2645
2816
  /<FileTree\s*>\s*\n([\s\S]*?)\n\s*<\/FileTree>/g,
@@ -2934,6 +3105,28 @@ function normalizeDocusaurusTabs(body) {
2934
3105
  }
2935
3106
  return result.replace(/<ThallyTabs\b/g, "<Tabs").replace(/<\/ThallyTabs>/g, "</Tabs>");
2936
3107
  }
3108
+ function normalizeStrayTabItems(body) {
3109
+ const tags = [...body.matchAll(/<TabItem\b([^<>]*)>|<\/TabItem>/g)].filter((tag) => !/\/$/.test(tag[1] ?? ""));
3110
+ const openers = [];
3111
+ const paired = /* @__PURE__ */ new Set();
3112
+ tags.forEach((tag, index) => {
3113
+ if (tag[0] !== "</TabItem>") openers.push(index);
3114
+ else if (openers.length > 0) {
3115
+ paired.add(openers.pop());
3116
+ paired.add(index);
3117
+ }
3118
+ });
3119
+ let result = "";
3120
+ let cursor = 0;
3121
+ tags.forEach((tag, index) => {
3122
+ if (!paired.has(index)) return;
3123
+ const attributes = tag[1] ?? "";
3124
+ const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
3125
+ result += `${body.slice(cursor, tag.index)}${tag[0] === "</TabItem>" ? "</Tab>" : `<Tab title="${title.replace(/"/g, "&quot;")}">`}`;
3126
+ cursor = tag.index + tag[0].length;
3127
+ });
3128
+ return result + body.slice(cursor);
3129
+ }
2937
3130
  var TAB_FENCE_OPEN = /^(`{3,}|~{3,})(\S*)\s+tab\b(.*)$/;
2938
3131
  function normalizeDocusaurusTabBlocks(body) {
2939
3132
  const lines = body.split("\n");
@@ -3157,6 +3350,37 @@ function pinStepsTitleSize(body) {
3157
3350
  }
3158
3351
  return result + body.slice(cursor);
3159
3352
  }
3353
+ function addMintlifyVideoAttributes(body) {
3354
+ if (!body.includes("<video")) return body;
3355
+ let result = "";
3356
+ let cursor = 0;
3357
+ for (const match of body.matchAll(/<video(?=[\s/>])/g)) {
3358
+ if (match.index < cursor) continue;
3359
+ const tag = readTagEnd(body, match.index + match[0].length);
3360
+ if (tag === UNTERMINATED) break;
3361
+ if (!tag) continue;
3362
+ let attributes;
3363
+ try {
3364
+ attributes = descriptionParser.parse(`<video${tag.attributes}/>`).children?.[0]?.attributes ?? [];
3365
+ } catch {
3366
+ continue;
3367
+ }
3368
+ const named2 = (name) => attributes.find((attribute2) => attribute2.type === "mdxJsxAttribute" && attribute2.name?.toLowerCase() === name.toLowerCase());
3369
+ const autoPlay = named2("autoPlay");
3370
+ const start = autoPlay?.position?.start.offset;
3371
+ const end = autoPlay?.position?.end.offset;
3372
+ const off = typeof autoPlay?.value === "object" && autoPlay.value?.value?.trim() === "false";
3373
+ const missing = ["muted", "loop", "playsInline"].filter((name) => !named2(name));
3374
+ if (!autoPlay || off || missing.length === 0 || start === void 0 || end === void 0) continue;
3375
+ const at = match.index + end;
3376
+ const indent = body.slice(body.lastIndexOf("\n", match.index + start - 1) + 1, match.index + start);
3377
+ const eol = /^[ \t]*(\r?\n)/.exec(body.slice(at))?.[1];
3378
+ const insert = eol && /^[ \t]*$/.test(indent) ? missing.map((name) => `${eol}${indent}${name}`).join("") : missing.map((name) => ` ${name}`).join("");
3379
+ result += body.slice(cursor, at) + insert;
3380
+ cursor = at;
3381
+ }
3382
+ return result + body.slice(cursor);
3383
+ }
3160
3384
  function escapeUnmatchedClosingTags(body) {
3161
3385
  const masked = maskCodeRegions(body);
3162
3386
  const tagPattern = /(\\?)<(\/?)([A-Za-z][\w.:-]*)/g;
@@ -3174,21 +3398,69 @@ function escapeUnmatchedClosingTags(body) {
3174
3398
  } else if (open.get(name)) {
3175
3399
  open.set(name, open.get(name) - 1);
3176
3400
  } else {
3177
- edits.push(match.index);
3401
+ edits.push({ start: match.index, end: tag.end });
3178
3402
  }
3179
3403
  }
3180
- if (edits.length === 0) return { body, lines: [] };
3404
+ if (edits.length === 0) return { body, escaped: [] };
3181
3405
  let result = "";
3182
3406
  let cursor = 0;
3183
- const lines = [];
3184
- let line = 1;
3185
- for (const start of edits) {
3407
+ for (const { start } of edits) {
3186
3408
  result += `${body.slice(cursor, start)}\\`;
3187
- for (let i = cursor; i < start; i++) if (body.charCodeAt(i) === 10) line++;
3188
- lines.push(line);
3189
3409
  cursor = start;
3190
3410
  }
3191
- return { body: result + body.slice(cursor), lines };
3411
+ return { body: result + body.slice(cursor), escaped: edits.map(({ start, end }) => body.slice(start, end)) };
3412
+ }
3413
+ function isBackslashEscaped(text4, index) {
3414
+ let slashes = 0;
3415
+ while (index - 1 - slashes >= 0 && text4[index - 1 - slashes] === "\\") slashes++;
3416
+ return slashes % 2 === 1;
3417
+ }
3418
+ var MAX_EXPRESSION_REPAIRS = 50;
3419
+ function escapeUnparseableExpressions(body) {
3420
+ let current = body;
3421
+ const escaped = [];
3422
+ for (let repairs = 0; ; repairs++) {
3423
+ let failure;
3424
+ try {
3425
+ compileSync(current, MDX_COMPILE_OPTIONS);
3426
+ return { body: current, escaped };
3427
+ } catch (error) {
3428
+ failure = error;
3429
+ }
3430
+ const offset = failure?.place?.offset;
3431
+ if (repairs === MAX_EXPRESSION_REPAIRS || failure?.source !== "micromark-extension-mdx-expression" || failure.ruleId !== "acorn" || typeof offset !== "number") return { body, escaped: [] };
3432
+ const masked = maskCodeRegions(current);
3433
+ let opener = -1;
3434
+ for (let i = offset - 1, depth = 0; i >= 0 && opener < 0; i--) {
3435
+ if (isBackslashEscaped(masked, i)) continue;
3436
+ if (masked[i] === "}") depth++;
3437
+ else if (masked[i] === "{") {
3438
+ if (depth === 0) opener = i;
3439
+ else depth--;
3440
+ }
3441
+ }
3442
+ if (opener < 0) return { body, escaped: [] };
3443
+ let braces = [];
3444
+ let close = -1;
3445
+ for (let i = opener, depth = 0; i < masked.length && close < 0; i++) {
3446
+ if (isBackslashEscaped(masked, i)) continue;
3447
+ if (masked[i] === "{") {
3448
+ depth++;
3449
+ braces.push(i);
3450
+ } else if (masked[i] === "}" && --depth === 0) {
3451
+ close = i;
3452
+ }
3453
+ }
3454
+ if (close < 0) braces = [opener];
3455
+ escaped.push(current.slice(opener, close < 0 ? opener + 1 : close + 1));
3456
+ let cursor = 0;
3457
+ let result = "";
3458
+ for (const brace of braces) {
3459
+ result += `${current.slice(cursor, brace)}\\`;
3460
+ cursor = brace;
3461
+ }
3462
+ current = result + current.slice(cursor);
3463
+ }
3192
3464
  }
3193
3465
  function isKnownComponentName(name, body) {
3194
3466
  if (isThallyBuiltinComponent(name)) return true;
@@ -3219,7 +3491,7 @@ function escapeOrphanCapitalizedTags(body) {
3219
3491
  }
3220
3492
  if (edits.length === 0) return body;
3221
3493
  try {
3222
- compileSync(body, { outputFormat: "program" });
3494
+ compileSync(body, MDX_COMPILE_OPTIONS);
3223
3495
  return body;
3224
3496
  } catch {
3225
3497
  }
@@ -3345,20 +3617,18 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3345
3617
  ));
3346
3618
  rewritten = rewritten.replace(/^\n{2,}/, "\n");
3347
3619
  }
3620
+ if (runMintlify && rewritten.includes("<TabItem")) rewritten = replaceOutsideCode(rewritten, normalizeDocusaurusTabs);
3348
3621
  if (runMintlify) rewritten = normalizeMintlifyParameterAnchors(rewritten);
3349
3622
  rewritten = unwrapHtmlCommentInMdxComment(rewritten);
3350
3623
  rewritten = replaceOutsideCode(rewritten, (segment) => {
3351
3624
  let result = convertHtmlStyleAttributes(segment).replace(/<!--([\s\S]*?)-->/g, (_match, content) => mdxComment(content)).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Warn(\s[^>]*)?>/g, "<Warning$1>").replace(/<\/Warn>/g, "</Warning>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
3352
3625
  if (runDocusaurus) {
3353
- result = result.replace(/<TabItem\b([^<>]*)>/g, (_match, attributes) => {
3354
- const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
3355
- return `<Tab title="${title.replace(/"/g, "&quot;")}">`;
3356
- }).replace(/<\/TabItem>/g, "</Tab>");
3357
- result = normalizeDocusaurusLinkTags(result).replace(/<(?:DocCardList|TOCInline)\b[^<>]*\/>/g, "");
3626
+ result = normalizeDocusaurusLinkTags(normalizeStrayTabItems(result)).replace(/<(?:DocCardList|TOCInline)\b[^<>]*\/>/g, "");
3358
3627
  }
3359
3628
  if (runMintlify) {
3360
- result = normalizeMintlifyUpdateLabels(result).replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Callout(\s[^<>]*)?>/g, (tag) => tag.replace(/(\s)type=(?:"[^"]*"|'[^']*'|\{[^{}]*\})/, '$1type="info"')).replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
3629
+ result = normalizeMintlifyUpdateLabels(normalizeStrayTabItems(result)).replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Callout(\s[^<>]*)?>/g, (tag) => tag.replace(/(\s)type=(?:"[^"]*"|'[^']*'|\{[^{}]*\})/, '$1type="info"')).replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
3361
3630
  result = pinStepsTitleSize(result);
3631
+ result = addMintlifyVideoAttributes(result);
3362
3632
  }
3363
3633
  if (runFern) {
3364
3634
  result = result.replace(/<CodeBlocks(\s[^>]*)?>/g, "<CodeGroup$1>").replace(/<\/CodeBlocks>/g, "</CodeGroup>").replace(/<ParameterField\b/g, "<ParamField").replace(/<\/ParameterField>/g, "</ParamField>").replace(/<Cards(\s[^>]*)?>/g, "<CardGroup$1>").replace(/<\/Cards>/g, "</CardGroup>").replace(/<Success(\s[^>]*)?>/g, "<Tip$1>").replace(/<\/Success>/g, "</Tip>").replace(/<Launch(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Launch>/g, "</Note>").replace(/<Files(\s[^>]*)?>\n?/g, "").replace(/<\/Files>/g, "");
@@ -3369,7 +3639,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3369
3639
  const normalized = escapeOrphanCapitalizedTags(provideScopedGlobalReferences(rewritten));
3370
3640
  if (runDocusaurus && sourceBody !== body) {
3371
3641
  try {
3372
- compileSync(normalized, { outputFormat: "program" });
3642
+ compileSync(normalized, MDX_COMPILE_OPTIONS);
3373
3643
  } catch {
3374
3644
  return normalizeMdx(body, platform, false);
3375
3645
  }
@@ -3431,14 +3701,20 @@ function parseMarkdownPage(input) {
3431
3701
  const normalized = normalizeMdx(parsed.content, input.platform);
3432
3702
  const body = removeUndefinedExpressions(injectReactHookImports(input.platform === "mintlify" ? normalizeMintlifyCodeFences(normalized, input.warn) : normalized), input.warn).trim();
3433
3703
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
3434
- const heading = leadingPageHeading(body);
3704
+ const namesOperation = typeof parsed.data.openapi === "string" && Boolean(parsed.data.openapi.trim());
3705
+ const heading = input.platform === "mintlify" && !namesOperation ? void 0 : leadingPageHeading(body);
3435
3706
  const authoredTitle = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title;
3436
3707
  const title = authoredTitle ?? titleFromId(identity.navigationId);
3437
- const namesOperation = typeof parsed.data.openapi === "string" && Boolean(parsed.data.openapi.trim());
3438
3708
  const sidebarTitle = input.platform === "docusaurus" ? parsed.data.sidebar_label : parsed.data.sidebarTitle;
3439
3709
  const navTitle = typeof sidebarTitle === "string" && sidebarTitle.trim() ? sidebarTitle.trim() : typeof parsed.data.navTitle === "string" && parsed.data.navTitle.trim() ? parsed.data.navTitle.trim() : void 0;
3440
- const icon = typeof parsed.data.icon === "string" && parsed.data.icon.trim() ? parsed.data.icon.trim() : void 0;
3441
- const iconType = icon && typeof parsed.data.iconType === "string" && ["regular", "solid", "outline", "brands"].includes(parsed.data.iconType) ? parsed.data.iconType : void 0;
3710
+ const iconObject = parsed.data.icon && typeof parsed.data.icon === "object" && !Array.isArray(parsed.data.icon) ? parsed.data.icon : void 0;
3711
+ const iconSource = iconObject ? iconObject.name : parsed.data.icon;
3712
+ const icon = typeof iconSource === "string" && iconSource.trim() ? iconSource.trim() : void 0;
3713
+ const iconStyle = (value) => typeof value === "string" && ["regular", "solid", "outline", "brands"].includes(value) ? value : void 0;
3714
+ const iconType = icon ? iconStyle(iconObject?.style) ?? iconStyle(parsed.data.iconType) : void 0;
3715
+ if (icon && typeof iconObject?.library === "string" && input.siteIconLibrary !== void 0 && iconObject.library !== input.siteIconLibrary) {
3716
+ input.warn?.(`Page "${identity.id}": its icon names the "${iconObject.library}" icon library, but this site uses "${input.siteIconLibrary}", so the icon "${icon}" is looked up there and may show a different icon or none. Use an icon name from "${input.siteIconLibrary}", or set icons.library in docs.json to "${iconObject.library}".`);
3717
+ }
3442
3718
  const meta = {};
3443
3719
  for (const [source, field] of PAGE_META_FIELDS) {
3444
3720
  const value = parsed.data[source];
@@ -3667,7 +3943,12 @@ function youtubeEmbedVideoId(node, specifier) {
3667
3943
  }
3668
3944
  return void 0;
3669
3945
  }
3670
- var parser = unified3().use(remarkParse3).use(remarkMdx2);
3946
+ var COMPONENT_ANALYSIS_FAILED = "Custom component analysis could not parse this MDX";
3947
+ function pageHasComponentWork(body) {
3948
+ const { text: text4, openFence } = maskedOutsideCode(splitFrontmatterBlock(body).body);
3949
+ return openFence || /^[ \t]*(?:import|export)\b|\bon[A-Z]\w*\s*=|\brequire\s*\(/m.test(text4);
3950
+ }
3951
+ var parser = unified3().use(remarkParse3).use(remarkGfm2).use(remarkMdx2);
3671
3952
  var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs"]);
3672
3953
  var DATA_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".css", ".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".woff", ".woff2"]);
3673
3954
  var RESCUABLE_ASSET_EXTENSIONS = /* @__PURE__ */ new Set([".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".ico", ".docx", ".pdf"]);
@@ -4437,7 +4718,7 @@ ${client ? "'use client';\n" : ""}
4437
4718
  try {
4438
4719
  tree = parser.parse(content);
4439
4720
  } catch {
4440
- warn("Custom component analysis could not parse this MDX; its source was preserved for manual migration.", currentFile);
4721
+ warn(`${COMPONENT_ANALYSIS_FAILED}; its source was preserved for manual migration.`, currentFile);
4441
4722
  return frontmatter + neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn);
4442
4723
  }
4443
4724
  const aliases = /* @__PURE__ */ new Map();
@@ -4541,7 +4822,7 @@ ${client ? "'use client';\n" : ""}
4541
4822
  }
4542
4823
  if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
4543
4824
  if (SHARED_IMPORTS.has(specifier)) {
4544
- const isUnusedReactHookImport = specifier === "react" && bindings.length > 0 && bindings.every((binding) => REACT_GLOBALS.has(binding.imported)) && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)));
4825
+ const isUnusedReactHookImport = specifier === "react" && bindings.length > 0 && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)), { includeDirectTags: true });
4545
4826
  if (!isUnusedReactHookImport) moduleImports.push(statement.getText(ast));
4546
4827
  if (isUnusedReactHookImport) edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
4547
4828
  else sharedImportEdits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
@@ -4688,14 +4969,22 @@ ${client ? "'use client';\n" : ""}
4688
4969
  unavailableImports: unsupportedImports,
4689
4970
  reactGlobals: REACT_GLOBALS
4690
4971
  });
4691
- for (const { name, reason } of plan.blocked) {
4972
+ for (const { name, reason, browser } of plan.blocked) {
4692
4973
  refusedInlineComponents.add(name);
4693
- warn(`Inline component "${name}" calls a React hook but was left in the page: ${reason}. Move it and everything it uses into a client component manually.`, currentFile);
4974
+ warn(`Inline component "${name}" ${browser ? "uses `window` or `document`, which only exist in the browser," : "calls a React hook"} but was left in the page: ${reason}. Move it and everything it uses into a client component manually.`, currentFile);
4975
+ }
4976
+ const moduleIndexes = [...plan.moved, ...plan.copied].sort((a, b) => a - b);
4977
+ const gated = new Set(plan.gated);
4978
+ let gate = "renderAfterMount";
4979
+ if (gated.size > 0) {
4980
+ const spelled = identifierNames(sourceFile([...plan.moduleImports, ...plan.moduleHelpers, ...moduleIndexes.map((index) => declarations2[index].source)].join("\n"), "inline.jsx"));
4981
+ for (let suffix = 2; spelled.has(gate); suffix += 1) gate = `renderAfterMount${suffix}`;
4694
4982
  }
4695
4983
  const moduleSource = plan.moved.length === 0 ? "" : [
4696
4984
  ...plan.moduleImports,
4697
4985
  ...plan.moduleHelpers,
4698
- ...[...plan.moved, ...plan.copied].sort((a, b) => a - b).map((index) => declarations2[index].source)
4986
+ ...moduleIndexes.map((index) => (gated.has(index) ? gateOnMount(declarations2[index].source, gate) : void 0) ?? declarations2[index].source),
4987
+ ...gated.size > 0 ? [mountGateHelper(gate)] : []
4699
4988
  ].join("\n\n");
4700
4989
  if (plan.moved.length > 0 && (copied.size >= MAX_COMPONENT_FILES || Buffer.byteLength(moduleSource) > MAX_FILE_BYTES || copiedBytes + Buffer.byteLength(moduleSource) > MAX_COMPONENT_BYTES)) {
4701
4990
  warn(`Inline component "${plan.movedNames[0]}" exceeded the component migration budget; source was preserved.`, currentFile);
@@ -5221,6 +5510,18 @@ function pageRoutes(pages) {
5221
5510
  function pageEntryRoutes(page) {
5222
5511
  return [.../* @__PURE__ */ new Set([...pageRoutes([page]), ...page.id === "introduction" ? ["/"] : []])];
5223
5512
  }
5513
+ function pagesRedirectedFrom(redirects, pages) {
5514
+ const sources = new Set(redirects.flatMap(({ source }) => exactRoute(source) ?? []));
5515
+ return pages.filter((page) => page.id !== "introduction" && !(page.locale && page.id === `${page.locale}/introduction`) && pageRoutes([page]).some((route) => sources.has(route)));
5516
+ }
5517
+ function redirectsOverPagesWarning(routes) {
5518
+ if (routes.length === 0) return null;
5519
+ const one = routes.length === 1;
5520
+ return {
5521
+ code: "unsupported-config",
5522
+ message: `Kept ${routes.length} redirect${one ? "" : "s"} from ${list([...routes])}: ${one ? "that URL is" : "those URLs are"} also ${one ? "a page" : "pages"} in this site. Mintlify follows a redirect and never shows the page, so the ${one ? "page was" : "pages were"} marked noindex and ${one ? "its file stays" : "their files stay"}. Delete ${one ? "the page" : "a page"} and its navigation entry if you do not need it.`
5523
+ };
5524
+ }
5224
5525
  function servedRoutes(pages) {
5225
5526
  return new Set(pageRoutes(pages).map((route) => route.toLowerCase()));
5226
5527
  }
@@ -5427,15 +5728,6 @@ function addMintlifyDirectoryRedirects(config, pages) {
5427
5728
  return match ? route === match[1] || route.startsWith(`${match[1]}/`) : false;
5428
5729
  });
5429
5730
  }
5430
- for (const page of pages) {
5431
- if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
5432
- const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
5433
- if (!parent || pageIds.has(parent.toLowerCase())) continue;
5434
- const source = `/${parent}`;
5435
- if (hasAuthoredRedirect(source)) continue;
5436
- redirects.push({ source, destination: `/${page.id}`, permanent: false });
5437
- redirectSources.add(source.toLowerCase());
5438
- }
5439
5731
  const navigationOrder = [];
5440
5732
  function collectPages(nodes) {
5441
5733
  for (const node of nodes) {
@@ -5463,6 +5755,15 @@ function addMintlifyDirectoryRedirects(config, pages) {
5463
5755
  }
5464
5756
  }
5465
5757
  }
5758
+ for (const page of pages) {
5759
+ if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
5760
+ const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
5761
+ if (!parent || pageIds.has(parent.toLowerCase()) || !firstDescendantByDirectory.has(parent)) continue;
5762
+ const source = `/${parent}`;
5763
+ if (hasAuthoredRedirect(source)) continue;
5764
+ redirects.push({ source, destination: `/${page.id}`, permanent: false });
5765
+ redirectSources.add(source.toLowerCase());
5766
+ }
5466
5767
  for (const [directory2, landing] of firstDescendantByDirectory) {
5467
5768
  if (pageIds.has(directory2.toLowerCase())) continue;
5468
5769
  const source = `/${directory2}`;
@@ -5472,6 +5773,9 @@ function addMintlifyDirectoryRedirects(config, pages) {
5472
5773
  }
5473
5774
  return redirects.length > 0 ? { ...config, redirects } : config;
5474
5775
  }
5776
+ function isMintlifyRootIndex(page, defaultLocale) {
5777
+ return page.id === "introduction" && (!page.locale || page.locale === defaultLocale) && /(?:^|[/#])index\.mdx?$/i.test(page.source);
5778
+ }
5475
5779
  function addMintlifyHomepageRedirects(config, pages) {
5476
5780
  const defaultPages = new Set(pages.filter((page) => !page.locale || page.locale === config.i18n?.defaultLocale).map((page) => page.navigationId));
5477
5781
  function firstPage(nodes) {
@@ -5491,8 +5795,9 @@ function addMintlifyHomepageRedirects(config, pages) {
5491
5795
  const sources = new Set(redirects.map((redirect) => routeKey(redirect.source)));
5492
5796
  const roots = ["", ...(config.i18n?.locales ?? []).filter((locale) => locale.code !== config.i18n?.defaultLocale).filter((locale) => homepage !== "introduction" || pages.some((page) => page.locale === locale.code && page.navigationId === homepage)).map((locale) => locale.code)];
5493
5797
  const homeLinksElsewhere = pages.some((page) => page.id === "introduction" && page.externalUrl);
5798
+ const rootIsIndexPage = pages.some((page) => isMintlifyRootIndex(page, config.i18n?.defaultLocale));
5494
5799
  for (const locale of roots) {
5495
- if (!locale && homeLinksElsewhere) continue;
5800
+ if (!locale && (homeLinksElsewhere || rootIsIndexPage)) continue;
5496
5801
  const source = locale ? `/${locale}` : "/";
5497
5802
  if (sources.has(source.toLowerCase())) continue;
5498
5803
  const destination = `${locale ? `/${locale}` : ""}/${homepage}`;
@@ -6257,12 +6562,43 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
6257
6562
  ...container.hidden === true ? { hidden: true } : {}
6258
6563
  }] : [];
6259
6564
  }
6565
+ function named(text4) {
6566
+ return text4.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 60);
6567
+ }
6260
6568
  function projectedTheme(value) {
6261
6569
  if (value === "maple" || value === "aspen") return "maple";
6262
6570
  if (["luma", "sequoia"].includes(String(value))) return "sharp";
6263
6571
  if (["almond", "palm"].includes(String(value))) return "minimal";
6264
6572
  return typeof value === "string" ? "default" : void 0;
6265
6573
  }
6574
+ function projectedThemeWithWarning(value, warnings) {
6575
+ const theme = projectedTheme(value);
6576
+ if (theme && value !== theme && value !== "mint") {
6577
+ warnings.push({ code: "unsupported-config", message: `theme ${JSON.stringify(named(String(value)))} has no exact Thally equivalent, so ${JSON.stringify(theme)} was used, which is the closest. Compare the look of the migrated site with the live one.`, source: "docs.json" });
6578
+ }
6579
+ return theme;
6580
+ }
6581
+ function projectedErrors(value, warnings) {
6582
+ const errors = objectValue2(value);
6583
+ if (!errors) return void 0;
6584
+ const notCarried = [];
6585
+ let redirect;
6586
+ for (const [status, entry] of Object.entries(errors)) {
6587
+ const settings = status === "404" ? objectValue2(entry) : null;
6588
+ if (!settings) {
6589
+ notCarried.push(named(`errors.${status}`));
6590
+ continue;
6591
+ }
6592
+ for (const [key, setting] of Object.entries(settings)) {
6593
+ if (key === "redirect" && typeof setting === "boolean") redirect = setting;
6594
+ else notCarried.push(named(`errors.404.${key}`));
6595
+ }
6596
+ }
6597
+ if (notCarried.length > 0) {
6598
+ warnings.push({ code: "unsupported-config", message: `docs.json ${notCarried.slice(0, 10).join(", ")}${notCarried.length > 10 ? ", ..." : ""} ${notCarried.length === 1 ? "was" : "were"} not carried over: Thally carries only errors.404.redirect (true or false).`, source: "docs.json" });
6599
+ }
6600
+ return redirect === void 0 ? void 0 : { "404": { redirect } };
6601
+ }
6266
6602
  function projectedColors(value, warnings) {
6267
6603
  const colors = objectValue2(value);
6268
6604
  if (!colors) {
@@ -6410,6 +6746,8 @@ function projectedCompatibleConfig(config, warnings, shortcutSources) {
6410
6746
  }
6411
6747
  const projectedMetatags = metatagEntries.length > 0 ? Object.fromEntries(metatagEntries) : void 0;
6412
6748
  const projectedColorsValue = projectedColors(config.colors, warnings);
6749
+ const theme = projectedThemeWithWarning(config.theme, warnings);
6750
+ const errors = projectedErrors(config.errors, warnings);
6413
6751
  const iconLibrary = objectValue2(config.icons)?.library;
6414
6752
  const projectedIconLibrary = iconLibrary === void 0 ? "fontawesome" : ["lucide", "fontawesome", "tabler"].includes(String(iconLibrary)) ? String(iconLibrary) : void 0;
6415
6753
  const docsNavbar = projectedNavbar(config.navbar);
@@ -6444,7 +6782,8 @@ function projectedCompatibleConfig(config, warnings, shortcutSources) {
6444
6782
  smartQuotes: true,
6445
6783
  // `styling.eyebrows: "breadcrumbs"` shows the whole group path; "section" (the default) shows the nearest group.
6446
6784
  ...objectValue2(config.styling)?.eyebrows === "breadcrumbs" ? { eyebrow: "path" } : {},
6447
- ...projectedTheme(config.theme) ? { theme: projectedTheme(config.theme) } : {},
6785
+ ...theme ? { theme } : {},
6786
+ ...errors ? { errors } : {},
6448
6787
  ...projectedContextual.length > 0 ? { contextual: { options: projectedContextual } } : {},
6449
6788
  ...projectedColorsValue ? { colors: projectedColorsValue } : {},
6450
6789
  ...projectedIconLibrary ? { icons: { library: projectedIconLibrary } } : {},
@@ -8257,10 +8596,10 @@ function namedExportStringText(source, name) {
8257
8596
  `\\bexport\\s+(?:const|let|var)\\s+${name}\\b(?:\\s*:\\s*[^=;]+)?\\s*=\\s*`
8258
8597
  ).exec(source);
8259
8598
  if (!match) return void 0;
8260
- const quoted = source.slice(match.index + match[0].length).match(/^("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/);
8261
- if (!quoted) return void 0;
8599
+ const quoted2 = source.slice(match.index + match[0].length).match(/^("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/);
8600
+ if (!quoted2) return void 0;
8262
8601
  try {
8263
- const parsed = JSON5.parse(quoted[1]);
8602
+ const parsed = JSON5.parse(quoted2[1]);
8264
8603
  return typeof parsed === "string" ? parsed : void 0;
8265
8604
  } catch {
8266
8605
  return void 0;
@@ -8411,13 +8750,13 @@ function staticObjectEntries(array) {
8411
8750
  function staticStringField(source, name, bindings) {
8412
8751
  const start = propertyStart(source, name);
8413
8752
  if (start < 0) return void 0;
8414
- const quoted = source.slice(start).match(/^("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/);
8415
- if (!quoted) {
8753
+ const quoted2 = source.slice(start).match(/^("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/);
8754
+ if (!quoted2) {
8416
8755
  const reference = source.slice(start).match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/)?.[1];
8417
8756
  return reference ? bindings?.get(reference) : void 0;
8418
8757
  }
8419
8758
  try {
8420
- const parsed = JSON5.parse(quoted[1]);
8759
+ const parsed = JSON5.parse(quoted2[1]);
8421
8760
  return typeof parsed === "string" ? parsed : void 0;
8422
8761
  } catch {
8423
8762
  return void 0;
@@ -9291,6 +9630,18 @@ var SAFE_PATH = /^\/[A-Za-z0-9._~\-/]*$/;
9291
9630
  var MAX_PLAUSIBLE_DOMAINS = 10;
9292
9631
  var SUPPORTED = ["ga4", "gtm", "posthog", "plausible"];
9293
9632
  var LEGACY_GA4_ALIAS = "googleAnalytics";
9633
+ var KNOWN_TRACKERS = [
9634
+ ["PostHog", /posthog/i],
9635
+ ["Intercom", /intercom/i],
9636
+ ["Segment", /segment\.(?:com|io)/i],
9637
+ ["Hotjar", /hotjar|_hjSettings/i],
9638
+ ["Google Tag Manager/Analytics", /googletagmanager|google-analytics\.com|\bgtag\s*\(|\bdataLayer\b/i],
9639
+ ["Mixpanel", /mixpanel/i],
9640
+ ["Amplitude", /amplitude/i]
9641
+ ];
9642
+ function trackersIn(source) {
9643
+ return KNOWN_TRACKERS.filter(([, pattern]) => pattern.test(source)).map(([name]) => name);
9644
+ }
9294
9645
  function own(record, key) {
9295
9646
  return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
9296
9647
  }
@@ -10015,9 +10366,9 @@ function projectCapturedTabs($, root) {
10015
10366
  import { createHash as createHash4 } from "crypto";
10016
10367
  import remarkMdx3 from "remark-mdx";
10017
10368
  import remarkParse4 from "remark-parse";
10018
- import remarkGfm2 from "remark-gfm";
10369
+ import remarkGfm3 from "remark-gfm";
10019
10370
  import { unified as unified4 } from "unified";
10020
- var parser2 = unified4().use(remarkParse4).use(remarkGfm2).use(remarkMdx3);
10371
+ var parser2 = unified4().use(remarkParse4).use(remarkGfm3).use(remarkMdx3);
10021
10372
  var MAX_SOURCE_BYTES = 8e6;
10022
10373
  var sourceTrees = /* @__PURE__ */ new Map();
10023
10374
  var cachedSourceBytes = 0;
@@ -10053,20 +10404,20 @@ function readMigrationFlightDocument($) {
10053
10404
  }
10054
10405
  function literalJson(value) {
10055
10406
  if (Buffer.byteLength(value) > MAX_SOURCE_BYTES) return void 0;
10056
- let depth = 0, quoted = false, escaped = false;
10407
+ let depth = 0, quoted2 = false, escaped = false;
10057
10408
  for (const character of value) {
10058
- if (quoted) {
10409
+ if (quoted2) {
10059
10410
  if (escaped) escaped = false;
10060
10411
  else if (character === "\\") escaped = true;
10061
- else if (character === '"') quoted = false;
10062
- } else if (character === '"') quoted = true;
10412
+ else if (character === '"') quoted2 = false;
10413
+ } else if (character === '"') quoted2 = true;
10063
10414
  else if (character === "[" || character === "{") {
10064
10415
  if (++depth > 96) return void 0;
10065
10416
  } else if (character === "]" || character === "}") {
10066
10417
  if (--depth < 0) return void 0;
10067
10418
  }
10068
10419
  }
10069
- if (depth !== 0 || quoted) return void 0;
10420
+ if (depth !== 0 || quoted2) return void 0;
10070
10421
  try {
10071
10422
  return JSON.parse(value);
10072
10423
  } catch {
@@ -13925,14 +14276,49 @@ function readMintignoreMatcher(mintlifyRoot) {
13925
14276
  if (!existsSync5(path) || !lstatSync6(path).isFile()) return null;
13926
14277
  return createIgnoreMatcher().add(readFileSync6(path, "utf8"));
13927
14278
  }
14279
+ var RUNTIME_DEFAULT_ICON_LIBRARY = "lucide";
14280
+ function quoted(items) {
14281
+ const unique = [...new Set(items.map((item) => item.replace(/\s+/g, " ").trim()))];
14282
+ const shown = unique.slice(0, 3).map((item) => `\`${item.length > 60 ? `${item.slice(0, 59)}\u2026` : item}\``);
14283
+ return `${shown.join(", ")}${unique.length > 3 ? `, and ${unique.length - 3} more` : ""}`;
14284
+ }
13928
14285
  function invalidMdxReason(body) {
13929
14286
  try {
13930
- compileSync2(body, { outputFormat: "program" });
14287
+ compileSync2(body, MDX_COMPILE_OPTIONS);
13931
14288
  return null;
13932
14289
  } catch (error) {
13933
14290
  return error instanceof Error ? error.message : String(error);
13934
14291
  }
13935
14292
  }
14293
+ function unresolvedComponentWarning(name, source) {
14294
+ return {
14295
+ code: "unsupported-config",
14296
+ message: `Unresolved MDX component <${name}> was replaced with a plain <div> (or removed, if self-closing) so the page still builds. It may be a site-authored or third-party implementation; review its source before adding a Thally equivalent.`,
14297
+ source
14298
+ };
14299
+ }
14300
+ function repairUncompilableMdx(body) {
14301
+ const closingTags = escapeUnmatchedClosingTags(body);
14302
+ if (closingTags.escaped.length > 0 && invalidMdxReason(closingTags.body) === null) {
14303
+ return { body: closingTags.body, closingTags: closingTags.escaped, braces: [] };
14304
+ }
14305
+ const braces = escapeUnparseableExpressions(closingTags.body);
14306
+ return braces.escaped.length > 0 ? { body: braces.body, closingTags: closingTags.escaped, braces: braces.escaped } : null;
14307
+ }
14308
+ function repairWarnings(repair, source) {
14309
+ return [
14310
+ ...repair.closingTags.length > 0 ? [{
14311
+ code: "unsupported-config",
14312
+ message: `Escaped unmatched closing tag(s) ${quoted(repair.closingTags)} so the page compiles; they render as literal text.`,
14313
+ source
14314
+ }] : [],
14315
+ ...repair.braces.length > 0 ? [{
14316
+ code: "unsupported-config",
14317
+ message: `Escaped the braces of ${quoted(repair.braces)} so the page compiles; they render as literal text.`,
14318
+ source
14319
+ }] : []
14320
+ ];
14321
+ }
13936
14322
  var HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
13937
14323
  function mintlifyThemeColors(value) {
13938
14324
  if (!value || typeof value !== "object") return void 0;
@@ -14177,9 +14563,9 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, n
14177
14563
  for (const page of pages) {
14178
14564
  const specRef = page.openapi ? splitOpenApiRef(page.openapi)?.specRef : void 0;
14179
14565
  if (!specRef || /^[a-z][a-z0-9+.-]*:/i.test(specRef)) continue;
14180
- const named = namedFiles.get(specRef) ?? resolveSpecRef(specRef, specFiles, false);
14181
- namedFiles.set(specRef, named);
14182
- const key = named.kind === "match" ? named.spec.sourcePath : "";
14566
+ const named2 = namedFiles.get(specRef) ?? resolveSpecRef(specRef, specFiles, false);
14567
+ namedFiles.set(specRef, named2);
14568
+ const key = named2.kind === "match" ? named2.spec.sourcePath : "";
14183
14569
  if (!key || known.has(key)) continue;
14184
14570
  known.add(key);
14185
14571
  references.push({ value: key, kind: "openapi", tabLabel: `OpenAPI: ${key}`, hidden: true, pageOnly: true });
@@ -14462,23 +14848,23 @@ function mintlifyEndpointUrls(directory2, operations) {
14462
14848
  const title = mintlifyOperationTitle(operation);
14463
14849
  const tag = operation.tag === void 0 ? "" : mintlifyUrlSlug(operation.tag);
14464
14850
  const slug2 = mintlifyUrlSlug(title);
14465
- const named = slug2 !== "" && (operation.tag === void 0 || tag !== "");
14851
+ const named2 = slug2 !== "" && (operation.tag === void 0 || tag !== "");
14466
14852
  const folder = [directory2, tag].filter(Boolean).join("/");
14467
- slugs.set(operation, { tag, slug: slug2, folder, title, named });
14468
- if (named) spelled.add(`${folder}/${slug2}`);
14853
+ slugs.set(operation, { tag, slug: slug2, folder, title, named: named2 });
14854
+ if (named2) spelled.add(`${folder}/${slug2}`);
14469
14855
  }
14470
14856
  const endpoints = /* @__PURE__ */ new Map();
14471
14857
  const taken = /* @__PURE__ */ new Set();
14472
14858
  const next = /* @__PURE__ */ new Map();
14473
- for (const [operation, { tag, slug: slug2, folder, title, named }] of slugs) {
14859
+ for (const [operation, { tag, slug: slug2, folder, title, named: named2 }] of slugs) {
14474
14860
  let leaf = slug2;
14475
- if (named) {
14861
+ if (named2) {
14476
14862
  let number = next.get(`${folder}/${slug2}`) ?? 1;
14477
14863
  while (taken.has(`${folder}/${leaf}`) || leaf !== slug2 && spelled.has(`${folder}/${leaf}`)) leaf = `${slug2}-${number++}`;
14478
14864
  next.set(`${folder}/${slug2}`, number);
14479
14865
  taken.add(`${folder}/${leaf}`);
14480
14866
  }
14481
- endpoints.set(operation, { tag, leaf, url: [folder, leaf].filter(Boolean).join("/"), title, named });
14867
+ endpoints.set(operation, { tag, leaf, url: [folder, leaf].filter(Boolean).join("/"), title, named: named2 });
14482
14868
  }
14483
14869
  return endpoints;
14484
14870
  }
@@ -15041,10 +15427,29 @@ function staticSnippetProperties(attributes) {
15041
15427
  }
15042
15428
  return properties;
15043
15429
  }
15044
- function interpolateSnippet(snippet, attributes, children) {
15430
+ function passedAttributeNames(attributes) {
15431
+ const tree = ts5.createSourceFile("snippet.tsx", `const snippet = <Snippet ${attributes.replace(/\/\s*$/, "")} />`, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
15432
+ const statement = tree.statements[0];
15433
+ const element = statement && ts5.isVariableStatement(statement) ? statement.declarationList.declarations[0]?.initializer : void 0;
15434
+ if (!element || !ts5.isJsxSelfClosingElement(element)) return void 0;
15435
+ const names = /* @__PURE__ */ new Set();
15436
+ for (const attribute2 of element.attributes.properties) {
15437
+ if (!ts5.isJsxAttribute(attribute2)) return void 0;
15438
+ names.add(attribute2.name.getText(tree));
15439
+ }
15440
+ return names;
15441
+ }
15442
+ function interpolateSnippet(snippet, attributes, children, parameters = []) {
15045
15443
  const properties = staticSnippetProperties(attributes);
15046
15444
  if (children !== void 0) properties.set("children", children.trim());
15047
- return bindSnippetProps(snippet, properties);
15445
+ const unset = /* @__PURE__ */ new Set();
15446
+ const passed = parameters.length > 0 ? passedAttributeNames(attributes) : void 0;
15447
+ for (const { name, fallback } of passed ? parameters : []) {
15448
+ if (name === "children" || passed.has(name)) continue;
15449
+ if (fallback === void 0) unset.add(name);
15450
+ else properties.set(name, fallback);
15451
+ }
15452
+ return bindSnippetProps(snippet, properties, unset);
15048
15453
  }
15049
15454
  function isolateFences(snippet, source, start, end) {
15050
15455
  const fence = /^\s*(?:`{3,}|~{3,})/;
@@ -15058,6 +15463,23 @@ function alignToTag(snippet, source, start) {
15058
15463
  const indent = /^[ \t]*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+)?$/.test(before) ? before.replace(/\S/g, " ") : before.match(/^[ \t]*/)[0];
15059
15464
  return indentSnippetLines(snippet, indent);
15060
15465
  }
15466
+ function snippetParameters(source, componentName) {
15467
+ const parsed = ts5.createSourceFile("snippet.tsx", source, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
15468
+ for (const statement of parsed.statements) {
15469
+ if (!ts5.isVariableStatement(statement)) continue;
15470
+ for (const declaration of statement.declarationList.declarations) {
15471
+ if (!ts5.isIdentifier(declaration.name) || declaration.name.text !== componentName) continue;
15472
+ const pattern = declaration.initializer && ts5.isArrowFunction(declaration.initializer) ? declaration.initializer.parameters[0]?.name : void 0;
15473
+ if (!pattern || !ts5.isObjectBindingPattern(pattern)) return [];
15474
+ return pattern.elements.flatMap((element) => {
15475
+ if (element.dotDotDotToken || element.propertyName || !ts5.isIdentifier(element.name)) return [];
15476
+ if (!element.initializer) return [{ name: element.name.text }];
15477
+ return ts5.isStringLiteralLike(element.initializer) ? [{ name: element.name.text, fallback: element.initializer.text }] : [];
15478
+ });
15479
+ }
15480
+ }
15481
+ return [];
15482
+ }
15061
15483
  function snippetComponentBody(source, componentName) {
15062
15484
  const parsed = ts5.createSourceFile("snippet.tsx", source, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
15063
15485
  for (const statement of parsed.statements) {
@@ -15144,20 +15566,37 @@ function staticNamedSnippetValues(source) {
15144
15566
  return values;
15145
15567
  }
15146
15568
  function hoistMdxImports(source) {
15147
- const imports2 = /* @__PURE__ */ new Set();
15148
- const withoutImports = replaceOutsideCode(source, (body) => body.replace(
15149
- /^[ \t]*import\s+(?:\{[^}\n]+\}|[A-Za-z_$][\w$]*)\s+from\s+(['"])[^'"\n]+\1[ \t]*;?/gm,
15150
- (statement) => {
15151
- imports2.add(statement.trim().replace(/;?$/, ";"));
15152
- return "";
15153
- }
15154
- ));
15569
+ const hoist = (text4) => {
15570
+ const imports3 = /* @__PURE__ */ new Set();
15571
+ const rest2 = replaceOutsideCode(text4, (body) => body.replace(
15572
+ /^[ \t]*import\s+(?:\{[^}\n]+\}|[A-Za-z_$][\w$]*)\s+from\s+(['"])[^'"\n]+\1[ \t]*;?/gm,
15573
+ (statement) => {
15574
+ imports3.add(statement.trim().replace(/;?$/, ";"));
15575
+ return "";
15576
+ }
15577
+ ));
15578
+ return { imports: imports3, rest: rest2 };
15579
+ };
15580
+ let { imports: imports2, rest } = hoist(source);
15155
15581
  if (imports2.size === 0) return source;
15156
- const frontmatter = withoutImports.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
15582
+ const literals = [];
15583
+ let protectedSource = "";
15584
+ let cursor = 0;
15585
+ for (const [start, end] of source.includes("`") ? esmTemplateLiteralRanges(source) : []) {
15586
+ if (start < cursor) continue;
15587
+ literals.push(source.slice(start, end));
15588
+ protectedSource += `${source.slice(cursor, start)}${literals.length - 1}`;
15589
+ cursor = end;
15590
+ }
15591
+ if (literals.length > 0) {
15592
+ ({ imports: imports2, rest } = hoist(protectedSource + source.slice(cursor)));
15593
+ if (imports2.size === 0) return source;
15594
+ }
15595
+ const frontmatter = rest.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
15157
15596
  const prefix = frontmatter?.[0] ?? "";
15158
15597
  return `${prefix}${[...imports2].join("\n")}
15159
15598
 
15160
- ${withoutImports.slice(prefix.length).replace(/^(?:\r?\n)*/, "")}`;
15599
+ ${rest.slice(prefix.length).replace(/^(?:\r?\n)*/, "")}`.replace(/\u0002(\d+)\u0002/g, (marker, index) => literals[Number(index)] ?? marker);
15161
15600
  }
15162
15601
  function fernSourceBasePath(config) {
15163
15602
  const instance = Array.isArray(config.instances) ? config.instances[0] : null;
@@ -15213,14 +15652,26 @@ function stripFernBasePathFromLinks(body, basePath) {
15213
15652
  const escaped = basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15214
15653
  return replaceOutsideCode(body, (text4) => text4.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] === "/" ? "" : "/")));
15215
15654
  }
15216
- function rewriteMintlifyMountedLinks(body, pageIds) {
15217
- const rewrite = (target2) => {
15655
+ function mintlifySourceSite(config) {
15656
+ const canonical2 = config?.seo?.metatags?.canonical;
15657
+ if (typeof canonical2 === "string") {
15658
+ try {
15659
+ const url = new URL(canonical2.trim());
15660
+ if (url.protocol === "http:" || url.protocol === "https:") return { host: url.hostname, path: url.pathname.replace(/\/+$/, "") };
15661
+ } catch {
15662
+ }
15663
+ }
15664
+ const domain = config?.integrations?.plausible?.domain;
15665
+ return typeof domain === "string" && /^(?:docs|developers?)(?:\.[a-z0-9-]+)+$/i.test(domain.trim()) ? { host: domain.trim().toLowerCase(), path: "" } : void 0;
15666
+ }
15667
+ function rewriteMintlifyMountedLinks(body, pageIds, sourceSite) {
15668
+ const resolve5 = (target2) => {
15218
15669
  const suffixAt = target2.search(/[?#]/);
15219
15670
  const pathname = suffixAt < 0 ? target2 : target2.slice(0, suffixAt);
15220
15671
  const suffix = suffixAt < 0 ? "" : target2.slice(suffixAt);
15221
- if (!pathname.startsWith("/") || pathname.startsWith("//") || pathname.includes("\\")) return target2;
15672
+ if (!pathname.startsWith("/") || pathname.startsWith("//") || pathname.includes("\\")) return void 0;
15222
15673
  const segments = pathname.slice(1).split("/");
15223
- if (segments.some((segment) => !segment || segment === "." || segment === "..")) return target2;
15674
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) return void 0;
15224
15675
  const markdown = pathname.endsWith(".md");
15225
15676
  const sourceMdx = pathname.endsWith(".mdx");
15226
15677
  const routeSegments = markdown || sourceMdx ? [...segments.slice(0, -1), segments.at(-1).replace(/\.mdx?$/, "")] : segments;
@@ -15229,9 +15680,21 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
15229
15680
  const candidate = routeSegments.slice(count).join("/");
15230
15681
  if (pageIds.has(candidate)) return `/${candidate}${markdown ? ".md" : ""}${suffix}`;
15231
15682
  }
15232
- return target2;
15683
+ return void 0;
15684
+ };
15685
+ const rewrite = (target2) => {
15686
+ if (target2.startsWith("/")) return resolve5(target2) ?? target2;
15687
+ if (!sourceSite) return target2;
15688
+ try {
15689
+ const url = new URL(target2);
15690
+ if (url.hostname !== sourceSite.host || url.port || url.username || url.password) return target2;
15691
+ if (sourceSite.path && url.pathname !== sourceSite.path && !url.pathname.startsWith(`${sourceSite.path}/`)) return target2;
15692
+ return resolve5(`${url.pathname}${url.search}${url.hash}`) ?? target2;
15693
+ } catch {
15694
+ return target2;
15695
+ }
15233
15696
  };
15234
- return replaceOutsideCode(body, (text4) => text4.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target2, after) => `${before}${rewrite(target2)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target2) => `${before}${rewrite(target2)}${quote}`));
15697
+ return replaceOutsideCode(body, (text4) => text4.replace(/(\]\()(\/[^\s)]{1,2048}|https?:\/\/[^\s)]{1,2048})(\))/g, (_match, before, target2, after) => `${before}${rewrite(target2)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]{1,2048}|https?:\/\/[^'"\n]{1,2048})\2/g, (_match, before, quote, target2) => `${before}${rewrite(target2)}${quote}`));
15235
15698
  }
15236
15699
  function preserveLinkedAnchors(pages) {
15237
15700
  const pageById = new Map(pages.map((page) => [page.id, page]));
@@ -15521,8 +15984,8 @@ function importUndeclaredSnippetComponents(raw, currentFile, repositoryRoot, sit
15521
15984
  try {
15522
15985
  const candidate = resolveSnippetPath(specifier, currentFile, repositoryRoot, siteRoot);
15523
15986
  if (candidate !== match || !lstatSync6(candidate).isFile() || gate?.(candidate)) continue;
15524
- const named = snippetExportedNames(readFileSync6(candidate, "utf8")).has(name);
15525
- statements.push(named ? `import { ${name} } from '${specifier}'` : `import ${name} from '${specifier}'`);
15987
+ const named2 = snippetExportedNames(readFileSync6(candidate, "utf8")).has(name);
15988
+ statements.push(named2 ? `import { ${name} } from '${specifier}'` : `import ${name} from '${specifier}'`);
15526
15989
  warnings.push({
15527
15990
  code: "unsupported-config",
15528
15991
  message: `<${name}> is used without an import; it was resolved to ${specifier.slice(1)} (the only snippet exporting that name). Mintlify renders unimported components as empty, so this page now shows content the live site does not.`,
@@ -15641,6 +16104,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
15641
16104
  if (depth >= 8) return raw;
15642
16105
  raw = normalizeIndentedFences(raw);
15643
16106
  const snippets = /* @__PURE__ */ new Map();
16107
+ const snippetProps = /* @__PURE__ */ new Map();
15644
16108
  const preservedDeclarations = /* @__PURE__ */ new Map();
15645
16109
  const source = relative6(repositoryRoot, currentFile).replace(/\\/g, "/");
15646
16110
  const blockInline = (candidate, shownPath, label = shownPath) => {
@@ -15749,7 +16213,10 @@ ${indent}`), segment, match.index, end);
15749
16213
  );
15750
16214
  const declaration = statefulSnippetDeclaration(nested, componentName);
15751
16215
  if (declaration) preservedDeclarations.set(componentName, declaration);
15752
- else snippets.set(componentName, snippetComponentBody(nested, componentName));
16216
+ else {
16217
+ snippets.set(componentName, snippetComponentBody(nested, componentName));
16218
+ snippetProps.set(componentName, snippetParameters(nested, componentName));
16219
+ }
15753
16220
  return "";
15754
16221
  } catch (error) {
15755
16222
  if (error instanceof Error && error.message.includes("escapes its root")) {
@@ -15834,6 +16301,7 @@ ${indent}`), segment, match.index, end);
15834
16301
  continue;
15835
16302
  }
15836
16303
  components.push([binding.local, body]);
16304
+ if (/^[A-Z]/.test(binding.exported)) snippetProps.set(binding.local, snippetParameters(snippetSource, binding.exported));
15837
16305
  }
15838
16306
  for (const [name, body] of components) snippets.set(name, body);
15839
16307
  return exportBlock(declarations2, statement, sourcePath);
@@ -15875,14 +16343,17 @@ ${indent}`), segment, match.index, end);
15875
16343
  );
15876
16344
  const declaration = statefulSnippetDeclaration(nested, componentName);
15877
16345
  if (declaration) preservedDeclarations.set(componentName, declaration);
15878
- else snippets.set(componentName, snippetComponentBody(nested, componentName));
16346
+ else {
16347
+ snippets.set(componentName, snippetComponentBody(nested, componentName));
16348
+ snippetProps.set(componentName, snippetParameters(nested, componentName));
16349
+ }
15879
16350
  }
15880
16351
  let result = replaceOutsideCodeAndComments(withoutImports, (segment) => {
15881
16352
  for (const [componentName, snippet] of snippets) {
15882
16353
  segment = segment.replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)\\s*/>`, "g"), (tag, attributes, offset, source2) => {
15883
- return isolateFences(alignToTag(closeOpenCodeFence(interpolateSnippet(snippet, attributes)), source2, offset), source2, offset, offset + tag.length);
16354
+ return isolateFences(alignToTag(closeOpenCodeFence(interpolateSnippet(snippet, attributes, void 0, snippetProps.get(componentName))), source2, offset), source2, offset, offset + tag.length);
15884
16355
  }).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>([\\s\\S]*?)<\\/${componentName}>`, "g"), (tag, attributes, children, offset, source2) => {
15885
- return isolateFences(alignToTag(closeOpenCodeFence(interpolateSnippet(snippet, attributes, children)), source2, offset), source2, offset, offset + tag.length);
16356
+ return isolateFences(alignToTag(closeOpenCodeFence(interpolateSnippet(snippet, attributes, children, snippetProps.get(componentName))), source2, offset), source2, offset, offset + tag.length);
15886
16357
  });
15887
16358
  }
15888
16359
  const tagLabel = (filePath) => platform === "fern" ? `Included file ${filePath}` : `Snippet file="${filePath}"`;
@@ -16064,6 +16535,7 @@ function migrateRepository(options) {
16064
16535
  let mintlifyConfig = null;
16065
16536
  let mintlifyApiTabSlots = [];
16066
16537
  let mintlifyDirectories = [];
16538
+ let mintlifyAuthoredRedirects = [];
16067
16539
  const mintlifyNavigationAccess = /* @__PURE__ */ new Map();
16068
16540
  let fernRawConfig = null;
16069
16541
  let fernBasePath = "";
@@ -16110,6 +16582,7 @@ function migrateRepository(options) {
16110
16582
  docsConfig = projected.docsConfig;
16111
16583
  mintlifyApiTabSlots = projected.apiTabSlots;
16112
16584
  mintlifyDirectories = projected.directories;
16585
+ mintlifyAuthoredRedirects = projected.docsConfig.redirects ?? [];
16113
16586
  warnings.push(...projected.warnings);
16114
16587
  for (const repo of projected.gatedSourceRefs) gatedSourceRepos.add(repo.toLowerCase());
16115
16588
  for (const appearance of projected.referenceAccess) {
@@ -16419,6 +16892,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16419
16892
  return { locale, navigationId, id: isDefaultLocale ? navigationId : `${locale}/${navigationId}` };
16420
16893
  };
16421
16894
  const routeAliases = [];
16895
+ const siteIconLibrary = docsConfig.icons?.library ?? RUNTIME_DEFAULT_ICON_LIBRARY;
16422
16896
  const pageFiles = [...files].sort((left, right) => {
16423
16897
  const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
16424
16898
  const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
@@ -16595,9 +17069,27 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16595
17069
  );
16596
17070
  }
16597
17071
  const pageWarningSource = relative6(componentRoot, file.absolutePath).replace(/\\/g, "/");
17072
+ let componentAnalysisLeftWork = false;
16598
17073
  if (componentMigrator) {
16599
17074
  const warningsBeforeTransform = warnings.length;
17075
+ const analysisFailed = () => warnings.slice(warningsBeforeTransform).some((warning) => warning.message.startsWith(COMPONENT_ANALYSIS_FAILED) && warning.source !== void 0 && resolvePath(componentRoot, warning.source) === file.absolutePath);
17076
+ const beforeTransform = raw;
16600
17077
  raw = componentMigrator.transform(raw, file.absolutePath);
17078
+ if (analysisFailed()) {
17079
+ const { front, body } = splitFrontmatterBlock(protectMathBlocks(beforeTransform).body);
17080
+ const repair = repairUncompilableMdx(normalizeHtmlComments(body));
17081
+ if (repair) {
17082
+ const failed = warnings.splice(warningsBeforeTransform);
17083
+ const retried = componentMigrator.transform(front + repair.body, file.absolutePath);
17084
+ if (analysisFailed()) {
17085
+ warnings.splice(warningsBeforeTransform, warnings.length, ...failed);
17086
+ } else {
17087
+ raw = retried;
17088
+ warnings.push(...repairWarnings(repair, file.relativePath));
17089
+ }
17090
+ }
17091
+ }
17092
+ componentAnalysisLeftWork = analysisFailed() && pageHasComponentWork(beforeTransform);
16601
17093
  if (warnings.slice(warningsBeforeTransform).some((warning) => warning.code === "skipped-file" && warning.source !== void 0 && resolvePath(componentRoot, warning.source) === file.absolutePath)) {
16602
17094
  skipped++;
16603
17095
  continue;
@@ -16624,6 +17116,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16624
17116
  navigationId,
16625
17117
  warn: (message) => warnings.push({ code: "unsupported-config", message, source: pageWarningSource }),
16626
17118
  ...locale ? { locale } : {},
17119
+ siteIconLibrary,
16627
17120
  raw,
16628
17121
  platform,
16629
17122
  source: `${options.sourceUrl}#${relative6(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
@@ -16698,25 +17191,28 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16698
17191
  if (platform === "mintlify" || platform === "docusaurus" || platform === "fern") {
16699
17192
  page.body = replaceLinkWithAnchor(page.body);
16700
17193
  page.body = replaceUnknownComponents(page.body, (name) => {
16701
- warnings.push({
16702
- code: "unsupported-config",
16703
- message: `Unresolved MDX component <${name}> was replaced with a plain <div> (or removed, if self-closing) so the page still builds. It may be a site-authored or third-party implementation; review its source before adding a Thally equivalent.`,
16704
- source: file.relativePath
16705
- });
17194
+ warnings.push(unresolvedComponentWarning(name, file.relativePath));
16706
17195
  });
16707
17196
  }
16708
17197
  if (platform === "docusaurus") resolvePageDisplayHeading(page, platform);
16709
17198
  let mdxError = invalidMdxReason(page.body);
16710
17199
  if (mdxError) {
16711
- const repaired = escapeUnmatchedClosingTags(page.body);
16712
- if (repaired.lines.length > 0 && invalidMdxReason(repaired.body) === null) {
16713
- page.body = repaired.body;
16714
- mdxError = null;
16715
- warnings.push({
16716
- code: "unsupported-config",
16717
- message: `Escaped unmatched closing tag(s) on line ${repaired.lines.join(", ")} so the page compiles; they render as literal text.`,
16718
- source: file.relativePath
17200
+ const repair = repairUncompilableMdx(page.body);
17201
+ if (repair && (repair.braces.length === 0 || !componentAnalysisLeftWork)) {
17202
+ const settledWarnings = [];
17203
+ let settled = escapeFernLiteralBraces(repair.body);
17204
+ settled = normalizeFlowParagraphContainers(settled);
17205
+ settled = removeUndefinedExpressions(settled, (message) => {
17206
+ settledWarnings.push({ code: "unsupported-config", message, source: pageWarningSource });
16719
17207
  });
17208
+ settled = replaceUnknownComponents(replaceLinkWithAnchor(settled), (name) => {
17209
+ settledWarnings.push(unresolvedComponentWarning(name, file.relativePath));
17210
+ });
17211
+ if (invalidMdxReason(settled) === null) {
17212
+ page.body = settled;
17213
+ mdxError = null;
17214
+ warnings.push(...repairWarnings(repair, file.relativePath), ...settledWarnings);
17215
+ }
16720
17216
  }
16721
17217
  }
16722
17218
  if (mdxError) {
@@ -16758,7 +17254,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16758
17254
  if (declarationsReferenceBrowserGlobal(page.body)) {
16759
17255
  warnings.push({
16760
17256
  code: "unsupported-config",
16761
- message: "This page defines a component that uses `document`/`window`, which isn't available when pages are rendered on the server. Move that code into a client component or edit the page manually.",
17257
+ message: "This page still defines code that uses `document`/`window`, which isn't available when pages are rendered on the server. Move that code into a client component or edit the page manually.",
16762
17258
  source: file.relativePath
16763
17259
  });
16764
17260
  }
@@ -16987,6 +17483,17 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
16987
17483
  code: "unsupported-config",
16988
17484
  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.`
16989
17485
  });
17486
+ for (const entry of scripts) {
17487
+ const content = assets.find((asset) => asset.path === entry.path)?.content;
17488
+ const trackers = content === void 0 ? [] : trackersIn(Buffer.from(content).toString("utf8"));
17489
+ if (trackers.length > 0) {
17490
+ warnings.push({
17491
+ code: "unsupported-config",
17492
+ message: `Script ${stripControlCharacters(entry.path)} loads or names ${trackers.join(", ")}, which tracks visitors. It was carried over and now runs on every page of the migrated site. Delete the file and its docs.json customScripts entry if you do not want that.`,
17493
+ source: entry.path
17494
+ });
17495
+ }
17496
+ }
16990
17497
  }
16991
17498
  for (const entry of copiedSite.filter((item) => item.kind === "style")) {
16992
17499
  warnings.push({
@@ -17450,7 +17957,8 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
17450
17957
  ...pages.map((page) => page.id),
17451
17958
  ...(docsConfig.redirects ?? []).map((redirect) => redirect.source.replace(/^\//, ""))
17452
17959
  ]);
17453
- for (const page of pages) page.body = rewriteMintlifyMountedLinks(page.body, reachablePaths);
17960
+ const sourceSite = mintlifySourceSite(mintlifyConfig);
17961
+ for (const page of pages) page.body = rewriteMintlifyMountedLinks(page.body, reachablePaths, sourceSite);
17454
17962
  }
17455
17963
  if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
17456
17964
  const additionalRoots = [
@@ -17648,6 +18156,9 @@ ${body}`;
17648
18156
  if (source) page.body = rewriteFernRelativePageLinks(page.body, source, routes);
17649
18157
  }
17650
18158
  }
18159
+ if (platform === "mintlify") {
18160
+ for (const page of pages) page.body = rewriteRelativeJsxHrefs(page.body, page.id);
18161
+ }
17651
18162
  appendMintlifyDirectories(pages, mintlifyDirectories, warnings);
17652
18163
  preserveLinkedAnchors(pages);
17653
18164
  if (platform === "fern" && fernRawConfig) {
@@ -17663,7 +18174,13 @@ ${body}`;
17663
18174
  }
17664
18175
  }
17665
18176
  if (docsConfig.redirects) {
17666
- docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), [...pages, ...sourceRefPages], warnings);
18177
+ const allPages = [...pages, ...sourceRefPages];
18178
+ const takenOver = platform === "mintlify" ? pagesRedirectedFrom(mintlifyAuthoredRedirects, allPages) : [];
18179
+ docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), takenOver.length > 0 ? allPages.filter((page) => !takenOver.includes(page)) : allPages, warnings);
18180
+ const standIns = pagesRedirectedFrom(docsConfig.redirects ?? [], takenOver);
18181
+ for (const page of standIns) page.noindex = true;
18182
+ const standInWarning = redirectsOverPagesWarning(standIns.map((page) => `/${page.id}`));
18183
+ if (standInWarning) warnings.push(standInWarning);
17667
18184
  }
17668
18185
  const redirectSources = new Map((docsConfig.redirects ?? []).map((redirect) => [(redirect.source.replace(/\/+$/, "") || "/").toLowerCase(), redirect.destination]));
17669
18186
  const linkRedirects = [];
@@ -17781,13 +18298,13 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
17781
18298
  });
17782
18299
  }
17783
18300
  if (!docsConfig.navigation?.display && docsConfig.tabs.filter((tab) => !tab.hidden).length > 6) {
17784
- docsConfig = { ...docsConfig, navigation: { display: "dropdown", ...docsConfig.navigation?.groupHeadings ? { groupHeadings: docsConfig.navigation.groupHeadings } : {} } };
18301
+ docsConfig = { ...docsConfig, navigation: { ...docsConfig.navigation, display: "dropdown" } };
17785
18302
  }
17786
18303
  const themeColors = mintlifyConfig ? mintlifyThemeColors(mintlifyConfig.colors) : fernRawConfig ? fernThemeColors(fernRawConfig.colors) ?? (fernRawConfig["global-theme"] === "nvidia" ? { primary: "#76B900" } : void 0) : isTopLevelDocusaurus ? presentation?.colors ?? readDocusaurusThemeColor(docusaurusProjectRoot) : void 0;
17787
18304
  const assetPaths = new Set(assets.map((asset) => asset.path));
17788
18305
  const publicBrandPath = (value) => {
17789
18306
  if (typeof value !== "string" || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(value)) return void 0;
17790
- const normalized = fernBrandAssetPaths.get(value) ?? normalizeAssetPath(value);
18307
+ const normalized = fernBrandAssetPaths.get(value) ?? normalizeAssetPath(value.replace(/^(?:\.[/\\]+)+/, ""));
17791
18308
  return normalized && assetPaths.has(normalized) ? `/${normalized}` : void 0;
17792
18309
  };
17793
18310
  const brandVariants = (value) => {
@@ -18279,14 +18796,14 @@ function target(value, base) {
18279
18796
  function readJson(value) {
18280
18797
  if (Buffer.byteLength(value) > MAX_JSON_BYTES) return void 0;
18281
18798
  let depth = 0;
18282
- let quoted = false;
18799
+ let quoted2 = false;
18283
18800
  let escaped = false;
18284
18801
  for (const char of value) {
18285
- if (quoted) {
18802
+ if (quoted2) {
18286
18803
  if (escaped) escaped = false;
18287
18804
  else if (char === "\\") escaped = true;
18288
- else if (char === '"') quoted = false;
18289
- } else if (char === '"') quoted = true;
18805
+ else if (char === '"') quoted2 = false;
18806
+ } else if (char === '"') quoted2 = true;
18290
18807
  else if (char === "[" || char === "{") {
18291
18808
  if (++depth > 96) return void 0;
18292
18809
  } else if (char === "]" || char === "}") depth--;
@@ -20674,7 +21191,8 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
20674
21191
  const withDirectories = bundle.platform === "mintlify" && newPages.length > 0 ? addMintlifyDirectoryRedirects(boundConfig, newPages) : boundConfig;
20675
21192
  const links = (withDirectories.redirects ?? []).filter(isLinkRedirect);
20676
21193
  const unchecked = links.length > 0 ? { ...withDirectories, redirects: withDirectories.redirects.filter((redirect) => !isLinkRedirect(redirect)) } : withDirectories;
20677
- const checked = dropConflictingRedirects(dropUnroutableRedirects(unchecked, warnings), pages, warnings);
21194
+ const standIns = bundle.platform === "mintlify" ? pagesRedirectedFrom(unchecked.redirects ?? [], pages.filter((page) => page.noindex)) : [];
21195
+ const checked = dropConflictingRedirects(dropUnroutableRedirects(unchecked, warnings), standIns.length > 0 ? pages.filter((page) => !standIns.includes(page)) : pages, warnings);
20678
21196
  const checkedWithLinks = links.length > 0 ? dropConflictingRedirects({ ...checked, redirects: [...checked.redirects ?? [], ...links] }, [], warnings) : checked;
20679
21197
  return {
20680
21198
  ...bundle,