@thallylabs/migrate 0.2.10 → 0.2.12

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
@@ -16,7 +16,7 @@ import {
16
16
  import { createRequire } from "module";
17
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";
18
18
  import { parse as parseYaml4 } from "yaml";
19
- import * as ts2 from "typescript";
19
+ import * as ts3 from "typescript";
20
20
 
21
21
  // src/components.ts
22
22
  import { createHash } from "crypto";
@@ -26,11 +26,25 @@ import postcss from "postcss";
26
26
  import selectorParser from "postcss-selector-parser";
27
27
  import remarkMdx2 from "remark-mdx";
28
28
  import remarkParse2 from "remark-parse";
29
- import ts from "typescript";
29
+ import ts2 from "typescript";
30
30
  import { unified as unified2 } from "unified";
31
31
 
32
32
  // src/frontmatter.ts
33
33
  import { parse as parseYaml } from "yaml";
34
+ function salvageFrontmatterLines(matter) {
35
+ const data = {};
36
+ for (const line of matter.split(/\r?\n/)) {
37
+ if (!/^[A-Za-z_][\w-]*:\s?/.test(line)) continue;
38
+ try {
39
+ const parsedLine = parseYaml(line);
40
+ if (parsedLine && typeof parsedLine === "object" && !Array.isArray(parsedLine)) {
41
+ Object.assign(data, parsedLine);
42
+ }
43
+ } catch {
44
+ }
45
+ }
46
+ return data;
47
+ }
34
48
  function parseFrontmatter(raw) {
35
49
  const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
36
50
  const opening = /^---([^\r\n]*)\r?\n/.exec(source);
@@ -45,11 +59,19 @@ function parseFrontmatter(raw) {
45
59
  if (matter.trim() === "" || !["", "yaml", "yml"].includes(language)) {
46
60
  return { content, data: {} };
47
61
  }
48
- const parsed = parseYaml(matter);
49
- return {
50
- content,
51
- data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
52
- };
62
+ try {
63
+ const parsed = parseYaml(matter);
64
+ return {
65
+ content,
66
+ data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
67
+ };
68
+ } catch (err) {
69
+ return {
70
+ content,
71
+ data: salvageFrontmatterLines(matter),
72
+ error: err instanceof Error ? err.message : String(err)
73
+ };
74
+ }
53
75
  }
54
76
 
55
77
  // src/mdx.ts
@@ -120,7 +142,15 @@ var THALLY_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
120
142
  "Visibility",
121
143
  "Human",
122
144
  "Agent",
123
- "BannerPreview"
145
+ "BannerPreview",
146
+ "Banner",
147
+ "CodeBlock",
148
+ "Column",
149
+ "FileTree",
150
+ "LiteYouTubeEmbed",
151
+ "MDX",
152
+ "Video",
153
+ "YouTube"
124
154
  ]);
125
155
  function isThallyBuiltinComponent(name) {
126
156
  return THALLY_BUILTIN_COMPONENTS.has(name) || THALLY_BUILTIN_COMPONENTS.has(name.split(".")[0]);
@@ -233,8 +263,9 @@ function unwrapDocusaurusMdxCodeBlocks(body) {
233
263
  }
234
264
  return isMdxCodeBlock ? body : output.join("\n");
235
265
  }
236
- function normalizeExplicitHeadingIds(body) {
237
- return replaceOutsideCode(body, (segment) => segment.split("\n").map((line) => {
266
+ function normalizeExplicitHeadingIds(raw) {
267
+ const { front, body } = splitFrontmatterBlock(raw);
268
+ return front + replaceOutsideCode(body, (segment) => segment.split("\n").map((line) => {
238
269
  if (!/^[ \t]{0,3}#{1,6}[ \t]+/.test(line)) return line;
239
270
  const trimmed = line.trimEnd();
240
271
  if (trimmed.endsWith("}")) {
@@ -276,6 +307,7 @@ function removeGlobalDocusaurusImports(body) {
276
307
  }
277
308
  var JS_LITERAL_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "undefined"]);
278
309
  var BARE_IDENTIFIER_PATH = /^[\p{ID_Start}$_][\p{ID_Continue}$‌‍]*(?:\.[\p{ID_Start}$_][\p{ID_Continue}$‌‍]*)*$/u;
310
+ var BARE_OBJECT_SHORTHAND = /^\{\s*([\p{ID_Start}$_][\p{ID_Continue}$‌‍]*)\s*\}$/u;
279
311
  var SAFE_BUILTIN_ROOTS = /* @__PURE__ */ new Set([
280
312
  "Math",
281
313
  "JSON",
@@ -500,12 +532,13 @@ function escapeFernLiteralBraces(raw) {
500
532
  const end = node.position?.end.offset;
501
533
  const root2 = value.split(".")[0];
502
534
  const safeBuiltinPath = value.includes(".") && SAFE_BUILTIN_ROOTS.has(root2);
503
- const isTemplatePlaceholder = /^\{\s*[A-Za-z_$][\w$.-]*\s*\}$/.test(value);
535
+ const isBareIdentifier = BARE_IDENTIFIER_PATH.test(value) && !JS_LITERAL_KEYWORDS.has(value) && !declared.has(root2) && !safeBuiltinPath;
536
+ const shorthandMatch = value.match(BARE_OBJECT_SHORTHAND);
537
+ const isDoubleBraceTemplate = shorthandMatch !== null && !declared.has(shorthandMatch[1]);
504
538
  const isIssueList = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\s*,\s*[A-Za-z][A-Za-z0-9]*-\d+)*$/.test(value);
505
- if ((isTemplatePlaceholder || isIssueList) && start !== void 0 && end !== void 0) {
506
- edits.push({ start, end, value: body.slice(start, end).replace(/\\/g, "\\\\").replace(/[{}]/g, "\\$&") });
507
- } else if (BARE_IDENTIFIER_PATH.test(value) && !JS_LITERAL_KEYWORDS.has(value) && !declared.has(root2) && !safeBuiltinPath && start !== void 0 && end !== void 0) {
508
- edits.push({ start, end, value: `\\{${value}\\}` });
539
+ if ((isBareIdentifier || isDoubleBraceTemplate || isIssueList) && start !== void 0 && end !== void 0) {
540
+ const rawSpan = body.slice(start, end);
541
+ edits.push({ start, end, value: rawSpan.replace(/\\/g, "\\\\").replace(/[{}]/g, "\\$&") });
509
542
  }
510
543
  return;
511
544
  }
@@ -1069,12 +1102,117 @@ function normalizeMintlifyParameterAnchors(body) {
1069
1102
  (match, _tag, _before, doubleQuoted, singleQuoted) => `${anchorFor(doubleQuoted ?? singleQuoted)}${match}`
1070
1103
  ));
1071
1104
  }
1105
+ function matchFenceLine(line) {
1106
+ const match = line.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
1107
+ if (!match) return null;
1108
+ return { index: -1, indent: match[1], char: match[2][0], length: match[2].length, info: match[3].trim(), rest: match[3] };
1109
+ }
1110
+ function normalizeNestedCodeFences(body) {
1111
+ const lines = body.split("\n");
1112
+ const fences = lines.map((line, index) => {
1113
+ const fence = matchFenceLine(line);
1114
+ return fence ? { ...fence, index } : null;
1115
+ }).filter((fence) => fence !== null);
1116
+ if (fences.length === 0) return body;
1117
+ const stack = [];
1118
+ const roots = [];
1119
+ for (const fence of fences) {
1120
+ const top = stack.at(-1);
1121
+ if (top && fence.info === "" && fence.char === top.open.char && fence.length >= top.open.length) {
1122
+ stack.pop();
1123
+ const block = { open: top.open, close: fence, children: top.children };
1124
+ const parent = stack.at(-1);
1125
+ if (parent) parent.children.push(block);
1126
+ else roots.push(block);
1127
+ } else {
1128
+ stack.push({ open: fence, children: [] });
1129
+ }
1130
+ }
1131
+ if (stack.length > 0) return body;
1132
+ const widenedLengths = /* @__PURE__ */ new Map();
1133
+ function requiredLength(block) {
1134
+ const deepestChild = Math.max(0, ...block.children.map(requiredLength));
1135
+ const length = Math.max(block.open.length, deepestChild > 0 ? deepestChild + 1 : 0);
1136
+ widenedLengths.set(block.open.index, length);
1137
+ widenedLengths.set(block.close.index, length);
1138
+ return length;
1139
+ }
1140
+ roots.forEach(requiredLength);
1141
+ return lines.map((line, index) => {
1142
+ const targetLength = widenedLengths.get(index);
1143
+ const fence = targetLength ? matchFenceLine(line) : null;
1144
+ if (!targetLength || !fence || targetLength <= fence.length) return line;
1145
+ return `${fence.indent}${fence.char.repeat(targetLength)}${fence.rest}`;
1146
+ }).join("\n");
1147
+ }
1148
+ function maskCodeRegions(body) {
1149
+ let inFence = false;
1150
+ return body.split("\n").map((line) => {
1151
+ if (/^\s*(`{3,}|~{3,})/.test(line)) {
1152
+ inFence = !inFence;
1153
+ return " ".repeat(line.length);
1154
+ }
1155
+ if (inFence) return " ".repeat(line.length);
1156
+ return line.replace(/`[^`]*`/g, (span) => " ".repeat(span.length));
1157
+ }).join("\n");
1158
+ }
1159
+ function isKnownComponentName(name, body) {
1160
+ if (isThallyBuiltinComponent(name)) return true;
1161
+ if (new RegExp(`^\\s*export\\s+(?:const|function|default\\s+function)\\s+${name}\\b`, "m").test(body)) return true;
1162
+ if (new RegExp(`^\\s*import\\s+(?:\\{[^}]*\\b${name}\\b[^}]*\\}|${name})\\s+from\\s+['"][^'"]+['"]`, "m").test(body)) return true;
1163
+ return false;
1164
+ }
1165
+ function escapeOrphanCapitalizedTags(body) {
1166
+ const masked = maskCodeRegions(body);
1167
+ const tagPattern = /<(\/?)([A-Z][A-Za-z0-9]*)(?:\s+[^<>]*?)?(\/?)>/g;
1168
+ const opens = /* @__PURE__ */ new Map();
1169
+ const closedOrSelfClosed = /* @__PURE__ */ new Set();
1170
+ for (const match of masked.matchAll(tagPattern)) {
1171
+ const [full, closing, name, selfClosing] = match;
1172
+ if (closing || selfClosing) {
1173
+ closedOrSelfClosed.add(name);
1174
+ continue;
1175
+ }
1176
+ const start = match.index;
1177
+ const list = opens.get(name) ?? [];
1178
+ list.push({ start, end: start + full.length });
1179
+ opens.set(name, list);
1180
+ }
1181
+ const edits = [];
1182
+ for (const [name, ranges] of opens) {
1183
+ if (closedOrSelfClosed.has(name) || isKnownComponentName(name, body)) continue;
1184
+ edits.push(...ranges);
1185
+ }
1186
+ if (edits.length === 0) return body;
1187
+ try {
1188
+ compileSync(body, { outputFormat: "program" });
1189
+ return body;
1190
+ } catch {
1191
+ }
1192
+ edits.sort((a, b) => a.start - b.start);
1193
+ let result = "";
1194
+ let cursor = 0;
1195
+ for (const edit of edits) {
1196
+ result += body.slice(cursor, edit.start);
1197
+ result += body.slice(edit.start, edit.end).replace(/^</, "&lt;").replace(/>$/, "&gt;");
1198
+ cursor = edit.end;
1199
+ }
1200
+ return result + body.slice(cursor);
1201
+ }
1202
+ var REACT_HOOK_NAMES = ["useState", "useEffect", "useRef", "useCallback", "useMemo", "useContext", "useReducer"];
1203
+ function injectReactHookImports(body) {
1204
+ const masked = maskCodeRegions(body);
1205
+ const used = REACT_HOOK_NAMES.filter((hook) => new RegExp(`\\b${hook}\\s*\\(`).test(masked) && !new RegExp(`import\\s*\\{[^}]*\\b${hook}\\b[^}]*\\}\\s*from\\s*['"]react['"]`).test(body));
1206
+ return used.length > 0 ? `import { ${used.join(", ")} } from 'react'
1207
+
1208
+ ${body}` : body;
1209
+ }
1072
1210
  function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
1073
1211
  const runFern = platform === "fern";
1074
1212
  const runMintlify = platform === "mintlify";
1075
1213
  const runDocusaurus = platform === "docusaurus";
1076
1214
  const sourceBody = runDocusaurus && unwrapMdxCodeBlocks ? unwrapDocusaurusMdxCodeBlocks(body) : body;
1077
- let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(sourceBody)) : sourceBody));
1215
+ let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(normalizeNestedCodeFences(sourceBody))) : normalizeNestedCodeFences(sourceBody)));
1078
1216
  if (runDocusaurus) {
1079
1217
  rewritten = replaceOutsideCode(rewritten, (segment) => segment.replace(
1080
1218
  /:([+\w-]+):/g,
@@ -1108,7 +1246,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
1108
1246
  return result;
1109
1247
  });
1110
1248
  if (runFern) rewritten = unwrapFernCodeBlockTabs(rewritten);
1111
- const normalized = provideScopedGlobalReferences(rewritten);
1249
+ const normalized = escapeOrphanCapitalizedTags(provideScopedGlobalReferences(rewritten));
1112
1250
  if (runDocusaurus && sourceBody !== body) {
1113
1251
  try {
1114
1252
  compileSync(normalized, { outputFormat: "program" });
@@ -1126,7 +1264,7 @@ function parseMarkdownPage(input) {
1126
1264
  ...input.locale ? { locale: input.locale } : {}
1127
1265
  };
1128
1266
  const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
1129
- let body = normalizeMdx(parsed.content, input.platform).trim();
1267
+ let body = injectReactHookImports(normalizeMdx(parsed.content, input.platform)).trim();
1130
1268
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
1131
1269
  const heading = leadingPageHeading(body);
1132
1270
  const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title ?? titleFromId(identity.navigationId);
@@ -1153,7 +1291,8 @@ function parseMarkdownPage(input) {
1153
1291
  noindex: parsed.data.noindex === true || parsed.data.noindex === "true" ? true : void 0,
1154
1292
  openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
1155
1293
  body,
1156
- source: input.source
1294
+ source: input.source,
1295
+ ...parsed.error ? { frontmatterError: parsed.error } : {}
1157
1296
  };
1158
1297
  }
1159
1298
 
@@ -1206,6 +1345,11 @@ function mintlifyLocalizedReference(value, locale, defaultPageIds = /* @__PURE__
1206
1345
  const candidateId = pageIdFromReference(candidate, true);
1207
1346
  if (candidateId && defaultPageIds.has(candidateId)) return candidate;
1208
1347
  }
1348
+ const segments = reference.split("/");
1349
+ const localeIndex = segments.findIndex((segment) => segment.toLowerCase() === locale.toLowerCase());
1350
+ if (localeIndex > 0) {
1351
+ return [...segments.slice(0, localeIndex), ...segments.slice(localeIndex + 1)].join("/");
1352
+ }
1209
1353
  return value;
1210
1354
  }
1211
1355
  function resolveWithin(root, candidate) {
@@ -1230,6 +1374,543 @@ function normalizeAssetPath(value) {
1230
1374
  return segments.join("/");
1231
1375
  }
1232
1376
 
1377
+ // src/inline-extraction.ts
1378
+ import ts from "typescript";
1379
+ var SERVER_ONLY_IDENTIFIERS = /* @__PURE__ */ new Set(["process", "require", "module", "exports", "__dirname", "__filename", "Buffer"]);
1380
+ var BROWSER_IDENTIFIERS = /* @__PURE__ */ new Set([
1381
+ "window",
1382
+ "document",
1383
+ "localStorage",
1384
+ "sessionStorage",
1385
+ "navigator",
1386
+ "location",
1387
+ "history",
1388
+ "IntersectionObserver",
1389
+ "ResizeObserver",
1390
+ "MutationObserver",
1391
+ "matchMedia",
1392
+ "requestAnimationFrame",
1393
+ "cancelAnimationFrame",
1394
+ "alert",
1395
+ "confirm",
1396
+ "prompt",
1397
+ "HTMLElement",
1398
+ "Element",
1399
+ "Node",
1400
+ "Event",
1401
+ "KeyboardEvent",
1402
+ "MouseEvent",
1403
+ "CustomEvent",
1404
+ "FormData",
1405
+ "File",
1406
+ "FileReader",
1407
+ "Image",
1408
+ "Audio",
1409
+ "XMLHttpRequest",
1410
+ "WebSocket",
1411
+ "getComputedStyle",
1412
+ "innerWidth",
1413
+ "innerHeight",
1414
+ "scrollTo"
1415
+ ]);
1416
+ var JS_GLOBALS = /* @__PURE__ */ new Set([
1417
+ "undefined",
1418
+ "NaN",
1419
+ "Infinity",
1420
+ "globalThis",
1421
+ "arguments",
1422
+ "Object",
1423
+ "Function",
1424
+ "Array",
1425
+ "Number",
1426
+ "Boolean",
1427
+ "String",
1428
+ "Symbol",
1429
+ "BigInt",
1430
+ "Date",
1431
+ "RegExp",
1432
+ "Error",
1433
+ "AggregateError",
1434
+ "EvalError",
1435
+ "RangeError",
1436
+ "ReferenceError",
1437
+ "SyntaxError",
1438
+ "TypeError",
1439
+ "URIError",
1440
+ "JSON",
1441
+ "Math",
1442
+ "Intl",
1443
+ "Reflect",
1444
+ "Proxy",
1445
+ "Promise",
1446
+ "Map",
1447
+ "Set",
1448
+ "WeakMap",
1449
+ "WeakSet",
1450
+ "WeakRef",
1451
+ "ArrayBuffer",
1452
+ "SharedArrayBuffer",
1453
+ "DataView",
1454
+ "Atomics",
1455
+ "Int8Array",
1456
+ "Uint8Array",
1457
+ "Uint8ClampedArray",
1458
+ "Int16Array",
1459
+ "Uint16Array",
1460
+ "Int32Array",
1461
+ "Uint32Array",
1462
+ "Float32Array",
1463
+ "Float64Array",
1464
+ "BigInt64Array",
1465
+ "BigUint64Array",
1466
+ "parseInt",
1467
+ "parseFloat",
1468
+ "isNaN",
1469
+ "isFinite",
1470
+ "encodeURI",
1471
+ "encodeURIComponent",
1472
+ "decodeURI",
1473
+ "decodeURIComponent",
1474
+ "console",
1475
+ "fetch",
1476
+ "URL",
1477
+ "URLSearchParams",
1478
+ "Headers",
1479
+ "Request",
1480
+ "Response",
1481
+ "AbortController",
1482
+ "AbortSignal",
1483
+ "TextEncoder",
1484
+ "TextDecoder",
1485
+ "atob",
1486
+ "btoa",
1487
+ "structuredClone",
1488
+ "queueMicrotask",
1489
+ "performance",
1490
+ "setTimeout",
1491
+ "clearTimeout",
1492
+ "setInterval",
1493
+ "clearInterval"
1494
+ ]);
1495
+ var IMPLICIT_MODULE_NAMES = /* @__PURE__ */ new Set(["React", "MintlifyComponents"]);
1496
+ function parse(source) {
1497
+ return ts.createSourceFile("inline.tsx", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
1498
+ }
1499
+ function bindingNames(name, into) {
1500
+ if (ts.isIdentifier(name)) into.add(name.text);
1501
+ else for (const element of name.elements) if (ts.isBindingElement(element)) bindingNames(element.name, into);
1502
+ }
1503
+ function isFunctionLike(node) {
1504
+ return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node);
1505
+ }
1506
+ function functionScope(fn) {
1507
+ const scope = /* @__PURE__ */ new Set();
1508
+ if ((ts.isFunctionExpression(fn) || ts.isFunctionDeclaration(fn)) && fn.name) scope.add(fn.name.text);
1509
+ for (const parameter of fn.parameters) bindingNames(parameter.name, scope);
1510
+ function collect(node) {
1511
+ if (ts.isVariableDeclaration(node)) bindingNames(node.name, scope);
1512
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) scope.add(node.name.text);
1513
+ if (isFunctionLike(node)) return;
1514
+ ts.forEachChild(node, collect);
1515
+ }
1516
+ if (fn.body) ts.forEachChild(fn.body, collect);
1517
+ if (fn.body && !ts.isBlock(fn.body)) collect(fn.body);
1518
+ return scope;
1519
+ }
1520
+ function collectRefs(root) {
1521
+ const refs = [];
1522
+ const assigned = [];
1523
+ let awaitOutsideAsync = false;
1524
+ let dynamicImport = false;
1525
+ function bound(name, scopes) {
1526
+ return scopes.some((scope) => scope.has(name));
1527
+ }
1528
+ function push(name, kind, scopes) {
1529
+ if (!bound(name, scopes)) refs.push({ name, kind });
1530
+ }
1531
+ function walkNode(node, scopes, inAsync) {
1532
+ if (ts.isTypeNode(node)) return;
1533
+ if (isFunctionLike(node)) {
1534
+ const nextScopes = [...scopes, functionScope(node)];
1535
+ const async = !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword);
1536
+ for (const parameter of node.parameters) {
1537
+ if (!ts.isIdentifier(parameter.name)) walkNode(parameter.name, nextScopes, async);
1538
+ if (parameter.initializer) walkNode(parameter.initializer, nextScopes, async);
1539
+ }
1540
+ if (node.body) walkNode(node.body, nextScopes, async);
1541
+ return;
1542
+ }
1543
+ if (ts.isAwaitExpression(node) && !inAsync) awaitOutsideAsync = true;
1544
+ const target = ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment ? node.left : (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) ? node.operand : void 0;
1545
+ if (target) {
1546
+ let base = target;
1547
+ while (ts.isPropertyAccessExpression(base) || ts.isElementAccessExpression(base)) base = base.expression;
1548
+ if (ts.isIdentifier(base) && !bound(base.text, scopes)) assigned.push(base.text);
1549
+ }
1550
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) dynamicImport = true;
1551
+ if (ts.isIdentifier(node)) {
1552
+ const parent = node.parent;
1553
+ const kind = ts.isJsxExpression(parent) && parent.parent && ts.isJsxAttribute(parent.parent) ? "prop" : "other";
1554
+ push(node.text, kind, scopes);
1555
+ return;
1556
+ }
1557
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
1558
+ let tag = node.tagName;
1559
+ while (ts.isPropertyAccessExpression(tag)) tag = tag.expression;
1560
+ if (ts.isIdentifier(tag)) {
1561
+ if (node.tagName !== tag || /^[A-Z]/.test(tag.text)) push(tag.text, node.tagName === tag ? "tag" : "member", scopes);
1562
+ }
1563
+ walkNode(node.attributes, scopes, inAsync);
1564
+ return;
1565
+ }
1566
+ if (ts.isJsxClosingElement(node)) return;
1567
+ if (ts.isJsxAttribute(node)) {
1568
+ if (node.initializer) walkNode(node.initializer, scopes, inAsync);
1569
+ return;
1570
+ }
1571
+ if (ts.isPropertyAccessExpression(node)) {
1572
+ walkNode(node.expression, scopes, inAsync);
1573
+ return;
1574
+ }
1575
+ if (ts.isQualifiedName(node)) return;
1576
+ if (ts.isPropertyAssignment(node)) {
1577
+ if (ts.isComputedPropertyName(node.name)) walkNode(node.name.expression, scopes, inAsync);
1578
+ walkNode(node.initializer, scopes, inAsync);
1579
+ return;
1580
+ }
1581
+ if (ts.isShorthandPropertyAssignment(node)) {
1582
+ push(node.name.text, "other", scopes);
1583
+ if (node.objectAssignmentInitializer) walkNode(node.objectAssignmentInitializer, scopes, inAsync);
1584
+ return;
1585
+ }
1586
+ if (ts.isBindingElement(node)) {
1587
+ if (node.propertyName && ts.isComputedPropertyName(node.propertyName)) walkNode(node.propertyName.expression, scopes, inAsync);
1588
+ if (node.initializer) walkNode(node.initializer, scopes, inAsync);
1589
+ if (!ts.isIdentifier(node.name)) walkNode(node.name, scopes, inAsync);
1590
+ return;
1591
+ }
1592
+ if (ts.isVariableDeclaration(node)) {
1593
+ if (!ts.isIdentifier(node.name)) walkNode(node.name, scopes, inAsync);
1594
+ if (node.initializer) walkNode(node.initializer, scopes, inAsync);
1595
+ return;
1596
+ }
1597
+ if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
1598
+ node.members.forEach((member) => walkNode(member, scopes, inAsync));
1599
+ for (const clause of node.heritageClauses ?? []) for (const type of clause.types) walkNode(type.expression, scopes, inAsync);
1600
+ return;
1601
+ }
1602
+ if (ts.isPropertyDeclaration(node)) {
1603
+ if (node.initializer) walkNode(node.initializer, scopes, inAsync);
1604
+ return;
1605
+ }
1606
+ if (ts.isLabeledStatement(node)) {
1607
+ walkNode(node.statement, scopes, inAsync);
1608
+ return;
1609
+ }
1610
+ if (ts.isBreakOrContinueStatement(node)) return;
1611
+ ts.forEachChild(node, (child) => walkNode(child, scopes, inAsync));
1612
+ }
1613
+ walkNode(root, [], false);
1614
+ return { refs, assigned, awaitOutsideAsync, dynamicImport };
1615
+ }
1616
+ function declaredNames(statement) {
1617
+ const isDefault = !!(ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : void 0)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) || ts.isExportAssignment(statement);
1618
+ const names = /* @__PURE__ */ new Set();
1619
+ let componentLike = false;
1620
+ if (ts.isVariableStatement(statement)) {
1621
+ for (const declaration of statement.declarationList.declarations) {
1622
+ bindingNames(declaration.name, names);
1623
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
1624
+ if (ts.isIdentifier(declaration.name) && /^[A-Z]/.test(declaration.name.text) && initializer && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer) || ts.isClassExpression(initializer))) {
1625
+ componentLike = true;
1626
+ }
1627
+ }
1628
+ } else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
1629
+ names.add(statement.name.text);
1630
+ componentLike = /^[A-Z]/.test(statement.name.text);
1631
+ }
1632
+ return { names: [...names], componentLike, defaultExport: isDefault };
1633
+ }
1634
+ function unwrap(expression) {
1635
+ let current = expression;
1636
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) current = current.expression;
1637
+ return current;
1638
+ }
1639
+ function analyzeUnit(index, source, reactGlobals) {
1640
+ const sf = parse(source);
1641
+ const statement = sf.statements[0];
1642
+ const blockers = [];
1643
+ if (!statement || sf.statements.length !== 1) {
1644
+ return { index, source, names: [], refs: [], assigned: [], hooks: false, clientOnly: false, componentLike: false, blockers: ["is not a single declaration"] };
1645
+ }
1646
+ const { names, componentLike, defaultExport } = declaredNames(statement);
1647
+ if (defaultExport) blockers.push("is a default export");
1648
+ const { refs, assigned, awaitOutsideAsync, dynamicImport } = collectRefs(statement);
1649
+ if (awaitOutsideAsync) blockers.push("uses await outside an async function");
1650
+ if (dynamicImport) blockers.push("uses a dynamic import()");
1651
+ const initializer = ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1 ? statement.declarationList.declarations[0].initializer && unwrap(statement.declarationList.declarations[0].initializer) : statement;
1652
+ if (initializer && isFunctionLike(initializer) && initializer.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
1653
+ blockers.push("is an async (server) component");
1654
+ }
1655
+ for (const { name } of refs) {
1656
+ if (SERVER_ONLY_IDENTIFIERS.has(name)) blockers.push(`uses the server-only \`${name}\``);
1657
+ }
1658
+ let handlers = false;
1659
+ function scanHandlers(node) {
1660
+ if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && /^on[A-Z]/.test(node.name.text)) handlers = true;
1661
+ ts.forEachChild(node, scanHandlers);
1662
+ }
1663
+ scanHandlers(statement);
1664
+ return {
1665
+ index,
1666
+ source,
1667
+ names,
1668
+ refs,
1669
+ assigned,
1670
+ hooks: refs.some(({ name }) => reactGlobals.has(name)) || /\bReact\.(?:use[A-Z]\w*|createContext|forwardRef|memo)\b/.test(source),
1671
+ clientOnly: handlers || refs.some(({ name }) => BROWSER_IDENTIFIERS.has(name)),
1672
+ componentLike,
1673
+ blockers: [...new Set(blockers)]
1674
+ };
1675
+ }
1676
+ function helperNames(source) {
1677
+ const statement = parse(source).statements[0];
1678
+ return statement ? declaredNames(statement).names : [];
1679
+ }
1680
+ function importLocals(statementText) {
1681
+ const sf = parse(statementText);
1682
+ const locals = [];
1683
+ for (const statement of sf.statements) {
1684
+ if (!ts.isImportDeclaration(statement) || !statement.importClause) continue;
1685
+ const clause = statement.importClause;
1686
+ if (clause.name) locals.push(clause.name.text);
1687
+ const named = clause.namedBindings;
1688
+ if (named && ts.isNamespaceImport(named)) locals.push(named.name.text);
1689
+ if (named && ts.isNamedImports(named)) for (const element of named.elements) locals.push(element.name.text);
1690
+ }
1691
+ return locals;
1692
+ }
1693
+ function expressionRefs(expression) {
1694
+ const sf = parse(`const __expression = (${expression})`);
1695
+ const statement = sf.statements[0];
1696
+ return statement ? collectRefs(statement).refs : [];
1697
+ }
1698
+ function bodyReferences(tree) {
1699
+ const mdastTags = /* @__PURE__ */ new Set();
1700
+ const refs = [];
1701
+ function visit(node) {
1702
+ if (node.type === "mdxjsEsm") return;
1703
+ if (["mdxJsxFlowElement", "mdxJsxTextElement"].includes(node.type) && node.name) {
1704
+ const root = node.name.split(".")[0];
1705
+ if (node.name.includes(".")) refs.push({ name: root, kind: "member" });
1706
+ else mdastTags.add(root);
1707
+ }
1708
+ if (["mdxFlowExpression", "mdxTextExpression"].includes(node.type) && node.value) refs.push(...expressionRefs(node.value));
1709
+ for (const attribute of node.attributes ?? []) {
1710
+ const value = attribute.value;
1711
+ if (attribute.type === "mdxJsxExpressionAttribute" && typeof value === "string") {
1712
+ refs.push(...expressionRefs(`{${value}}`));
1713
+ } else if (value && typeof value === "object" && typeof value.value === "string") {
1714
+ refs.push(...expressionRefs(value.value).map((ref, _index, all) => all.length === 1 && value.value.trim() === ref.name ? { name: ref.name, kind: "prop" } : ref));
1715
+ }
1716
+ }
1717
+ for (const child of node.children ?? []) visit(child);
1718
+ }
1719
+ visit(tree);
1720
+ return { mdastTags, refs };
1721
+ }
1722
+ function planInlineExtraction(input) {
1723
+ const units = input.declarations.map((declaration, index) => analyzeUnit(index, declaration.source, input.reactGlobals));
1724
+ const owner = /* @__PURE__ */ new Map();
1725
+ for (const unit of units) for (const name of unit.names) owner.set(name, unit.index);
1726
+ for (let changed = true; changed; ) {
1727
+ changed = false;
1728
+ for (const unit of units) {
1729
+ if (unit.hooks) continue;
1730
+ if (unit.refs.some(({ name }) => /^use[A-Z]/.test(name) && units[owner.get(name) ?? -1]?.hooks)) {
1731
+ unit.hooks = true;
1732
+ changed = true;
1733
+ }
1734
+ }
1735
+ }
1736
+ const importByLocal = /* @__PURE__ */ new Map();
1737
+ for (const statement of input.copyableImports) for (const local of importLocals(statement)) importByLocal.set(local, statement);
1738
+ const helperByName = /* @__PURE__ */ new Map();
1739
+ for (const helper of input.helperDeclarations) {
1740
+ for (const name of helperNames(helper)) helperByName.set(name, helper);
1741
+ }
1742
+ const body = bodyReferences(input.tree);
1743
+ const dependencies = (unit) => {
1744
+ const found = /* @__PURE__ */ new Set();
1745
+ for (const { name } of unit.refs) {
1746
+ const target = owner.get(name);
1747
+ if (target !== void 0 && target !== unit.index) found.add(target);
1748
+ }
1749
+ return found;
1750
+ };
1751
+ const closureOf = (rootIndex) => {
1752
+ const seen = /* @__PURE__ */ new Set([rootIndex]);
1753
+ const queue = [rootIndex];
1754
+ while (queue.length) for (const next of dependencies(units[queue.pop()])) {
1755
+ if (!seen.has(next)) {
1756
+ seen.add(next);
1757
+ queue.push(next);
1758
+ }
1759
+ }
1760
+ return seen;
1761
+ };
1762
+ const intrinsicBlocker = (unit) => {
1763
+ if (unit.blockers.length > 0) return `\`${unit.names[0] ?? "a dependency"}\` ${unit.blockers[0]}`;
1764
+ for (const { name, kind } of unit.refs) {
1765
+ if (owner.has(name) || importByLocal.has(name) || helperByName.has(name)) continue;
1766
+ if (input.unavailableImports.has(name)) {
1767
+ return `\`${unit.names[0]}\` uses \`${name}\`, imported from ${input.unavailableImports.get(name)}, which is not available in the migrated project`;
1768
+ }
1769
+ if (input.reactGlobals.has(name) || IMPLICIT_MODULE_NAMES.has(name) || JS_GLOBALS.has(name) || BROWSER_IDENTIFIERS.has(name)) continue;
1770
+ if ((kind === "tag" || kind === "member") && isThallyBuiltinComponent(name)) continue;
1771
+ return `\`${unit.names[0]}\` uses \`${name}\`, which is not declared or imported on the page`;
1772
+ }
1773
+ return void 0;
1774
+ };
1775
+ const roots = units.filter((unit) => unit.hooks && unit.names.some((name) => /^[A-Z]/.test(name))).map((unit) => unit.index);
1776
+ const blocked = [];
1777
+ const active = new Set(roots);
1778
+ const block = (rootIndex, reason) => {
1779
+ active.delete(rootIndex);
1780
+ blocked.push({ name: units[rootIndex].names.find((name) => /^[A-Z]/.test(name)) ?? units[rootIndex].names[0], reason });
1781
+ };
1782
+ for (const rootIndex of roots) {
1783
+ for (const member of closureOf(rootIndex)) {
1784
+ const reason = intrinsicBlocker(units[member]);
1785
+ if (reason) {
1786
+ block(rootIndex, reason);
1787
+ break;
1788
+ }
1789
+ }
1790
+ }
1791
+ for (let round = 0; round <= units.length + 1; round += 1) {
1792
+ const inModule = /* @__PURE__ */ new Set();
1793
+ for (const rootIndex of active) for (const member of closureOf(rootIndex)) inModule.add(member);
1794
+ const clientSide = /* @__PURE__ */ new Set();
1795
+ for (const index of inModule) if (units[index].hooks || units[index].clientOnly) clientSide.add(index);
1796
+ for (let changed = true; changed; ) {
1797
+ changed = false;
1798
+ for (const index of inModule) {
1799
+ if (clientSide.has(index)) continue;
1800
+ const unit = units[index];
1801
+ const callsClient = unit.refs.some(({ name, kind }) => {
1802
+ const target = owner.get(name);
1803
+ return target !== void 0 && clientSide.has(target) && kind !== "tag" && kind !== "prop";
1804
+ });
1805
+ if (callsClient) {
1806
+ clientSide.add(index);
1807
+ changed = true;
1808
+ }
1809
+ }
1810
+ }
1811
+ for (const rootIndex of active) clientSide.add(rootIndex);
1812
+ const nonModuleRefs = [...body.refs];
1813
+ for (const unit of units) if (!inModule.has(unit.index)) nonModuleRefs.push(...unit.refs);
1814
+ const referencedByPage = (index) => units[index].names.some((name) => body.mdastTags.has(name) || nonModuleRefs.some((ref) => ref.name === name));
1815
+ const kept = /* @__PURE__ */ new Set();
1816
+ for (const index of inModule) {
1817
+ if (clientSide.has(index)) continue;
1818
+ if (!units[index].componentLike || referencedByPage(index)) kept.add(index);
1819
+ }
1820
+ for (let changed = true; changed; ) {
1821
+ changed = false;
1822
+ for (const index of kept) for (const dependency of dependencies(units[index])) {
1823
+ if (inModule.has(dependency) && !clientSide.has(dependency) && !kept.has(dependency)) {
1824
+ kept.add(dependency);
1825
+ changed = true;
1826
+ }
1827
+ }
1828
+ }
1829
+ const moved = new Set([...inModule].filter((index) => !kept.has(index)));
1830
+ const pageRefs = [...nonModuleRefs];
1831
+ for (const index of kept) pageRefs.push(...units[index].refs);
1832
+ const violation = [...moved].find((index) => clientSide.has(index) && units[index].names.some((name) => pageRefs.some((ref) => ref.name === name && ref.kind !== "tag" && ref.kind !== "prop")));
1833
+ if (violation !== void 0) {
1834
+ const names = units[violation].names;
1835
+ const readsIt = (ref) => names.includes(ref.name) && ref.kind !== "tag" && ref.kind !== "prop";
1836
+ const readers = units.filter((unit) => !moved.has(unit.index) && unit.refs.some(readsIt));
1837
+ const promotable = !body.refs.some(readsIt) && readers.every((unit) => unit.componentLike && !active.has(unit.index) && [...closureOf(unit.index)].every((member) => !intrinsicBlocker(units[member])));
1838
+ if (promotable && readers.length > 0) {
1839
+ for (const unit of readers) active.add(unit.index);
1840
+ continue;
1841
+ }
1842
+ for (const rootIndex of [...active]) {
1843
+ if (closureOf(rootIndex).has(violation)) {
1844
+ block(rootIndex, `\`${names[0]}\` is also read or called by page code that stays on the server`);
1845
+ }
1846
+ }
1847
+ continue;
1848
+ }
1849
+ const reachable = new Set([...kept].filter(referencedByPage));
1850
+ for (let changed = true; changed; ) {
1851
+ changed = false;
1852
+ for (const index of [...reachable]) for (const dependency of dependencies(units[index])) {
1853
+ if (kept.has(dependency) && !reachable.has(dependency)) {
1854
+ reachable.add(dependency);
1855
+ changed = true;
1856
+ }
1857
+ }
1858
+ }
1859
+ const mutated = [...reachable].find((index) => units.some((unit) => unit.assigned.some((name) => units[index].names.includes(name))));
1860
+ if (mutated !== void 0) {
1861
+ for (const rootIndex of [...active]) {
1862
+ if (closureOf(rootIndex).has(mutated)) {
1863
+ block(rootIndex, `\`${units[mutated].names[0]}\` is mutable state that page code also uses, so a copy in the client module would diverge`);
1864
+ }
1865
+ }
1866
+ continue;
1867
+ }
1868
+ const movedNames = [...moved].flatMap((index) => units[index].names);
1869
+ const registryNames = movedNames.filter((name) => body.mdastTags.has(name));
1870
+ const importNames = movedNames.filter((name) => pageRefs.some((ref) => ref.name === name));
1871
+ const moduleMembers = [...inModule].sort((a, b) => a - b);
1872
+ const moduleNames = new Set(moduleMembers.flatMap((index) => units[index].names));
1873
+ const usedImports = [];
1874
+ const usedHelpers = [];
1875
+ for (const index of moduleMembers) {
1876
+ for (const { name } of units[index].refs) {
1877
+ const statement = importByLocal.get(name);
1878
+ if (statement && !usedImports.includes(statement)) usedImports.push(statement);
1879
+ const helper = helperByName.get(name);
1880
+ if (helper && !usedHelpers.includes(helper)) usedHelpers.push(helper);
1881
+ }
1882
+ }
1883
+ for (const statement of usedImports) for (const local of importLocals(statement)) moduleNames.add(local);
1884
+ for (const helper of usedHelpers) for (const name of helperNames(helper)) moduleNames.add(name);
1885
+ const pageReferences = new Set(pageRefs.map((ref) => ref.name));
1886
+ for (const tag of body.mdastTags) pageReferences.add(tag);
1887
+ return {
1888
+ blocked,
1889
+ moved: [...moved].sort((a, b) => a - b),
1890
+ copied: [...kept].sort((a, b) => a - b),
1891
+ movedNames,
1892
+ moduleImports: usedImports,
1893
+ moduleHelpers: usedHelpers,
1894
+ registryNames: [...new Set(registryNames)],
1895
+ pageImportNames: [...new Set(importNames)],
1896
+ moduleNames,
1897
+ pageReferences
1898
+ };
1899
+ }
1900
+ return { blocked, moved: [], copied: [], movedNames: [], moduleImports: [], moduleHelpers: [], registryNames: [], pageImportNames: [], moduleNames: /* @__PURE__ */ new Set(), pageReferences: /* @__PURE__ */ new Set() };
1901
+ }
1902
+ function unboundTags(moduleSource, boundNames) {
1903
+ const sf = parse(moduleSource);
1904
+ const tags = /* @__PURE__ */ new Set();
1905
+ for (const statement of sf.statements) {
1906
+ if (ts.isImportDeclaration(statement)) continue;
1907
+ for (const ref of collectRefs(statement).refs) {
1908
+ if ((ref.kind === "tag" || ref.kind === "member") && /^[A-Z]/.test(ref.name) && !boundNames.has(ref.name)) tags.add(ref.name);
1909
+ }
1910
+ }
1911
+ return [...tags].sort();
1912
+ }
1913
+
1233
1914
  // src/components.ts
1234
1915
  function normalizeIndentedFences(content) {
1235
1916
  const lines = content.split("\n");
@@ -1276,23 +1957,23 @@ function normalizeIndentedFences(content) {
1276
1957
  }
1277
1958
  function staticServerHelper(path, imported, local) {
1278
1959
  if (imported === "default") return null;
1279
- const parsed = ts.createSourceFile(path, readFileSync(path, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
1960
+ const parsed = ts2.createSourceFile(path, readFileSync(path, "utf8"), ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
1280
1961
  const isLiteral = (node) => {
1281
- if (ts.isParenthesizedExpression(node)) return isLiteral(node.expression);
1282
- if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return true;
1283
- if ([ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.NullKeyword].includes(node.kind)) return true;
1284
- if (ts.isPrefixUnaryExpression(node)) return [ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(node.operator) && ts.isNumericLiteral(node.operand);
1285
- if (ts.isArrayLiteralExpression(node)) return node.elements.every((element) => ts.isExpression(element) && isLiteral(element));
1286
- if (ts.isObjectLiteralExpression(node)) return node.properties.every((property) => ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name)) && isLiteral(property.initializer));
1962
+ if (ts2.isParenthesizedExpression(node)) return isLiteral(node.expression);
1963
+ if (ts2.isStringLiteral(node) || ts2.isNoSubstitutionTemplateLiteral(node) || ts2.isNumericLiteral(node)) return true;
1964
+ if ([ts2.SyntaxKind.TrueKeyword, ts2.SyntaxKind.FalseKeyword, ts2.SyntaxKind.NullKeyword].includes(node.kind)) return true;
1965
+ if (ts2.isPrefixUnaryExpression(node)) return [ts2.SyntaxKind.PlusToken, ts2.SyntaxKind.MinusToken].includes(node.operator) && ts2.isNumericLiteral(node.operand);
1966
+ if (ts2.isArrayLiteralExpression(node)) return node.elements.every((element) => ts2.isExpression(element) && isLiteral(element));
1967
+ if (ts2.isObjectLiteralExpression(node)) return node.properties.every((property) => ts2.isPropertyAssignment(property) && (ts2.isIdentifier(property.name) || ts2.isStringLiteral(property.name) || ts2.isNumericLiteral(property.name)) && isLiteral(property.initializer));
1287
1968
  return false;
1288
1969
  };
1289
1970
  for (const statement of parsed.statements) {
1290
- if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue;
1971
+ if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
1291
1972
  for (const declaration of statement.declarationList.declarations) {
1292
- if (!ts.isIdentifier(declaration.name) || declaration.name.text !== imported || !declaration.initializer || !ts.isArrowFunction(declaration.initializer)) continue;
1973
+ if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== imported || !declaration.initializer || !ts2.isArrowFunction(declaration.initializer)) continue;
1293
1974
  const arrow = declaration.initializer;
1294
1975
  if (arrow.parameters.length > 0) continue;
1295
- const result = ts.isBlock(arrow.body) ? arrow.body.statements.length === 1 && ts.isReturnStatement(arrow.body.statements[0]) ? arrow.body.statements[0].expression : void 0 : arrow.body;
1976
+ const result = ts2.isBlock(arrow.body) ? arrow.body.statements.length === 1 && ts2.isReturnStatement(arrow.body.statements[0]) ? arrow.body.statements[0].expression : void 0 : arrow.body;
1296
1977
  if (result && isLiteral(result)) return `export const ${local} = () => (${result.getText(parsed)});`;
1297
1978
  }
1298
1979
  }
@@ -1516,12 +2197,12 @@ function wrapImpureCssModuleSelectors(css) {
1516
2197
  return root.toString();
1517
2198
  }
1518
2199
  function sourceFile(source, filename) {
1519
- return ts.createSourceFile(
2200
+ return ts2.createSourceFile(
1520
2201
  filename,
1521
2202
  source,
1522
- ts.ScriptTarget.Latest,
2203
+ ts2.ScriptTarget.Latest,
1523
2204
  true,
1524
- /\.[cm]?ts$/.test(filename) ? ts.ScriptKind.TS : ts.ScriptKind.TSX
2205
+ /\.[cm]?ts$/.test(filename) ? ts2.ScriptKind.TS : ts2.ScriptKind.TSX
1525
2206
  );
1526
2207
  }
1527
2208
  function walk(node, visitor) {
@@ -1532,24 +2213,24 @@ function implicitReactImports(source) {
1532
2213
  const bindings = /* @__PURE__ */ new Set();
1533
2214
  const references = /* @__PURE__ */ new Set();
1534
2215
  function bind(name) {
1535
- if (ts.isIdentifier(name)) bindings.add(name.text);
1536
- else for (const element of name.elements) if (ts.isBindingElement(element)) bind(element.name);
2216
+ if (ts2.isIdentifier(name)) bindings.add(name.text);
2217
+ else for (const element of name.elements) if (ts2.isBindingElement(element)) bind(element.name);
1537
2218
  }
1538
2219
  for (const statement of source.statements) {
1539
- if (ts.isImportDeclaration(statement) && statement.importClause) {
2220
+ if (ts2.isImportDeclaration(statement) && statement.importClause) {
1540
2221
  const clause = statement.importClause;
1541
2222
  if (clause.name) bindings.add(clause.name.text);
1542
2223
  if (clause.namedBindings) {
1543
- if (ts.isNamespaceImport(clause.namedBindings)) bindings.add(clause.namedBindings.name.text);
2224
+ if (ts2.isNamespaceImport(clause.namedBindings)) bindings.add(clause.namedBindings.name.text);
1544
2225
  else for (const element of clause.namedBindings.elements) bindings.add(element.name.text);
1545
2226
  }
1546
2227
  }
1547
- if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) bind(declaration.name);
1548
- if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) bindings.add(statement.name.text);
2228
+ if (ts2.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) bind(declaration.name);
2229
+ if ((ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement)) && statement.name) bindings.add(statement.name.text);
1549
2230
  }
1550
2231
  function visit(node) {
1551
- if (ts.isIdentifier(node) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) && !(ts.isPropertyAssignment(node.parent) && node.parent.name === node)) references.add(node.text);
1552
- ts.forEachChild(node, visit);
2232
+ if (ts2.isIdentifier(node) && !(ts2.isPropertyAccessExpression(node.parent) && node.parent.name === node) && !(ts2.isPropertyAssignment(node.parent) && node.parent.name === node)) references.add(node.text);
2233
+ ts2.forEachChild(node, visit);
1553
2234
  }
1554
2235
  visit(source);
1555
2236
  const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
@@ -1565,13 +2246,22 @@ function implicitReactImports(source) {
1565
2246
  usesMintlifyComponents ? mintlifyComponentsShim() : ""
1566
2247
  ].filter(Boolean).join("\n");
1567
2248
  }
2249
+ function resolveInlineBuiltinReferences(source, moduleNames) {
2250
+ const unresolved = unboundTags(source, moduleNames);
2251
+ if (unresolved.length === 0) return source;
2252
+ return [
2253
+ "import { builtinMdxComponents } from '@/components/mdx/builtin-components';",
2254
+ `const { ${unresolved.join(", ")} } = builtinMdxComponents;`,
2255
+ source
2256
+ ].join("\n");
2257
+ }
1568
2258
  function imports(statement) {
1569
- if (!ts.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) return [];
2259
+ if (!ts2.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) return [];
1570
2260
  const clause = statement.importClause;
1571
2261
  if (clause.isTypeOnly) return [];
1572
2262
  const source = statement.moduleSpecifier.text;
1573
2263
  const bindings = clause.name ? [{ local: clause.name.text, imported: "default", source }] : [];
1574
- if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
2264
+ if (clause.namedBindings && ts2.isNamedImports(clause.namedBindings)) {
1575
2265
  for (const element of clause.namedBindings.elements) {
1576
2266
  if (!element.isTypeOnly) bindings.push({ local: element.name.text, imported: element.propertyName?.text ?? element.name.text, source });
1577
2267
  }
@@ -1587,6 +2277,7 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
1587
2277
  "ParamField",
1588
2278
  "Expandable",
1589
2279
  "CodeGroup",
2280
+ "CodeBlock",
1590
2281
  "Badge",
1591
2282
  "Tooltip",
1592
2283
  "Tabs",
@@ -1596,6 +2287,7 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
1596
2287
  "InlineRequestExample",
1597
2288
  "InlineResponseExample",
1598
2289
  "Tree",
2290
+ "FileTree",
1599
2291
  "Folder",
1600
2292
  "File",
1601
2293
  "Mermaid",
@@ -1617,8 +2309,8 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
1617
2309
  function isConfirmedClientBoundaryTag(name) {
1618
2310
  return EXTRACTED_CLIENT_COMPONENT_TAG.test(name) || CLIENT_BUILTIN_COMPONENT_TAGS.has(name);
1619
2311
  }
1620
- function functionValuedAttribute(node, declaredNames) {
1621
- const isFunctionValue = (value) => declaredNames.has(value.trim()) || isFunctionInitializer(value.trim(), 0);
2312
+ function functionValuedAttribute(node, declaredNames2) {
2313
+ const isFunctionValue = (value) => declaredNames2.has(value.trim()) || isFunctionInitializer(value.trim(), 0);
1622
2314
  for (const attribute of node.attributes ?? []) {
1623
2315
  const raw = attribute.value;
1624
2316
  const value = raw && typeof raw === "object" ? raw.value : void 0;
@@ -1631,21 +2323,21 @@ function functionValuedAttribute(node, declaredNames) {
1631
2323
  }
1632
2324
  return void 0;
1633
2325
  }
1634
- function propsTargetExtractedClientComponent(body, declaredNames) {
2326
+ function propsTargetExtractedClientComponent(body, declaredNames2) {
1635
2327
  const tree = parser.parse(body);
1636
2328
  let found = false;
1637
2329
  walk(tree, (node) => {
1638
2330
  if (found || !node.name || !isConfirmedClientBoundaryTag(node.name)) return;
1639
- if (functionValuedAttribute(node, declaredNames) !== void 0) found = true;
2331
+ if (functionValuedAttribute(node, declaredNames2) !== void 0) found = true;
1640
2332
  });
1641
2333
  return found;
1642
2334
  }
1643
- function hasAnyFunctionValuedProp(body, declaredNames) {
2335
+ function hasAnyFunctionValuedProp(body, declaredNames2) {
1644
2336
  const tree = parser.parse(body);
1645
2337
  let found = false;
1646
2338
  walk(tree, (node) => {
1647
2339
  if (found || !node.name) return;
1648
- if (functionValuedAttribute(node, declaredNames) !== void 0) found = true;
2340
+ if (functionValuedAttribute(node, declaredNames2) !== void 0) found = true;
1649
2341
  });
1650
2342
  return found;
1651
2343
  }
@@ -1653,17 +2345,30 @@ function declarationsReferenceBrowserGlobal(body) {
1653
2345
  const tree = parser.parse(body);
1654
2346
  let found = false;
1655
2347
  function inspect(node) {
1656
- if (ts.isIdentifier(node) && (node.text === "document" || node.text === "window")) found = true;
1657
- ts.forEachChild(node, inspect);
2348
+ if (ts2.isIdentifier(node) && (node.text === "document" || node.text === "window")) found = true;
2349
+ ts2.forEachChild(node, inspect);
1658
2350
  }
1659
2351
  for (const node of tree.children ?? []) {
1660
2352
  if (found || node.type !== "mdxjsEsm" || node.value === void 0) continue;
1661
2353
  for (const statement of sourceFile(node.value, "inline.tsx").statements) {
1662
- if (ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement)) inspect(statement);
2354
+ if (ts2.isVariableStatement(statement) || ts2.isFunctionDeclaration(statement)) inspect(statement);
1663
2355
  }
1664
2356
  }
1665
2357
  return found;
1666
2358
  }
2359
+ function neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn) {
2360
+ const importPattern = /^import\s+(?:\{\s*(?:default\s+as\s+)?([A-Z][A-Za-z0-9_]*)\s*\}|([A-Z][A-Za-z0-9_]*))\s+from\s+(['"])((?:@site\/|\.\.?\/|\/)[^'"]+)\3\s*;?[ \t]*$/gm;
2361
+ const dropped = [];
2362
+ let result = content.replace(importPattern, (_match, namedComponent, defaultComponent, _quote, specifier) => {
2363
+ dropped.push({ name: namedComponent ?? defaultComponent, specifier });
2364
+ return "";
2365
+ });
2366
+ for (const { name, specifier } of dropped) {
2367
+ warn(`Component import ${JSON.stringify(specifier)} could not be resolved because this page has invalid MDX (see the previous warning), so the import and its usage were removed to keep the site building.`, currentFile);
2368
+ result = result.replace(new RegExp(`<${name}(?:\\s[^>]*)?/>`, "g"), `{/* Removed <${name}>: unresolved during fallback migration */}`).replace(new RegExp(`<${name}(?:\\s[^>]*)?>[\\s\\S]*?<\\/${name}>`, "g"), `{/* Removed <${name}>: unresolved during fallback migration */}`);
2369
+ }
2370
+ return result;
2371
+ }
1667
2372
  function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIdentity) {
1668
2373
  const root = resolve2(siteRoot);
1669
2374
  const confined = resolve2(confinementRoot);
@@ -1750,14 +2455,14 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1750
2455
  return;
1751
2456
  }
1752
2457
  const text = content.toString("utf8");
1753
- const syntax = ts.transpileModule(text, {
2458
+ const syntax = ts2.transpileModule(text, {
1754
2459
  fileName: path,
1755
2460
  reportDiagnostics: true,
1756
- compilerOptions: { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext }
2461
+ compilerOptions: { jsx: ts2.JsxEmit.Preserve, target: ts2.ScriptTarget.ESNext, module: ts2.ModuleKind.ESNext }
1757
2462
  });
1758
- if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
2463
+ if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
1759
2464
  const ast = sourceFile(text, path);
1760
- if (ast.statements.some((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression) && statement.expression.text === "use server")) throw new Error("server-only component modules require manual migration");
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");
1761
2466
  const edits = [];
1762
2467
  function dependency(literal, importDeclaration) {
1763
2468
  const specifier = literal.text;
@@ -1767,10 +2472,10 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1767
2472
  const localName = importDeclaration?.importClause?.name?.text;
1768
2473
  if (localName) {
1769
2474
  let renameToProp2 = function(node) {
1770
- if (ts.isJsxAttribute(node) && node.name.getText(ast) === "to" && ts.isJsxOpeningLikeElement(node.parent.parent) && node.parent.parent.tagName.getText(ast) === localName) {
2475
+ if (ts2.isJsxAttribute(node) && node.name.getText(ast) === "to" && ts2.isJsxOpeningLikeElement(node.parent.parent) && node.parent.parent.tagName.getText(ast) === localName) {
1771
2476
  edits.push({ start: node.name.getStart(ast), end: node.name.end, value: "href" });
1772
2477
  }
1773
- ts.forEachChild(node, renameToProp2);
2478
+ ts2.forEachChild(node, renameToProp2);
1774
2479
  };
1775
2480
  var renameToProp = renameToProp2;
1776
2481
  renameToProp2(ast);
@@ -1793,15 +2498,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1793
2498
  edits.push({ start: literal.getStart(ast), end: literal.end, value: JSON.stringify(portableSpecifier(nextPath.startsWith(".") ? nextPath : `./${nextPath}`)) });
1794
2499
  }
1795
2500
  function inspect(node) {
1796
- if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
1797
- dependency(node.moduleSpecifier, ts.isImportDeclaration(node) ? node : void 0);
2501
+ if ((ts2.isImportDeclaration(node) || ts2.isExportDeclaration(node)) && node.moduleSpecifier && ts2.isStringLiteral(node.moduleSpecifier)) {
2502
+ dependency(node.moduleSpecifier, ts2.isImportDeclaration(node) ? node : void 0);
1798
2503
  }
1799
- if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require")) {
2504
+ if (ts2.isCallExpression(node) && (node.expression.kind === ts2.SyntaxKind.ImportKeyword || ts2.isIdentifier(node.expression) && node.expression.text === "require")) {
1800
2505
  const argument = node.arguments[0];
1801
- if (node.arguments.length !== 1 || !argument || !ts.isStringLiteralLike(argument)) throw new Error("computed component imports require manual migration");
2506
+ if (node.arguments.length !== 1 || !argument || !ts2.isStringLiteralLike(argument)) throw new Error("computed component imports require manual migration");
1802
2507
  dependency(argument);
1803
2508
  }
1804
- ts.forEachChild(node, inspect);
2509
+ ts2.forEachChild(node, inspect);
1805
2510
  }
1806
2511
  inspect(ast);
1807
2512
  const replaced = applyReplacements(text, edits);
@@ -1858,16 +2563,17 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1858
2563
  function transform(raw, currentFile) {
1859
2564
  const parsedFrontmatter = parseFrontmatter(raw).content;
1860
2565
  const frontmatter = raw.slice(0, raw.length - parsedFrontmatter.length);
1861
- const content = normalizeIndentedFences(normalizeHtmlComments(parsedFrontmatter));
2566
+ const content = normalizeExplicitHeadingIds(normalizeIndentedFences(normalizeHtmlComments(parsedFrontmatter)));
1862
2567
  let tree;
1863
2568
  try {
1864
2569
  tree = parser.parse(content);
1865
2570
  } catch {
1866
2571
  warn("Custom component analysis could not parse this MDX; its source was preserved for manual migration.", currentFile);
1867
- return raw;
2572
+ return frontmatter + neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn);
1868
2573
  }
1869
2574
  const aliases = /* @__PURE__ */ new Map();
1870
2575
  const unsupportedImports = /* @__PURE__ */ new Map();
2576
+ const droppedMdxDataBindings = /* @__PURE__ */ new Set();
1871
2577
  const edits = [];
1872
2578
  const declarations = [];
1873
2579
  const moduleImports = [];
@@ -1878,15 +2584,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1878
2584
  function hasExpressionReference(names, options = {}) {
1879
2585
  let found = false;
1880
2586
  function inspect2(node) {
1881
- if (ts.isIdentifier(node) && names.has(node.text)) found = true;
1882
- ts.forEachChild(node, inspect2);
2587
+ if (ts2.isIdentifier(node) && names.has(node.text)) found = true;
2588
+ ts2.forEachChild(node, inspect2);
1883
2589
  }
1884
2590
  walk(tree, (node) => {
1885
2591
  if (options.excludedNodes?.some((excluded) => node.position?.start.offset !== void 0 && node.position.end.offset !== void 0 && node.position.start.offset >= excluded.position.start.offset && node.position.end.offset <= excluded.position.end.offset)) return;
1886
2592
  if (node.name && [...names].some((name) => node.name.startsWith(`${name}.`) || options.includeDirectTags && node.name === name)) found = true;
1887
2593
  if (options.includeDeclarations !== false && node.type === "mdxjsEsm" && node.value) {
1888
2594
  for (const statement of sourceFile(node.value, "expression.tsx").statements) {
1889
- if (!ts.isImportDeclaration(statement)) inspect2(statement);
2595
+ if (!ts2.isImportDeclaration(statement)) inspect2(statement);
1890
2596
  }
1891
2597
  }
1892
2598
  const expressions = [
@@ -1915,13 +2621,13 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1915
2621
  if (node.value === void 0 || node.position?.start.offset === void 0) continue;
1916
2622
  const ast = sourceFile(node.value, "inline.tsx");
1917
2623
  for (const statement of ast.statements) {
1918
- if (!ts.isImportDeclaration(statement)) {
2624
+ if (!ts2.isImportDeclaration(statement)) {
1919
2625
  let inspect2 = function(node2) {
1920
- if (ts.isCallExpression(node2) && (node2.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node2.expression) && node2.expression.text === "require")) unsupportedDependency = true;
1921
- ts.forEachChild(node2, inspect2);
2626
+ if (ts2.isCallExpression(node2) && (node2.expression.kind === ts2.SyntaxKind.ImportKeyword || ts2.isIdentifier(node2.expression) && node2.expression.text === "require")) unsupportedDependency = true;
2627
+ ts2.forEachChild(node2, inspect2);
1922
2628
  };
1923
2629
  var inspect = inspect2;
1924
- let unsupportedDependency = ts.isExportDeclaration(statement) && !!statement.moduleSpecifier;
2630
+ let unsupportedDependency = ts2.isExportDeclaration(statement) && !!statement.moduleSpecifier;
1925
2631
  inspect2(statement);
1926
2632
  if (unsupportedDependency) {
1927
2633
  hasUnsupportedImports = true;
@@ -1935,7 +2641,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1935
2641
  continue;
1936
2642
  }
1937
2643
  const bindings = imports(statement);
1938
- const rawSpecifier = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
2644
+ const rawSpecifier = ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
1939
2645
  const specifier = rawSpecifier.startsWith("@site/") ? `/${rawSpecifier.slice("@site/".length)}` : rawSpecifier;
1940
2646
  if (isScaffoldProvidedImport(specifier)) {
1941
2647
  if (specifier === "react" && bindings.length > 0 && bindings.every((binding) => REACT_GLOBALS.has(binding.imported)) && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
@@ -1967,21 +2673,38 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
1967
2673
  }
1968
2674
  continue;
1969
2675
  }
1970
- if (/\.mdx?$/.test(specifier)) continue;
2676
+ if (/\.mdx?$/.test(specifier)) {
2677
+ if (bindings.length > 0 && bindings.every((binding) => !/^[A-Z]/.test(binding.local))) {
2678
+ warn(`MDX named import of ${JSON.stringify(rawSpecifier)} isn't a component and has no equivalent in the migrated site (for example Docusaurus's generated 'toc' export), so it was removed, along with any '...${bindings.map((binding) => binding.local).join(", '...")}' spread of it.`, currentFile);
2679
+ edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
2680
+ for (const binding of bindings) droppedMdxDataBindings.add(binding.local);
2681
+ }
2682
+ continue;
2683
+ }
1971
2684
  if (bindings.length && RESCUABLE_ASSET_EXTENSIONS.has(extname(specifier).toLowerCase()) && !bindings.some((binding) => usedAsJsxTagName(binding.local))) {
1972
2685
  try {
1973
2686
  const href = copyPublicAsset(resolveDependency(specifier, currentFile));
1974
2687
  edits.push({
1975
2688
  start: node.position.start.offset + statement.getStart(ast),
1976
2689
  end: node.position.start.offset + statement.end,
1977
- value: bindings.map((binding) => `const ${binding.local} = ${JSON.stringify(href)};`).join("\n")
2690
+ // `export`, not a bare `const`: this replaces an `import`
2691
+ // statement in place, and the final MDX compile (a fresh
2692
+ // parse of this text, not a reuse of this pass's own AST)
2693
+ // only recognizes a top-level JS block as ESM — evaluated and
2694
+ // its bindings put in scope — when it starts with `import` or
2695
+ // `export`. A bare `const` there is just prose to that parser
2696
+ // and renders as literal paragraph text instead, leaving
2697
+ // every reference to this binding a `ReferenceError` at
2698
+ // render (reproduced against hasura/graphql-engine's
2699
+ // databases/overview.mdx, which binds asset URLs this way).
2700
+ value: bindings.map((binding) => `export const ${binding.local} = ${JSON.stringify(href)};`).join("\n")
1978
2701
  });
1979
2702
  warn(`Asset import ${JSON.stringify(rawSpecifier)} was copied to ${href} and bound to that URL instead of importing it as a component.`, currentFile);
1980
2703
  continue;
1981
2704
  } catch {
1982
2705
  }
1983
2706
  }
1984
- if (!bindings.length || statement.importClause?.namedBindings && ts.isNamespaceImport(statement.importClause.namedBindings)) {
2707
+ if (!bindings.length || statement.importClause?.namedBindings && ts2.isNamespaceImport(statement.importClause.namedBindings)) {
1985
2708
  warn("Namespace or side-effect MDX imports require manual registration; the import was preserved.", currentFile);
1986
2709
  hasUnsupportedImports = true;
1987
2710
  continue;
@@ -2042,7 +2765,9 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2042
2765
  edits.push({
2043
2766
  start: importStart,
2044
2767
  end: importEnd,
2045
- value: bindings.map((binding) => `const ${binding.local} = ${JSON.stringify(rescuedAssetHref)};`).join("\n")
2768
+ // See the identical `export const` note on the other asset
2769
+ // rescue above — same reason.
2770
+ value: bindings.map((binding) => `export const ${binding.local} = ${JSON.stringify(rescuedAssetHref)};`).join("\n")
2046
2771
  });
2047
2772
  warn(`Asset import ${JSON.stringify(rawSpecifier)} could not be copied as a component; it was copied to ${rescuedAssetHref} and bound to that URL instead.`, currentFile);
2048
2773
  } else {
@@ -2052,6 +2777,77 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2052
2777
  }
2053
2778
  }
2054
2779
  }
2780
+ const refusedInlineComponents = /* @__PURE__ */ new Set();
2781
+ {
2782
+ const plan = planInlineExtraction({
2783
+ declarations,
2784
+ tree,
2785
+ copyableImports: [...moduleImports, ...realPageImports],
2786
+ helperDeclarations: serverPageDeclarations,
2787
+ unavailableImports: unsupportedImports,
2788
+ reactGlobals: REACT_GLOBALS
2789
+ });
2790
+ for (const { name, reason } of plan.blocked) {
2791
+ refusedInlineComponents.add(name);
2792
+ 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);
2793
+ }
2794
+ const moduleSource = plan.moved.length === 0 ? "" : [
2795
+ ...plan.moduleImports,
2796
+ ...plan.moduleHelpers,
2797
+ ...[...plan.moved, ...plan.copied].sort((a, b) => a - b).map((index) => declarations[index].source)
2798
+ ].join("\n\n");
2799
+ if (plan.moved.length > 0 && (copied.size >= MAX_COMPONENT_FILES || Buffer.byteLength(moduleSource) > MAX_FILE_BYTES || copiedBytes + Buffer.byteLength(moduleSource) > MAX_COMPONENT_BYTES)) {
2800
+ warn(`Inline component "${plan.movedNames[0]}" exceeded the component migration budget; source was preserved.`, currentFile);
2801
+ } else if (plan.moved.length > 0) {
2802
+ const path = `${destinationRoot}/inline-${hash(`${relative2(root, currentFile)}:components`)}.jsx`;
2803
+ const inlineSource = `${resolveInlineBuiltinReferences(moduleSource, plan.moduleNames)}
2804
+ `;
2805
+ copied.set(path, { path, content: `'use client';
2806
+ ${implicitReactImports(sourceFile(inlineSource, "inline.jsx"))}
2807
+ ${inlineSource}` });
2808
+ copiedBytes += Buffer.byteLength(inlineSource);
2809
+ const movedIndexes = new Set(plan.moved);
2810
+ for (const index of plan.moved) edits.push({ start: declarations[index].start, end: declarations[index].end, value: "" });
2811
+ for (const name of plan.registryNames) {
2812
+ const registered = register(path, name);
2813
+ aliases.set(name, registered);
2814
+ moduleImports.push(`import { ${name} } from ${JSON.stringify(portableSpecifier(`./${relative2(destinationRoot, path).replace(/\\/g, "/")}`))};`);
2815
+ }
2816
+ if (plan.pageImportNames.length > 0) {
2817
+ realPageImports.push(`import { ${plan.pageImportNames.join(", ")} } from ${JSON.stringify(portableSpecifier(`@/${path.replace(/^src\//, "").replace(/\\/g, "/")}`))};`);
2818
+ }
2819
+ for (const entry of [...sharedImportEdits]) {
2820
+ const locals = [...sourceFile(content.slice(entry.start, entry.end), "shared.tsx").statements].flatMap((statement) => ts2.isImportDeclaration(statement) ? imports(statement).map((binding) => binding.local) : []);
2821
+ if (locals.length === 0 || locals.some((local) => plan.pageReferences.has(local))) continue;
2822
+ edits.push(entry);
2823
+ sharedImportEdits.splice(sharedImportEdits.indexOf(entry), 1);
2824
+ }
2825
+ for (let index = declarations.length - 1; index >= 0; index -= 1) {
2826
+ if (movedIndexes.has(index)) declarations.splice(index, 1);
2827
+ }
2828
+ }
2829
+ }
2830
+ if (droppedMdxDataBindings.size > 0) {
2831
+ for (const node of esmNodes) {
2832
+ let stripDroppedSpreads2 = function(inner) {
2833
+ if (ts2.isSpreadElement(inner) && ts2.isIdentifier(inner.expression) && droppedMdxDataBindings.has(inner.expression.text)) {
2834
+ const start = nodeStart + inner.getStart(ast);
2835
+ let end = nodeStart + inner.end;
2836
+ const following = nodeText.slice(inner.end).match(/^\s*,/);
2837
+ if (following) end += following[0].length;
2838
+ edits.push({ start, end, value: "" });
2839
+ return;
2840
+ }
2841
+ ts2.forEachChild(inner, stripDroppedSpreads2);
2842
+ };
2843
+ var stripDroppedSpreads = stripDroppedSpreads2;
2844
+ if (node.value === void 0 || node.position?.start.offset === void 0) continue;
2845
+ const nodeStart = node.position.start.offset;
2846
+ const nodeText = node.value;
2847
+ const ast = sourceFile(nodeText, "inline.tsx");
2848
+ for (const statement of ast.statements) stripDroppedSpreads2(statement);
2849
+ }
2850
+ }
2055
2851
  walk(tree, (node) => {
2056
2852
  for (const attribute of node.attributes ?? []) {
2057
2853
  if (attribute.type !== "mdxJsxAttribute" || !attribute.value || typeof attribute.value !== "object") continue;
@@ -2076,7 +2872,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2076
2872
  }
2077
2873
  });
2078
2874
  const statefulDeclarationNames = new Set(
2079
- declarations.filter(({ source }) => /\bon[A-Z]\w*\s*=|\buse[A-Z]\w*\s*\(/.test(source)).map(({ source }) => source.match(/^export const (\w+)/)?.[1]).filter((name) => !!name)
2875
+ declarations.filter(({ source }) => /\bon[A-Z]\w*\s*=|\buse[A-Z]\w*\s*\(/.test(source)).map(({ source }) => source.match(/^export const (\w+)/)?.[1]).filter((name) => !!name && !refusedInlineComponents.has(name))
2080
2876
  );
2081
2877
  const extracted = [];
2082
2878
  for (const candidate of tree.children ?? []) {
@@ -2095,12 +2891,12 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2095
2891
  continue;
2096
2892
  }
2097
2893
  const jsx = content.slice(node.position.start.offset, node.position.end.offset);
2098
- const result = ts.transpileModule(`export default function Inline() { return (${jsx}); }`, {
2894
+ const result = ts2.transpileModule(`export default function Inline() { return (${jsx}); }`, {
2099
2895
  fileName: "inline.jsx",
2100
- compilerOptions: { jsx: ts.JsxEmit.Preserve },
2896
+ compilerOptions: { jsx: ts2.JsxEmit.Preserve },
2101
2897
  reportDiagnostics: true
2102
2898
  });
2103
- if (result.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) {
2899
+ if (result.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) {
2104
2900
  warn("Interactive MDX could not be safely extracted as JSX; source was preserved.", currentFile);
2105
2901
  continue;
2106
2902
  }
@@ -2108,15 +2904,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2108
2904
  }
2109
2905
  if (extracted.length && declarations.length) {
2110
2906
  let bind2 = function(name) {
2111
- if (ts.isIdentifier(name)) declared.add(name.text);
2112
- else for (const element of name.elements) if (ts.isBindingElement(element)) bind2(element.name);
2907
+ if (ts2.isIdentifier(name)) declared.add(name.text);
2908
+ else for (const element of name.elements) if (ts2.isBindingElement(element)) bind2(element.name);
2113
2909
  };
2114
2910
  var bind = bind2;
2115
2911
  const declared = /* @__PURE__ */ new Set();
2116
2912
  for (const declaration of declarations) {
2117
2913
  for (const statement of sourceFile(declaration.source, "declaration.tsx").statements) {
2118
- if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) declared.add(statement.name.text);
2119
- if (ts.isVariableStatement(statement)) for (const variable of statement.declarationList.declarations) bind2(variable.name);
2914
+ if ((ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement)) && statement.name) declared.add(statement.name.text);
2915
+ if (ts2.isVariableStatement(statement)) for (const variable of statement.declarationList.declarations) bind2(variable.name);
2120
2916
  }
2121
2917
  }
2122
2918
  const hasSharedDeclarations = hasExpressionReference(declared, {
@@ -2277,22 +3073,22 @@ function mergeComponentRegistry(existing, incoming) {
2277
3073
  const name = `MigratedRegistry${fingerprint}`;
2278
3074
  const registryPath = `./migrated-components-${fingerprint}`;
2279
3075
  const source = sourceFile(existing, "custom-components.tsx");
2280
- const alreadyImported = source.statements.some((statement) => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
3076
+ const alreadyImported = source.statements.some((statement) => ts2.isImportDeclaration(statement) && ts2.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
2281
3077
  let initializer;
2282
3078
  for (const statement of source.statements) {
2283
- if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue;
3079
+ if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
2284
3080
  for (const declaration of statement.declarationList.declarations) {
2285
- if (ts.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
3081
+ if (ts2.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
2286
3082
  }
2287
3083
  }
2288
- while (initializer && (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer) || ts.isParenthesizedExpression(initializer))) initializer = initializer.expression;
2289
- if (!initializer || !ts.isObjectLiteralExpression(initializer)) {
3084
+ while (initializer && (ts2.isAsExpression(initializer) || ts2.isSatisfiesExpression(initializer) || ts2.isParenthesizedExpression(initializer))) initializer = initializer.expression;
3085
+ if (!initializer || !ts2.isObjectLiteralExpression(initializer)) {
2290
3086
  throw new Error("Cannot safely merge src/mdx/custom-components.tsx: export customComponents as an object literal before importing components. The existing registry was preserved.");
2291
3087
  }
2292
3088
  const content = alreadyImported ? existing : applyReplacements(existing, [
2293
3089
  {
2294
- start: source.statements.filter((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
2295
- end: source.statements.filter((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
3090
+ start: source.statements.filter((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
3091
+ end: source.statements.filter((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
2296
3092
  value: `
2297
3093
  import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
2298
3094
  `
@@ -2315,7 +3111,7 @@ import { parse as parseYaml2 } from "yaml";
2315
3111
  // src/navigation.ts
2316
3112
  import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
2317
3113
  import { dirname as dirname2, extname as extname2, relative as relative3 } from "path";
2318
- var MAX_MINTLIFY_CONFIG_BYTES = 2e6;
3114
+ var MAX_MINTLIFY_CONFIG_BYTES = 2e7;
2319
3115
  function addMintlifyDirectoryRedirects(config, pages) {
2320
3116
  const pageIds = new Set(pages.map((page) => page.id));
2321
3117
  const redirects = [...config.redirects ?? []];
@@ -2363,15 +3159,10 @@ function addMintlifyDirectoryRedirects(config, pages) {
2363
3159
  }
2364
3160
  }
2365
3161
  }
2366
- for (const redirect of config.redirects ?? []) {
2367
- const destination = redirect.destination.split(/[?#]/, 1)[0].replace(/\/:[A-Za-z_][A-Za-z0-9_]*\*\/?$/, "");
2368
- if (!destination.startsWith("/") || destination.startsWith("//") || /[:*()[\]{}]/.test(destination)) continue;
2369
- const directory = trimEdgeSlashes(destination);
2370
- if (!directory || pageIds.has(directory)) continue;
3162
+ for (const [directory, landing] of firstDescendantByDirectory) {
3163
+ if (pageIds.has(directory)) continue;
2371
3164
  const source = `/${directory}`;
2372
3165
  if (hasAuthoredRedirect(source)) continue;
2373
- const landing = firstDescendantByDirectory.get(directory);
2374
- if (!landing) continue;
2375
3166
  redirects.push({ source, destination: `/${landing.id}`, permanent: false });
2376
3167
  redirectSources.add(source);
2377
3168
  }
@@ -2422,6 +3213,18 @@ var LANGUAGE_LABELS = {
2422
3213
  function objectValue(value) {
2423
3214
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2424
3215
  }
3216
+ function withFlattenedProductGroups(container) {
3217
+ if (!Array.isArray(container.productGroups)) return container;
3218
+ const flattened = container.productGroups.flatMap((entry) => {
3219
+ const group = objectValue(entry);
3220
+ return group && Array.isArray(group.products) ? group.products : [];
3221
+ });
3222
+ if (flattened.length === 0) return container;
3223
+ return {
3224
+ ...container,
3225
+ products: [...Array.isArray(container.products) ? container.products : [], ...flattened]
3226
+ };
3227
+ }
2425
3228
  function projectedHref(value) {
2426
3229
  if (typeof value !== "string") return void 0;
2427
3230
  const href = value.trim();
@@ -2509,12 +3312,23 @@ function mintlifyNavigationApiReferences(config) {
2509
3312
  const references = [];
2510
3313
  const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus", "languages"];
2511
3314
  const nestedKeys = ["groups", "pages"];
2512
- function visit(node, tabLabel) {
2513
- if (typeof node.openapi === "string" && node.openapi.trim()) {
2514
- references.push({ value: node.openapi.trim(), kind: "openapi", tabLabel });
2515
- }
2516
- if (typeof node.asyncapi === "string" && node.asyncapi.trim()) {
2517
- references.push({ value: node.asyncapi.trim(), kind: "asyncapi", tabLabel });
3315
+ function visit(rawNode, tabLabel) {
3316
+ const node = withFlattenedProductGroups(rawNode);
3317
+ for (const kind of ["openapi", "asyncapi"]) {
3318
+ const value = node[kind];
3319
+ if (typeof value === "string" && value.trim()) {
3320
+ references.push({ value: value.trim(), kind, tabLabel });
3321
+ continue;
3322
+ }
3323
+ const object = objectValue(value);
3324
+ if (object && typeof object.source === "string" && object.source.trim()) {
3325
+ references.push({
3326
+ value: object.source.trim(),
3327
+ kind,
3328
+ tabLabel,
3329
+ ...typeof object.directory === "string" && object.directory.trim() ? { directory: object.directory.trim() } : {}
3330
+ });
3331
+ }
2518
3332
  }
2519
3333
  for (const key of containerKeys) {
2520
3334
  const entries = node[key];
@@ -2575,8 +3389,19 @@ function registerReference(value, context) {
2575
3389
  }
2576
3390
  return navigationId;
2577
3391
  }
3392
+ var OPENAPI_OPERATION_REF = /^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+\S/;
2578
3393
  function convertPage(value, context) {
2579
- if (typeof value === "string") return registerReference(value, context);
3394
+ if (typeof value === "string") {
3395
+ if (OPENAPI_OPERATION_REF.test(value)) {
3396
+ warnOnce(
3397
+ context,
3398
+ "openapi-operation-list",
3399
+ 'One or more navigation groups hand-pick or reorder individual OpenAPI operations (e.g. "GET /users"); Thally always renders every operation of a bound spec and has no manual-selection equivalent, so that list was dropped. The operations themselves are still available through the bound API reference.'
3400
+ );
3401
+ return null;
3402
+ }
3403
+ return registerReference(value, context);
3404
+ }
2580
3405
  const object = objectValue(value);
2581
3406
  if (!object) return null;
2582
3407
  if (typeof object.page === "string") return registerReference(object.page, context);
@@ -2619,8 +3444,9 @@ function convertNavigationValues(values, context) {
2619
3444
  });
2620
3445
  }
2621
3446
  function convertContainerToTabs(containerValue, context, fallbackTab, trace, depth = 0) {
2622
- const container = objectValue(containerValue);
2623
- if (!container) return [];
3447
+ const rawContainer = objectValue(containerValue);
3448
+ if (!rawContainer) return [];
3449
+ const container = withFlattenedProductGroups(rawContainer);
2624
3450
  const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
2625
3451
  for (const key of containerKeys) {
2626
3452
  if (!Array.isArray(container[key])) continue;
@@ -2842,6 +3668,47 @@ function isRedirectPathSafe(rawSource, rawDestination) {
2842
3668
  }
2843
3669
  return true;
2844
3670
  }
3671
+ function mintlifyDefaultVersionPrefixes(config) {
3672
+ const navigation = objectValue(config.navigation) ?? config;
3673
+ const prefixes = /* @__PURE__ */ new Set();
3674
+ function collect(container) {
3675
+ if (Array.isArray(container.versions)) {
3676
+ const entries = container.versions.map((value) => objectValue(value)).filter((value) => value !== null);
3677
+ const defaultEntry = entries.find((entry) => entry.default === true) ?? entries[0];
3678
+ if (defaultEntry && typeof defaultEntry.version === "string" && defaultEntry.version.trim()) {
3679
+ prefixes.add(defaultEntry.version.trim());
3680
+ }
3681
+ }
3682
+ if (Array.isArray(container.languages)) {
3683
+ for (const value of container.languages) {
3684
+ const language = objectValue(value);
3685
+ if (language) collect(language);
3686
+ }
3687
+ }
3688
+ }
3689
+ collect(navigation);
3690
+ return prefixes;
3691
+ }
3692
+ function mintlifyAllVersionPrefixes(config) {
3693
+ const navigation = objectValue(config.navigation) ?? config;
3694
+ const prefixes = /* @__PURE__ */ new Set();
3695
+ function collect(container) {
3696
+ if (Array.isArray(container.versions)) {
3697
+ for (const value of container.versions) {
3698
+ const entry = objectValue(value);
3699
+ if (entry && typeof entry.version === "string" && entry.version.trim()) prefixes.add(entry.version.trim());
3700
+ }
3701
+ }
3702
+ if (Array.isArray(container.languages)) {
3703
+ for (const value of container.languages) {
3704
+ const language = objectValue(value);
3705
+ if (language) collect(language);
3706
+ }
3707
+ }
3708
+ }
3709
+ collect(navigation);
3710
+ return prefixes;
3711
+ }
2845
3712
  function projectMintlifyNavigation(config, options = {}) {
2846
3713
  const warnings = [];
2847
3714
  const references = [];
@@ -3087,7 +3954,7 @@ function pruneMissingNavigationPages(config, availableIds) {
3087
3954
  }
3088
3955
 
3089
3956
  // src/docusaurus.ts
3090
- var MAX_CONFIG_BYTES = 1e6;
3957
+ var MAX_CONFIG_BYTES = 2e7;
3091
3958
  var CATEGORY_FILENAMES = ["_category_.json", "_category_.yml", "_category_.yaml"];
3092
3959
  var SIDEBAR_FILENAMES = [
3093
3960
  "sidebars.json",
@@ -3284,7 +4151,8 @@ function rewriteDocusaurusLinks(body, current, descriptors, options = {}) {
3284
4151
  return options.assetPaths?.has(path.slice(1)) ? target : null;
3285
4152
  }
3286
4153
  function rewriteTarget(target) {
3287
- if (!target || target.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
4154
+ if (!target || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(target)) return target;
4155
+ if (target.startsWith("#")) return target;
3288
4156
  const suffixIndex = target.search(/[?#]/);
3289
4157
  const path = suffixIndex >= 0 ? target.slice(0, suffixIndex) : target;
3290
4158
  const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
@@ -3338,7 +4206,7 @@ function readDocusaurusSiteOrigin(repositoryRoot) {
3338
4206
  }
3339
4207
  function readBoundedText(path) {
3340
4208
  if (lstatSync3(path).size > MAX_CONFIG_BYTES) {
3341
- throw new Error("Docusaurus sidebar config exceeded the 1 MB static-parser limit.");
4209
+ throw new Error("The Docusaurus sidebar config is larger than 20 MB and could not be imported.");
3342
4210
  }
3343
4211
  return readFileSync3(path, "utf8");
3344
4212
  }
@@ -3970,7 +4838,7 @@ import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as rea
3970
4838
  import { basename as basename2, dirname as dirname3, extname as extname4, relative as relative4 } from "path";
3971
4839
  import { load } from "cheerio";
3972
4840
  import { parse as parseYaml3 } from "yaml";
3973
- var MAX_CONFIG_BYTES2 = 2e6;
4841
+ var MAX_CONFIG_BYTES2 = 2e7;
3974
4842
  function announcementMarkdown(message) {
3975
4843
  const document = load(message);
3976
4844
  document("script, style, iframe, object, embed, svg, math").remove();
@@ -4005,7 +4873,7 @@ function titleCase3(value) {
4005
4873
  }
4006
4874
  function readBoundedYaml(path) {
4007
4875
  if (lstatSync4(path).size > MAX_CONFIG_BYTES2) {
4008
- throw new Error("Fern config exceeded the 2 MB static-parser limit.");
4876
+ throw new Error("The Fern config is larger than 20 MB and could not be imported.");
4009
4877
  }
4010
4878
  return parseYaml3(readFileSync4(path, "utf8"));
4011
4879
  }
@@ -4067,7 +4935,7 @@ function registerPageAt(rawPath, base, context, navTitle, hidden = false) {
4067
4935
  let sourcePath;
4068
4936
  try {
4069
4937
  const absolute = resolveWithinRoot(
4070
- context.pathPrefix ? resolveWithin(context.fernRoot, context.pathPrefix) : context.fernRoot,
4938
+ context.pathPrefix ? resolveWithinRoot(context.fernRoot, context.pathPrefix, context.repositoryRoot) : context.fernRoot,
4071
4939
  rawPath.trim(),
4072
4940
  context.repositoryRoot
4073
4941
  );
@@ -4096,7 +4964,7 @@ function registerFolder(object, parentSegments, context) {
4096
4964
  let folder;
4097
4965
  try {
4098
4966
  folder = resolveWithinRoot(
4099
- context.pathPrefix ? resolveWithin(context.fernRoot, context.pathPrefix) : context.fernRoot,
4967
+ context.pathPrefix ? resolveWithinRoot(context.fernRoot, context.pathPrefix, context.repositoryRoot) : context.fernRoot,
4100
4968
  rawPath,
4101
4969
  context.repositoryRoot
4102
4970
  );
@@ -4202,10 +5070,11 @@ function convertNode(node, parentSegments, context) {
4202
5070
  return null;
4203
5071
  }
4204
5072
  if (typeof object.api === "string") {
5073
+ const ownSegment = segmentFor(object, object.api, context);
4205
5074
  context.apiSections.push({
4206
5075
  name: typeof object["api-name"] === "string" ? object["api-name"] : object.api,
4207
5076
  nameExplicit: typeof object["api-name"] === "string",
4208
- routeSegments: parentSegments
5077
+ routeSegments: ownSegment ? [...parentSegments, ownSegment] : parentSegments
4209
5078
  });
4210
5079
  return null;
4211
5080
  }
@@ -4247,7 +5116,7 @@ function resolveVersionedNavigation(config, configDir, fernRoot, context) {
4247
5116
  }
4248
5117
  if (!chosen || typeof chosen.path !== "string") return config;
4249
5118
  try {
4250
- const versionPath = resolveWithinRoot(configDir, chosen.path, fernRoot);
5119
+ const versionPath = resolveWithinRoot(configDir, chosen.path, context.repositoryRoot);
4251
5120
  if (!existsSync4(versionPath) || !lstatSync4(versionPath).isFile()) {
4252
5121
  warnOnce2(context, "fern-version-not-file", `Fern version file "${chosen.path}" is not a regular file and was skipped.`);
4253
5122
  return config;
@@ -4485,7 +5354,16 @@ function projectFernNavigation(input) {
4485
5354
  return section;
4486
5355
  }
4487
5356
  const distinguishingSegment = section.routeSegments.at(-1);
4488
- const tabLabel = distinguishingSegment ? `${section.tabLabel}: ${titleCase3(distinguishingSegment)}` : section.tabLabel;
5357
+ let tabLabel = distinguishingSegment ? `${section.tabLabel}: ${titleCase3(distinguishingSegment)}` : section.tabLabel;
5358
+ if (!tabLabel || tabLabel === `${section.tabLabel}: ${section.tabLabel}` || claimedTabLabels.has(tabLabel)) {
5359
+ let ordinal = 2;
5360
+ let candidate = `${section.tabLabel} (${ordinal})`;
5361
+ while (claimedTabLabels.has(candidate)) {
5362
+ ordinal += 1;
5363
+ candidate = `${section.tabLabel} (${ordinal})`;
5364
+ }
5365
+ tabLabel = candidate;
5366
+ }
4489
5367
  claimedTabLabels.add(tabLabel);
4490
5368
  return { ...section, tabLabel };
4491
5369
  });
@@ -4535,11 +5413,14 @@ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
4535
5413
  ".ico",
4536
5414
  ".jpeg",
4537
5415
  ".jpg",
5416
+ ".m4a",
4538
5417
  ".mp3",
4539
5418
  ".mp4",
5419
+ ".ogg",
4540
5420
  ".pdf",
4541
5421
  ".png",
4542
5422
  ".svg",
5423
+ ".wav",
4543
5424
  ".webm",
4544
5425
  ".webp"
4545
5426
  ]);
@@ -4727,9 +5608,92 @@ function readDocusaurusConfigSource(projectRoot) {
4727
5608
  if (!path || lstatSync5(path).size > 1e6) return "";
4728
5609
  return readFileSync5(path, "utf8");
4729
5610
  }
5611
+ function matchingBraceIndex(source, openBraceIndex) {
5612
+ let depth = 0;
5613
+ for (let i = openBraceIndex; i < source.length; i++) {
5614
+ const ch = source[i];
5615
+ if (ch === '"' || ch === "'" || ch === "`") {
5616
+ const quote = ch;
5617
+ i++;
5618
+ while (i < source.length && source[i] !== quote) {
5619
+ if (source[i] === "\\") i++;
5620
+ i++;
5621
+ }
5622
+ continue;
5623
+ }
5624
+ if (ch === "/" && source[i + 1] === "/") {
5625
+ const newline = source.indexOf("\n", i);
5626
+ i = newline === -1 ? source.length : newline;
5627
+ continue;
5628
+ }
5629
+ if (ch === "/" && source[i + 1] === "*") {
5630
+ const end = source.indexOf("*/", i + 2);
5631
+ i = end === -1 ? source.length : end + 1;
5632
+ continue;
5633
+ }
5634
+ if (ch === "{") depth++;
5635
+ else if (ch === "}") {
5636
+ depth--;
5637
+ if (depth === 0) return i;
5638
+ }
5639
+ }
5640
+ return -1;
5641
+ }
5642
+ function topLevelStringProperty(objectBody, key) {
5643
+ let depth = 0;
5644
+ const keyPattern = new RegExp(`^${key}\\s*:\\s*(['"])`);
5645
+ for (let i = 0; i < objectBody.length; i++) {
5646
+ const ch = objectBody[i];
5647
+ if (ch === '"' || ch === "'" || ch === "`") {
5648
+ const quote = ch;
5649
+ i++;
5650
+ while (i < objectBody.length && objectBody[i] !== quote) {
5651
+ if (objectBody[i] === "\\") i++;
5652
+ i++;
5653
+ }
5654
+ continue;
5655
+ }
5656
+ if (ch === "/" && objectBody[i + 1] === "/") {
5657
+ const newline = objectBody.indexOf("\n", i);
5658
+ i = newline === -1 ? objectBody.length : newline;
5659
+ continue;
5660
+ }
5661
+ if (ch === "/" && objectBody[i + 1] === "*") {
5662
+ const end = objectBody.indexOf("*/", i + 2);
5663
+ i = end === -1 ? objectBody.length : end + 1;
5664
+ continue;
5665
+ }
5666
+ if (ch === "{" || ch === "[" || ch === "(") {
5667
+ depth++;
5668
+ continue;
5669
+ }
5670
+ if (ch === "}" || ch === "]" || ch === ")") {
5671
+ depth--;
5672
+ continue;
5673
+ }
5674
+ if (depth === 0) {
5675
+ const match = keyPattern.exec(objectBody.slice(i));
5676
+ if (match) {
5677
+ const quote = match[1];
5678
+ const valueStart = i + match[0].length;
5679
+ const valueEnd = objectBody.indexOf(quote, valueStart);
5680
+ if (valueEnd !== -1) return objectBody.slice(valueStart, valueEnd);
5681
+ }
5682
+ }
5683
+ }
5684
+ return void 0;
5685
+ }
4730
5686
  function primaryDocusaurusDocsDirectory(projectRoot) {
4731
5687
  const source = readDocusaurusConfigSource(projectRoot);
4732
- const configured = source.match(/\bdocs\s*:\s*\{[\s\S]{0,4000}?\bpath\s*:\s*(['"])([^'"]+)\1/)?.[2];
5688
+ const docsKeyMatch = source.match(/\bdocs\s*:\s*\{/);
5689
+ let configured;
5690
+ if (docsKeyMatch?.index !== void 0) {
5691
+ const openBrace = docsKeyMatch.index + docsKeyMatch[0].length - 1;
5692
+ const closeBrace = matchingBraceIndex(source, openBrace);
5693
+ if (closeBrace !== -1) {
5694
+ configured = topLevelStringProperty(source.slice(openBrace + 1, closeBrace), "path");
5695
+ }
5696
+ }
4733
5697
  return trimTrailingSlashes(configured?.replace(/^\.\//, "") ?? "") || "docs";
4734
5698
  }
4735
5699
  function docusaurusArchiveRoots(projectRoot, repositoryDir, warnings) {
@@ -4781,7 +5745,7 @@ function docusaurusArchiveRoots(projectRoot, repositoryDir, warnings) {
4781
5745
  function additionalDocusaurusPluginRoots(repositoryDir, projectRoot, warnings) {
4782
5746
  const source = readDocusaurusConfigSource(projectRoot);
4783
5747
  const plugins = [];
4784
- const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
5748
+ const matcher = /['"](?:@docusaurus\/plugin-)?content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
4785
5749
  for (const match of source.matchAll(matcher)) {
4786
5750
  const localPath = trimTrailingSlashes(match[2].replace(/^\.\//, ""));
4787
5751
  const routePrefix = trimEdgeSlashes(match[4]);
@@ -5019,8 +5983,10 @@ function detectRepositoryDocsDir(repositoryDir) {
5019
5983
  }
5020
5984
  return "";
5021
5985
  }
5022
- function scanFiles(root, confinementRoot = root, warnings) {
5986
+ var MAX_RANKED_WALK_FILES = MAX_SOURCE_FILES * 20;
5987
+ function scanFiles(root, confinementRoot = root, warnings, rank) {
5023
5988
  const files = [];
5989
+ const walkCap = rank ? MAX_RANKED_WALK_FILES : MAX_SOURCE_FILES;
5024
5990
  let confinementReal;
5025
5991
  try {
5026
5992
  confinementReal = realpathSync2(confinementRoot);
@@ -5029,7 +5995,7 @@ function scanFiles(root, confinementRoot = root, warnings) {
5029
5995
  }
5030
5996
  const visitedRealPaths = /* @__PURE__ */ new Set();
5031
5997
  function visit(directory, logicalDirectory = directory) {
5032
- if (files.length >= MAX_SOURCE_FILES) return;
5998
+ if (files.length >= walkCap) return;
5033
5999
  let directoryReal;
5034
6000
  try {
5035
6001
  directoryReal = realpathSync2(directory);
@@ -5045,7 +6011,7 @@ function scanFiles(root, confinementRoot = root, warnings) {
5045
6011
  return;
5046
6012
  }
5047
6013
  for (const entry of entries) {
5048
- if (files.length >= MAX_SOURCE_FILES) return;
6014
+ if (files.length >= walkCap) return;
5049
6015
  if (isIgnoredContentDirectory(entry.name)) {
5050
6016
  if (warnings && entry.isDirectory() && !entry.isSymbolicLink() && containsMarkdown(resolveWithin(directory, entry.name))) {
5051
6017
  warnings.push({
@@ -5082,8 +6048,44 @@ function scanFiles(root, confinementRoot = root, warnings) {
5082
6048
  }
5083
6049
  }
5084
6050
  visit(root);
6051
+ if (rank && files.length >= walkCap) {
6052
+ warnings?.push({
6053
+ code: "limit-reached",
6054
+ message: `Stopped scanning after ${walkCap} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.`
6055
+ });
6056
+ }
5085
6057
  return files;
5086
6058
  }
6059
+ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, label) {
6060
+ if (scanned.length <= MAX_SOURCE_FILES) return scanned;
6061
+ 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));
6062
+ const dropped = ranked.slice(MAX_SOURCE_FILES).map(({ file }) => file);
6063
+ if (warnings) {
6064
+ const droppedPages = label === "file" ? dropped.filter((file) => isDocumentationExtension(file.relativePath)) : dropped;
6065
+ const noun = label === "file" ? "page" : "image or media file";
6066
+ const droppedVersions = /* @__PURE__ */ new Set();
6067
+ for (const file of dropped) {
6068
+ const firstSegment = file.relativePath.split("/", 1)[0];
6069
+ if (allVersionPrefixes.has(firstSegment)) droppedVersions.add(firstSegment);
6070
+ }
6071
+ const examples = droppedPages.slice(0, 3).map((file) => file.relativePath);
6072
+ const rest = droppedPages.length - examples.length;
6073
+ warnings.push({
6074
+ code: "limit-reached",
6075
+ 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.")
6076
+ });
6077
+ }
6078
+ return ranked.slice(0, MAX_SOURCE_FILES).map(({ file }) => file);
6079
+ }
6080
+ function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes) {
6081
+ const isAsset = (file) => ASSET_EXTENSIONS.has(extname5(file.relativePath).toLowerCase());
6082
+ const assetFiles = scanned.filter(isAsset);
6083
+ const otherFiles = scanned.filter((file) => !isAsset(file));
6084
+ return [
6085
+ ...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file"),
6086
+ ...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset")
6087
+ ];
6088
+ }
5087
6089
  function readMintignoreMatcher(mintlifyRoot) {
5088
6090
  const path = resolveWithin(mintlifyRoot, MINTIGNORE_FILENAME);
5089
6091
  if (!existsSync5(path) || !lstatSync5(path).isFile()) return null;
@@ -5200,10 +6202,159 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
5200
6202
  });
5201
6203
  continue;
5202
6204
  }
5203
- specs.push({ filename: basename3(match.relativePath), content: readFileSync5(match.absolutePath), tabLabel: reference.tabLabel });
6205
+ if (reference.directory) {
6206
+ warnings.push({
6207
+ code: "unsupported-config",
6208
+ message: `The OpenAPI spec "${reference.value}"${tabSuffix} was limited to pages under "${reference.directory}" in the source, but Thally's API reference always covers a whole tab, so it was migrated as the tab's full API reference. Update any links to "${reference.directory}/..." pages manually.`
6209
+ });
6210
+ }
6211
+ specs.push({
6212
+ filename: basename3(match.relativePath),
6213
+ content: readFileSync5(match.absolutePath),
6214
+ tabLabel: reference.tabLabel,
6215
+ sourcePath: match.relativePath,
6216
+ directory: reference.directory
6217
+ });
6218
+ }
6219
+ const filenameOwners = /* @__PURE__ */ new Map();
6220
+ for (const spec of specs) {
6221
+ const groupKey = spec.tabLabel ?? "\0default";
6222
+ const owner = filenameOwners.get(spec.filename);
6223
+ if (owner !== void 0 && owner !== groupKey) {
6224
+ let disambiguated = spec.sourcePath.replace(/\//g, "-");
6225
+ while (filenameOwners.has(disambiguated) && filenameOwners.get(disambiguated) !== groupKey) {
6226
+ disambiguated = `${groupKey.replace(/\W+/g, "-")}-${disambiguated}`;
6227
+ }
6228
+ spec.filename = disambiguated;
6229
+ }
6230
+ filenameOwners.set(spec.filename, groupKey);
5204
6231
  }
5205
6232
  return specs;
5206
6233
  }
6234
+ var OPENAPI_HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head", "trace"];
6235
+ var FERN_CRUD_METHOD_NAMES = {
6236
+ findall: "list",
6237
+ findone: "get",
6238
+ remove: "delete"
6239
+ };
6240
+ function mintlifyOperationSlugSegment(value) {
6241
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
6242
+ }
6243
+ function thallyOperationSlugSegments(path, method) {
6244
+ const cleaned = path.split("/").filter(Boolean).map((segment) => segment.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").toLowerCase());
6245
+ if (!cleaned.length) cleaned.push("root");
6246
+ cleaned.push(method.toLowerCase());
6247
+ return cleaned;
6248
+ }
6249
+ function slugifyApiSpecId(value) {
6250
+ return value.toLowerCase().replace(/[^a-z0-9/]+/g, "-").replace(/(^-|-$)+/g, "").replace(/\//g, "-") || value.toLowerCase();
6251
+ }
6252
+ function parseOpenApiOperationsForLinking(content) {
6253
+ let document;
6254
+ try {
6255
+ document = parseYaml4(content.toString("utf8"));
6256
+ } catch {
6257
+ return [];
6258
+ }
6259
+ const paths = document && typeof document === "object" ? document.paths : null;
6260
+ if (!paths || typeof paths !== "object") return [];
6261
+ const operations = [];
6262
+ for (const [path, pathItem] of Object.entries(paths)) {
6263
+ if (!pathItem || typeof pathItem !== "object") continue;
6264
+ for (const method of OPENAPI_HTTP_METHODS) {
6265
+ const operation = pathItem[method];
6266
+ if (!operation || typeof operation !== "object") continue;
6267
+ const op = operation;
6268
+ const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
6269
+ operations.push({
6270
+ method,
6271
+ path,
6272
+ tag: tags[0],
6273
+ summary: typeof op.summary === "string" ? op.summary : void 0,
6274
+ operationId: typeof op.operationId === "string" ? op.operationId : void 0
6275
+ });
6276
+ }
6277
+ }
6278
+ return operations;
6279
+ }
6280
+ function apiOperationLinkMap(sources, docsConfig) {
6281
+ const map = /* @__PURE__ */ new Map();
6282
+ const prefixLandings = /* @__PURE__ */ new Map();
6283
+ const apiTabs = docsConfig.tabs.filter((tab) => !tab.hidden && tab.api);
6284
+ apiTabs.forEach((tab, index) => {
6285
+ const source = sources.find((entry) => tab.api?.source === `/${entry.filename}`);
6286
+ if (!source || !source.prefix) return;
6287
+ const specId = index === 0 ? "default" : slugifyApiSpecId(tab.tab);
6288
+ const prefix = source.prefix.replace(/^\/+|\/+$/g, "").toLowerCase();
6289
+ if (prefix) prefixLandings.set(prefix, specId === "default" ? "/api" : `/api/${specId}`);
6290
+ for (const operation of parseOpenApiOperationsForLinking(source.content)) {
6291
+ const thallyHref = `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
6292
+ const leafCandidates = /* @__PURE__ */ new Set();
6293
+ if (operation.operationId) {
6294
+ const parts = operation.operationId.split(/[_.]/).filter(Boolean);
6295
+ const lastPart = parts.at(-1) ?? operation.operationId;
6296
+ const conventional = FERN_CRUD_METHOD_NAMES[lastPart.toLowerCase()];
6297
+ if (conventional) leafCandidates.add(conventional);
6298
+ leafCandidates.add(mintlifyOperationSlugSegment(lastPart));
6299
+ }
6300
+ if (operation.summary) leafCandidates.add(mintlifyOperationSlugSegment(operation.summary));
6301
+ if (operation.operationId) leafCandidates.add(mintlifyOperationSlugSegment(operation.operationId));
6302
+ leafCandidates.add(mintlifyOperationSlugSegment(`${operation.method} ${operation.path}`));
6303
+ const tagSegment = mintlifyOperationSlugSegment(operation.tag ?? "default");
6304
+ for (const leaf of leafCandidates) {
6305
+ if (!leaf) continue;
6306
+ const tagged = `${source.prefix}/${tagSegment}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
6307
+ const flat = `${source.prefix}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
6308
+ if (!map.has(tagged)) map.set(tagged, thallyHref);
6309
+ if (!map.has(flat)) map.set(flat, thallyHref);
6310
+ }
6311
+ }
6312
+ });
6313
+ return { operationLinks: map, prefixLandings };
6314
+ }
6315
+ function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings) {
6316
+ if (operationLinks.size === 0 && prefixLandings.size === 0) return;
6317
+ const unmatched = /* @__PURE__ */ new Set();
6318
+ for (const page of pages) {
6319
+ page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, unmatched);
6320
+ }
6321
+ if (unmatched.size === 0) return;
6322
+ const sorted = [...unmatched].sort();
6323
+ warnings.push({
6324
+ code: "unsupported-config",
6325
+ message: `${sorted.length} link(s) to API endpoints matched no endpoint in the OpenAPI spec, so they now point to the API section's overview page instead: ${sorted.slice(0, 10).join(", ")}${sorted.length > 10 ? `, and ${sorted.length - 10} more` : ""}. Update those links if you want them to reach a specific endpoint.`
6326
+ });
6327
+ }
6328
+ function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
6329
+ if (linkMap.size === 0 && prefixLandings.size === 0) return body;
6330
+ const orderedPrefixes = [...prefixLandings.entries()].sort((a, b) => b[0].length - a[0].length);
6331
+ let codeFence = null;
6332
+ return body.split("\n").map((line) => {
6333
+ const fence = line.match(/^\s*(`{3,}|~{3,})/);
6334
+ if (fence) {
6335
+ if (!codeFence) codeFence = fence[1][0];
6336
+ else if (fence[1][0] === codeFence) codeFence = null;
6337
+ return line;
6338
+ }
6339
+ if (codeFence) return line;
6340
+ const rewriteTarget = (target) => {
6341
+ const suffixIndex = target.search(/[?#]/);
6342
+ const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "").toLowerCase();
6343
+ const suffix = suffixIndex >= 0 ? target.slice(suffixIndex) : "";
6344
+ const mapped = linkMap.get(path);
6345
+ if (mapped) return `${mapped}${suffix}`;
6346
+ for (const [prefix, landing] of orderedPrefixes) {
6347
+ if (path === prefix) return `${landing}${suffix}`;
6348
+ if (path.startsWith(`${prefix}/`)) {
6349
+ unmatched.add(target);
6350
+ return `${landing}${suffix}`;
6351
+ }
6352
+ }
6353
+ return target;
6354
+ };
6355
+ return line.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
6356
+ }).join("\n");
6357
+ }
5207
6358
  var MAX_FERN_GENERATORS_BYTES = 2e6;
5208
6359
  function fernGeneratorsOpenApiPaths(config) {
5209
6360
  const api = config.api;
@@ -5333,18 +6484,18 @@ function interpolateSnippet(snippet, attributes, children) {
5333
6484
  }).join("\n");
5334
6485
  }
5335
6486
  function snippetComponentBody(source, componentName) {
5336
- const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
6487
+ const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
5337
6488
  for (const statement of parsed.statements) {
5338
- if (!ts2.isVariableStatement(statement)) continue;
6489
+ if (!ts3.isVariableStatement(statement)) continue;
5339
6490
  for (const declaration of statement.declarationList.declarations) {
5340
- if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== componentName) continue;
6491
+ if (!ts3.isIdentifier(declaration.name) || declaration.name.text !== componentName) continue;
5341
6492
  const initializer = declaration.initializer;
5342
- if (!initializer || !ts2.isArrowFunction(initializer)) continue;
5343
- const returned = ts2.isBlock(initializer.body) ? initializer.body.statements.find(ts2.isReturnStatement)?.expression : initializer.body;
6493
+ if (!initializer || !ts3.isArrowFunction(initializer)) continue;
6494
+ const returned = ts3.isBlock(initializer.body) ? initializer.body.statements.find(ts3.isReturnStatement)?.expression : initializer.body;
5344
6495
  if (!returned) continue;
5345
6496
  let expression = returned;
5346
- while (ts2.isParenthesizedExpression(expression)) expression = expression.expression;
5347
- if (ts2.isJsxElement(expression) || ts2.isJsxSelfClosingElement(expression) || ts2.isJsxFragment(expression)) {
6497
+ while (ts3.isParenthesizedExpression(expression)) expression = expression.expression;
6498
+ if (ts3.isJsxElement(expression) || ts3.isJsxSelfClosingElement(expression) || ts3.isJsxFragment(expression)) {
5348
6499
  return expression.getText(parsed);
5349
6500
  }
5350
6501
  }
@@ -5352,30 +6503,31 @@ function snippetComponentBody(source, componentName) {
5352
6503
  return source;
5353
6504
  }
5354
6505
  function statefulSnippetDeclaration(source, componentName) {
5355
- const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
5356
- if (parsed.statements.length !== 1) return null;
5357
- const statement = parsed.statements[0];
5358
- if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword) || statement.declarationList.declarations.length !== 1) return null;
6506
+ const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
6507
+ const isEsm = (statement2) => ts3.isImportDeclaration(statement2) || (ts3.isVariableStatement(statement2) || ts3.isFunctionDeclaration(statement2) || ts3.isClassDeclaration(statement2)) && !!ts3.getModifiers(statement2)?.some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword);
6508
+ if (parsed.statements.length === 0 || !parsed.statements.every(isEsm)) return null;
6509
+ const statement = parsed.statements.find((candidate) => ts3.isVariableStatement(candidate) && candidate.declarationList.declarations.length === 1 && ts3.isIdentifier(candidate.declarationList.declarations[0].name) && candidate.declarationList.declarations[0].name.text === componentName);
6510
+ if (!statement || !ts3.isVariableStatement(statement)) return null;
5359
6511
  const declaration = statement.declarationList.declarations[0];
5360
- if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== componentName || !declaration.initializer || !ts2.isArrowFunction(declaration.initializer) || !ts2.isBlock(declaration.initializer.body)) return null;
6512
+ if (!declaration.initializer || !ts3.isArrowFunction(declaration.initializer) || !ts3.isBlock(declaration.initializer.body)) return null;
5361
6513
  const body = declaration.initializer.body.statements;
5362
- if (body.length < 2 || !body.some(ts2.isReturnStatement)) return null;
6514
+ if (body.length < 2 || !body.some(ts3.isReturnStatement)) return null;
5363
6515
  return source.trim();
5364
6516
  }
5365
6517
  function staticNamedSnippetValues(source) {
5366
6518
  const values = /* @__PURE__ */ new Map();
5367
- const parsed = ts2.createSourceFile("snippet.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
6519
+ const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
5368
6520
  for (const statement of parsed.statements) {
5369
- if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
6521
+ if (!ts3.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword)) continue;
5370
6522
  for (const declaration of statement.declarationList.declarations) {
5371
- if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
6523
+ if (!ts3.isIdentifier(declaration.name) || !declaration.initializer) continue;
5372
6524
  const expression = declaration.initializer;
5373
- if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
6525
+ if (ts3.isStringLiteral(expression) || ts3.isNoSubstitutionTemplateLiteral(expression)) {
5374
6526
  values.set(declaration.name.text, JSON.stringify(expression.text));
5375
- } else if (ts2.isNumericLiteral(expression)) {
6527
+ } else if (ts3.isNumericLiteral(expression)) {
5376
6528
  values.set(declaration.name.text, expression.text);
5377
- } else if (expression.kind === ts2.SyntaxKind.TrueKeyword || expression.kind === ts2.SyntaxKind.FalseKeyword) {
5378
- values.set(declaration.name.text, expression.kind === ts2.SyntaxKind.TrueKeyword ? "true" : "false");
6529
+ } else if (expression.kind === ts3.SyntaxKind.TrueKeyword || expression.kind === ts3.SyntaxKind.FalseKeyword) {
6530
+ values.set(declaration.name.text, expression.kind === ts3.SyntaxKind.TrueKeyword ? "true" : "false");
5379
6531
  }
5380
6532
  }
5381
6533
  }
@@ -5791,8 +6943,17 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
5791
6943
  result = result.replace(SNIPPET_TAG_PATTERN, (_tag, doubleQuoted, singleQuoted) => {
5792
6944
  const filePath = doubleQuoted ?? singleQuoted;
5793
6945
  try {
5794
- const candidate = resolveWithin(siteRoot, `snippets/${filePath}`);
5795
- if (!existsSync5(candidate) || !lstatSync5(candidate).isFile()) throw new Error("file not found");
6946
+ const candidate = [
6947
+ () => resolveWithin(siteRoot, `snippets/${filePath}`),
6948
+ () => resolveSnippetPath(filePath, currentFile, repositoryRoot, siteRoot)
6949
+ ].map((resolveCandidate) => {
6950
+ try {
6951
+ return resolveCandidate();
6952
+ } catch {
6953
+ return void 0;
6954
+ }
6955
+ }).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
6956
+ if (!candidate) throw new Error("file not found");
5796
6957
  return inlineMdxSnippets(
5797
6958
  withoutFrontmatter(readFileSync5(candidate, "utf8")),
5798
6959
  candidate,
@@ -5814,23 +6975,33 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
5814
6975
  if (preservedDeclarations.size > 0) {
5815
6976
  const frontmatter = result.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
5816
6977
  const prefix = frontmatter?.[0] ?? "";
5817
- result = `${prefix}${[...preservedDeclarations.values()].join("\n\n")}
6978
+ result = `${prefix}${[...new Set(preservedDeclarations.values())].join("\n\n")}
5818
6979
 
5819
6980
  ${result.slice(prefix.length)}`;
5820
6981
  }
5821
6982
  return depth === 0 ? hoistMdxImports(result) : result;
5822
6983
  }
5823
- function injectOpenApiSpecs(config, specs) {
6984
+ function injectOpenApiSpecs(config, specs, warnings) {
5824
6985
  let tabs = config.tabs.map((tab) => ({ ...tab }));
5825
6986
  for (const spec of specs) {
5826
6987
  const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
5827
- if (apiTab) apiTab.api = {
5828
- source: `/${spec.filename}`,
5829
- // An API-only tab needs generated endpoint navigation; an authored
5830
- // page tab keeps its own groups alongside the bound spec.
5831
- ...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
5832
- };
5833
- else tabs = [...tabs, { tab: spec.tabLabel ?? "API Reference", api: { source: `/${spec.filename}` } }];
6988
+ if (apiTab) {
6989
+ if (apiTab.api && warnings) {
6990
+ warnings.push({
6991
+ code: "unsupported-config",
6992
+ message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "/${spec.filename}" was not added. Put it in its own tab to include it.`
6993
+ });
6994
+ continue;
6995
+ }
6996
+ apiTab.api = {
6997
+ source: `/${spec.filename}`,
6998
+ // An API-only tab needs generated endpoint navigation; an authored
6999
+ // page tab keeps its own groups alongside the bound spec.
7000
+ ...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
7001
+ };
7002
+ } else {
7003
+ tabs = [...tabs, { tab: spec.tabLabel ?? "API Reference", api: { source: `/${spec.filename}` } }];
7004
+ }
5834
7005
  }
5835
7006
  return { ...config, tabs };
5836
7007
  }
@@ -5870,6 +7041,8 @@ function migrateRepository(options) {
5870
7041
  const componentRoot = mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir;
5871
7042
  const componentMigrator = platform === "mintlify" || platform === "docusaurus" || platform === "fern" ? createComponentMigrator(componentRoot, repositoryDir, warnings, componentSourceIdentity(options.sourceUrl, repositoryDir, componentRoot)) : void 0;
5872
7043
  let docsConfig = { tabs: [] };
7044
+ let defaultVersionPrefixes = /* @__PURE__ */ new Set();
7045
+ let allVersionPrefixes = /* @__PURE__ */ new Set();
5873
7046
  const referenceMap = /* @__PURE__ */ new Map();
5874
7047
  const exactReferenceMap = /* @__PURE__ */ new Map();
5875
7048
  const referenceOrder = /* @__PURE__ */ new Map();
@@ -5879,6 +7052,7 @@ function migrateRepository(options) {
5879
7052
  let fernBasePath = "";
5880
7053
  let fernVersionPath = "";
5881
7054
  let fernApiSections = [];
7055
+ const fernExternalSourcePaths = /* @__PURE__ */ new Set();
5882
7056
  let fernChangelogIndexes = [];
5883
7057
  const fernReferencedPaths = /* @__PURE__ */ new Set();
5884
7058
  const fernSourceLinkAliases = /* @__PURE__ */ new Map();
@@ -5889,6 +7063,8 @@ function migrateRepository(options) {
5889
7063
  const config = readMintlifyConfig(mintlifyProjectRoot ?? repositoryDir);
5890
7064
  if (config) {
5891
7065
  mintlifyConfig = config;
7066
+ defaultVersionPrefixes = mintlifyDefaultVersionPrefixes(config);
7067
+ allVersionPrefixes = mintlifyAllVersionPrefixes(config);
5892
7068
  const projected = projectMintlifyNavigation(config);
5893
7069
  docsConfig = projected.docsConfig;
5894
7070
  warnings.push(...projected.warnings);
@@ -5939,6 +7115,7 @@ function migrateRepository(options) {
5939
7115
  const exactKey = exactReferenceKey(descriptor.sourcePath);
5940
7116
  if (!exactReferenceMap.has(exactKey)) exactReferenceMap.set(exactKey, { navigationId: descriptor.navigationId });
5941
7117
  if (!referenceOrder.has(key)) referenceOrder.set(key, index);
7118
+ if (descriptor.sourcePath.startsWith("../")) fernExternalSourcePaths.add(descriptor.sourcePath);
5942
7119
  }
5943
7120
  }
5944
7121
  } catch (error) {
@@ -5963,15 +7140,35 @@ function migrateRepository(options) {
5963
7140
  throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
5964
7141
  }
5965
7142
  const mintignoreMatcher = platform === "mintlify" && mintlifyProjectRoot ? readMintignoreMatcher(mintlifyProjectRoot) : null;
5966
- const files = mintignoreMatcher ? scanFiles(contentRoot, repositoryDir, warnings).filter((file) => !mintignoreMatcher.ignores(file.relativePath)) : scanFiles(contentRoot, repositoryDir, warnings);
7143
+ const lastReferencedIndexByVersionLocale = /* @__PURE__ */ new Map();
7144
+ for (const [key, index] of referenceOrder) {
7145
+ const blockKey = key.split("/").slice(0, 2).join("/");
7146
+ const current = lastReferencedIndexByVersionLocale.get(blockKey);
7147
+ if (current === void 0 || index > current) lastReferencedIndexByVersionLocale.set(blockKey, index);
7148
+ }
7149
+ const discoveryRank = referenceOrder.size > 0 ? (relativePath) => {
7150
+ const referencedIndex = referenceOrder.get(normalizedReferenceKey(relativePath));
7151
+ if (referencedIndex !== void 0) return referencedIndex;
7152
+ const blockKey = relativePath.split("/").slice(0, 2).join("/");
7153
+ const versionCeiling = lastReferencedIndexByVersionLocale.get(blockKey);
7154
+ return versionCeiling !== void 0 ? versionCeiling + 1 : Number.MAX_SAFE_INTEGER;
7155
+ } : void 0;
7156
+ const scannedFiles = scanFiles(contentRoot, repositoryDir, warnings, discoveryRank);
7157
+ const mintignoreFilteredFiles = mintignoreMatcher ? scannedFiles.filter((file) => !mintignoreMatcher.ignores(file.relativePath)) : scannedFiles;
7158
+ const discoveryBudgetApplied = discoveryRank !== void 0 && mintignoreFilteredFiles.length > MAX_SOURCE_FILES;
7159
+ const files = discoveryRank ? selectFilesWithinBudget(mintignoreFilteredFiles, discoveryRank, warnings, allVersionPrefixes) : mintignoreFilteredFiles;
5967
7160
  if (platform === "fern" && fernProjectRoot) {
5968
- const scanned = new Set(files.map((file) => file.absolutePath));
5969
- for (const sourcePath of fernReferencedPaths) {
7161
+ const discovered2 = new Set(files.map((file) => file.relativePath));
7162
+ for (const sourcePath of fernExternalSourcePaths) {
7163
+ if (discovered2.has(sourcePath)) continue;
5970
7164
  try {
5971
7165
  const absolutePath = resolveWithinRoot(fernProjectRoot, sourcePath, repositoryDir);
5972
- if (scanned.has(absolutePath) || !existsSync5(absolutePath) || !lstatSync5(absolutePath).isFile()) continue;
5973
- files.push({ absolutePath, relativePath: sourcePath });
5974
- scanned.add(absolutePath);
7166
+ if (existsSync5(absolutePath) && lstatSync5(absolutePath).isFile()) {
7167
+ files.push({ absolutePath, relativePath: sourcePath });
7168
+ discovered2.add(sourcePath);
7169
+ } else {
7170
+ warnings.push({ code: "missing-page", message: "A Fern navigation path could not be read within this repository.", source: sourcePath });
7171
+ }
5975
7172
  } catch {
5976
7173
  warnings.push({ code: "missing-page", message: "A Fern navigation path could not be read within this repository.", source: sourcePath });
5977
7174
  }
@@ -6057,6 +7254,9 @@ function migrateRepository(options) {
6057
7254
  mintlifyProjectRoot ?? docusaurusProjectRoot ?? fernProjectRoot ?? repositoryDir,
6058
7255
  snippetAliases
6059
7256
  );
7257
+ if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
7258
+ raw = normalizeExplicitHeadingIds(raw);
7259
+ }
6060
7260
  if (componentMigrator) {
6061
7261
  const warningsBeforeTransform = warnings.length;
6062
7262
  raw = componentMigrator.transform(raw, file.absolutePath);
@@ -6132,6 +7332,13 @@ function migrateRepository(options) {
6132
7332
  skipped++;
6133
7333
  continue;
6134
7334
  }
7335
+ if (page.frontmatterError) {
7336
+ warnings.push({
7337
+ code: "unsupported-config",
7338
+ message: `The page's frontmatter is not valid YAML (${page.frontmatterError}); the page was kept and as much of its metadata as could be read was used.`,
7339
+ source: relative5(repositoryDir, file.absolutePath).replace(/\\/g, "/")
7340
+ });
7341
+ }
6135
7342
  if (platform === "fern" && !page.navTitle) {
6136
7343
  const navTitle = fernNavTitles.get(navigationId);
6137
7344
  if (navTitle && navTitle !== page.title) page.navTitle = navTitle;
@@ -6172,9 +7379,9 @@ function migrateRepository(options) {
6172
7379
  });
6173
7380
  continue;
6174
7381
  }
6175
- const declaredNames = functionDeclaredNames(page.body);
6176
- if (hasAnyFunctionValuedProp(page.body, declaredNames)) {
6177
- if (propsTargetExtractedClientComponent(page.body, declaredNames)) {
7382
+ const declaredNames2 = functionDeclaredNames(page.body);
7383
+ if (hasAnyFunctionValuedProp(page.body, declaredNames2)) {
7384
+ if (propsTargetExtractedClientComponent(page.body, declaredNames2)) {
6178
7385
  skipped++;
6179
7386
  warnings.push({
6180
7387
  code: "skipped-file",
@@ -6211,6 +7418,13 @@ function migrateRepository(options) {
6211
7418
  if (sourcePath !== page.id && /^[A-Za-z0-9_./-]+$/.test(sourcePath)) {
6212
7419
  routeAliases.push({ source: `/${sourcePath}`, destination: `/${page.id}`, permanent: false });
6213
7420
  }
7421
+ const versionPrefix = [...defaultVersionPrefixes].find((prefix) => sourcePath === prefix || sourcePath.startsWith(`${prefix}/`));
7422
+ if (versionPrefix) {
7423
+ const unversioned = sourcePath.slice(versionPrefix.length).replace(/^\/+/, "");
7424
+ if (unversioned && unversioned !== sourcePath && unversioned !== page.id && /^[A-Za-z0-9_./-]+$/.test(unversioned) && !routeAliases.some((redirect) => redirect.source === `/${unversioned}`)) {
7425
+ routeAliases.push({ source: `/${unversioned}`, destination: `/${page.id}`, permanent: false });
7426
+ }
7427
+ }
6214
7428
  }
6215
7429
  if (docusaurusDescriptor) {
6216
7430
  docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
@@ -6289,7 +7503,7 @@ function migrateRepository(options) {
6289
7503
  const referencingPages = referencedAssetPaths.get(assetPath);
6290
7504
  warnings.push({
6291
7505
  code: "limit-reached",
6292
- message: referencingPages ? `Asset was skipped because the migration asset budget was exhausted, but it is referenced by: ${[...referencingPages].join(", ")}.` : "An asset was skipped because the migration asset budget was exhausted.",
7506
+ message: "This file was not copied because it is larger than 25 MB or the copied files would exceed 500 MB in total. Add it to public/ manually" + (referencingPages ? `; it is used by ${[...referencingPages].slice(0, 3).join(", ")}${referencingPages.size > 3 ? `, and ${referencingPages.size - 3} more` : ""}.` : "."),
6293
7507
  source: file.relativePath
6294
7508
  });
6295
7509
  continue;
@@ -6306,8 +7520,8 @@ function migrateRepository(options) {
6306
7520
  assets.push({ path: assetPath, content });
6307
7521
  totalAssetBytes += size;
6308
7522
  }
6309
- if (files.length >= MAX_SOURCE_FILES) {
6310
- warnings.push({ code: "limit-reached", message: `Repository discovery stopped at ${MAX_SOURCE_FILES} files.` });
7523
+ if (!discoveryBudgetApplied && files.length >= MAX_SOURCE_FILES) {
7524
+ 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.` });
6311
7525
  }
6312
7526
  if (platform === "docusaurus") {
6313
7527
  const projected = projectDocusaurusNavigation({
@@ -6393,10 +7607,11 @@ function migrateRepository(options) {
6393
7607
  filename = `${prefix}${filename}`;
6394
7608
  }
6395
7609
  specFilenameSources.set(filename, spec.absolutePath);
7610
+ const specContent = readFileSync5(spec.absolutePath);
6396
7611
  if (!assets.some((asset) => asset.path === filename)) {
6397
- assets.push({ path: filename, content: readFileSync5(spec.absolutePath) });
7612
+ assets.push({ path: filename, content: specContent });
6398
7613
  }
6399
- resolvedSpecs.push({ filename, tabLabel: section.tabLabel });
7614
+ resolvedSpecs.push({ filename, tabLabel: section.tabLabel, content: specContent, routeSegments: section.routeSegments ?? [] });
6400
7615
  continue;
6401
7616
  }
6402
7617
  if (resolution.unsupported.length > 0) {
@@ -6424,7 +7639,14 @@ function migrateRepository(options) {
6424
7639
  message: checked.length ? `No OpenAPI/AsyncAPI spec could be found for the "${section.name}" API. Checked: ${checked.join(", ")}. Point generators.yml at a spec file that exists.` : `No OpenAPI/AsyncAPI spec could be found for the "${section.name}" API, because its api-name is not a valid folder name. Add the spec manually.`
6425
7640
  });
6426
7641
  }
6427
- if (resolvedSpecs.length > 0) docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs);
7642
+ if (resolvedSpecs.length > 0) {
7643
+ docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
7644
+ const { operationLinks, prefixLandings } = apiOperationLinkMap(
7645
+ resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.routeSegments.join("/") })),
7646
+ docsConfig
7647
+ );
7648
+ rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
7649
+ }
6428
7650
  } else if (platform === "mintlify") {
6429
7651
  const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
6430
7652
  for (const spec of resolvedSpecs) {
@@ -6433,7 +7655,12 @@ function migrateRepository(options) {
6433
7655
  }
6434
7656
  }
6435
7657
  if (resolvedSpecs.length > 0) {
6436
- docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs);
7658
+ docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
7659
+ const { operationLinks, prefixLandings } = apiOperationLinkMap(
7660
+ resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.directory })),
7661
+ docsConfig
7662
+ );
7663
+ rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
6437
7664
  } else {
6438
7665
  const fallback = findOpenApi(files);
6439
7666
  if (fallback) {
@@ -6466,7 +7693,7 @@ function migrateRepository(options) {
6466
7693
  ];
6467
7694
  for (const plugin of additionalRoots) {
6468
7695
  if (discovered >= MAX_SOURCE_FILES) {
6469
- warnings.push({ code: "limit-reached", message: `Docusaurus docs import stopped at the ${MAX_SOURCE_FILES}-file discovery budget.` });
7696
+ warnings.push({ code: "limit-reached", message: `Stopped importing Docusaurus docs plugins after ${MAX_SOURCE_FILES} files; the remaining plugin docs were not migrated.` });
6470
7697
  break;
6471
7698
  }
6472
7699
  if (plugin.docsDir === configuredDocsDir) continue;
@@ -6957,6 +8184,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
6957
8184
  "Card",
6958
8185
  "CardGroup",
6959
8186
  "Check",
8187
+ "CodeBlock",
6960
8188
  "CodeGroup",
6961
8189
  "Color",
6962
8190
  "Column",
@@ -6967,6 +8195,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
6967
8195
  "Expandable",
6968
8196
  "Agent",
6969
8197
  "AgentPrompt",
8198
+ "Banner",
6970
8199
  "BannerPreview",
6971
8200
  "Embed",
6972
8201
  "File",
@@ -6983,6 +8212,8 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
6983
8212
  "InlineResponseExample",
6984
8213
  "Latex",
6985
8214
  "LegacyView",
8215
+ "LiteYouTubeEmbed",
8216
+ "MDX",
6986
8217
  "Mermaid",
6987
8218
  "Note",
6988
8219
  "Panel",
@@ -7006,9 +8237,11 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
7006
8237
  "Tooltip",
7007
8238
  "Tree",
7008
8239
  "Update",
8240
+ "Video",
7009
8241
  "View",
7010
8242
  "Visibility",
7011
- "Warning"
8243
+ "Warning",
8244
+ "YouTube"
7012
8245
  ]);
7013
8246
  var LOCALE_CODES = /* @__PURE__ */ new Set([
7014
8247
  "ar",