@thallylabs/migrate 0.2.11 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -10
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1102 -162
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
19
|
+
import * as ts3 from "typescript";
|
|
20
20
|
|
|
21
21
|
// src/components.ts
|
|
22
22
|
import { createHash } from "crypto";
|
|
@@ -26,7 +26,7 @@ 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
|
|
29
|
+
import ts2 from "typescript";
|
|
30
30
|
import { unified as unified2 } from "unified";
|
|
31
31
|
|
|
32
32
|
// src/frontmatter.ts
|
|
@@ -142,7 +142,15 @@ var THALLY_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
142
142
|
"Visibility",
|
|
143
143
|
"Human",
|
|
144
144
|
"Agent",
|
|
145
|
-
"BannerPreview"
|
|
145
|
+
"BannerPreview",
|
|
146
|
+
"Banner",
|
|
147
|
+
"CodeBlock",
|
|
148
|
+
"Column",
|
|
149
|
+
"FileTree",
|
|
150
|
+
"LiteYouTubeEmbed",
|
|
151
|
+
"MDX",
|
|
152
|
+
"Video",
|
|
153
|
+
"YouTube"
|
|
146
154
|
]);
|
|
147
155
|
function isThallyBuiltinComponent(name) {
|
|
148
156
|
return THALLY_BUILTIN_COMPONENTS.has(name) || THALLY_BUILTIN_COMPONENTS.has(name.split(".")[0]);
|
|
@@ -1094,12 +1102,117 @@ function normalizeMintlifyParameterAnchors(body) {
|
|
|
1094
1102
|
(match, _tag, _before, doubleQuoted, singleQuoted) => `${anchorFor(doubleQuoted ?? singleQuoted)}${match}`
|
|
1095
1103
|
));
|
|
1096
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(/^</, "<").replace(/>$/, ">");
|
|
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
|
+
}
|
|
1097
1210
|
function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
1098
1211
|
const runFern = platform === "fern";
|
|
1099
1212
|
const runMintlify = platform === "mintlify";
|
|
1100
1213
|
const runDocusaurus = platform === "docusaurus";
|
|
1101
1214
|
const sourceBody = runDocusaurus && unwrapMdxCodeBlocks ? unwrapDocusaurusMdxCodeBlocks(body) : body;
|
|
1102
|
-
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(sourceBody)) : sourceBody));
|
|
1215
|
+
let rewritten = normalizeDocusaurusAdmonitions(normalizeExplicitHeadingIds(runFern ? normalizeFernFileTrees(normalizeFernCallouts(normalizeNestedCodeFences(sourceBody))) : normalizeNestedCodeFences(sourceBody)));
|
|
1103
1216
|
if (runDocusaurus) {
|
|
1104
1217
|
rewritten = replaceOutsideCode(rewritten, (segment) => segment.replace(
|
|
1105
1218
|
/:([+\w-]+):/g,
|
|
@@ -1133,7 +1246,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
|
1133
1246
|
return result;
|
|
1134
1247
|
});
|
|
1135
1248
|
if (runFern) rewritten = unwrapFernCodeBlockTabs(rewritten);
|
|
1136
|
-
const normalized = provideScopedGlobalReferences(rewritten);
|
|
1249
|
+
const normalized = escapeOrphanCapitalizedTags(provideScopedGlobalReferences(rewritten));
|
|
1137
1250
|
if (runDocusaurus && sourceBody !== body) {
|
|
1138
1251
|
try {
|
|
1139
1252
|
compileSync(normalized, { outputFormat: "program" });
|
|
@@ -1151,7 +1264,7 @@ function parseMarkdownPage(input) {
|
|
|
1151
1264
|
...input.locale ? { locale: input.locale } : {}
|
|
1152
1265
|
};
|
|
1153
1266
|
const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
|
|
1154
|
-
let body = normalizeMdx(parsed.content, input.platform).trim();
|
|
1267
|
+
let body = injectReactHookImports(normalizeMdx(parsed.content, input.platform)).trim();
|
|
1155
1268
|
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
1156
1269
|
const heading = leadingPageHeading(body);
|
|
1157
1270
|
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title ?? titleFromId(identity.navigationId);
|
|
@@ -1261,6 +1374,543 @@ function normalizeAssetPath(value) {
|
|
|
1261
1374
|
return segments.join("/");
|
|
1262
1375
|
}
|
|
1263
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
|
+
|
|
1264
1914
|
// src/components.ts
|
|
1265
1915
|
function normalizeIndentedFences(content) {
|
|
1266
1916
|
const lines = content.split("\n");
|
|
@@ -1307,23 +1957,23 @@ function normalizeIndentedFences(content) {
|
|
|
1307
1957
|
}
|
|
1308
1958
|
function staticServerHelper(path, imported, local) {
|
|
1309
1959
|
if (imported === "default") return null;
|
|
1310
|
-
const parsed =
|
|
1960
|
+
const parsed = ts2.createSourceFile(path, readFileSync(path, "utf8"), ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
1311
1961
|
const isLiteral = (node) => {
|
|
1312
|
-
if (
|
|
1313
|
-
if (
|
|
1314
|
-
if ([
|
|
1315
|
-
if (
|
|
1316
|
-
if (
|
|
1317
|
-
if (
|
|
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));
|
|
1318
1968
|
return false;
|
|
1319
1969
|
};
|
|
1320
1970
|
for (const statement of parsed.statements) {
|
|
1321
|
-
if (!
|
|
1971
|
+
if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
|
|
1322
1972
|
for (const declaration of statement.declarationList.declarations) {
|
|
1323
|
-
if (!
|
|
1973
|
+
if (!ts2.isIdentifier(declaration.name) || declaration.name.text !== imported || !declaration.initializer || !ts2.isArrowFunction(declaration.initializer)) continue;
|
|
1324
1974
|
const arrow = declaration.initializer;
|
|
1325
1975
|
if (arrow.parameters.length > 0) continue;
|
|
1326
|
-
const result =
|
|
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;
|
|
1327
1977
|
if (result && isLiteral(result)) return `export const ${local} = () => (${result.getText(parsed)});`;
|
|
1328
1978
|
}
|
|
1329
1979
|
}
|
|
@@ -1547,12 +2197,12 @@ function wrapImpureCssModuleSelectors(css) {
|
|
|
1547
2197
|
return root.toString();
|
|
1548
2198
|
}
|
|
1549
2199
|
function sourceFile(source, filename) {
|
|
1550
|
-
return
|
|
2200
|
+
return ts2.createSourceFile(
|
|
1551
2201
|
filename,
|
|
1552
2202
|
source,
|
|
1553
|
-
|
|
2203
|
+
ts2.ScriptTarget.Latest,
|
|
1554
2204
|
true,
|
|
1555
|
-
/\.[cm]?ts$/.test(filename) ?
|
|
2205
|
+
/\.[cm]?ts$/.test(filename) ? ts2.ScriptKind.TS : ts2.ScriptKind.TSX
|
|
1556
2206
|
);
|
|
1557
2207
|
}
|
|
1558
2208
|
function walk(node, visitor) {
|
|
@@ -1563,24 +2213,24 @@ function implicitReactImports(source) {
|
|
|
1563
2213
|
const bindings = /* @__PURE__ */ new Set();
|
|
1564
2214
|
const references = /* @__PURE__ */ new Set();
|
|
1565
2215
|
function bind(name) {
|
|
1566
|
-
if (
|
|
1567
|
-
else for (const element of name.elements) if (
|
|
2216
|
+
if (ts2.isIdentifier(name)) bindings.add(name.text);
|
|
2217
|
+
else for (const element of name.elements) if (ts2.isBindingElement(element)) bind(element.name);
|
|
1568
2218
|
}
|
|
1569
2219
|
for (const statement of source.statements) {
|
|
1570
|
-
if (
|
|
2220
|
+
if (ts2.isImportDeclaration(statement) && statement.importClause) {
|
|
1571
2221
|
const clause = statement.importClause;
|
|
1572
2222
|
if (clause.name) bindings.add(clause.name.text);
|
|
1573
2223
|
if (clause.namedBindings) {
|
|
1574
|
-
if (
|
|
2224
|
+
if (ts2.isNamespaceImport(clause.namedBindings)) bindings.add(clause.namedBindings.name.text);
|
|
1575
2225
|
else for (const element of clause.namedBindings.elements) bindings.add(element.name.text);
|
|
1576
2226
|
}
|
|
1577
2227
|
}
|
|
1578
|
-
if (
|
|
1579
|
-
if ((
|
|
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);
|
|
1580
2230
|
}
|
|
1581
2231
|
function visit(node) {
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
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);
|
|
1584
2234
|
}
|
|
1585
2235
|
visit(source);
|
|
1586
2236
|
const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
|
|
@@ -1596,13 +2246,22 @@ function implicitReactImports(source) {
|
|
|
1596
2246
|
usesMintlifyComponents ? mintlifyComponentsShim() : ""
|
|
1597
2247
|
].filter(Boolean).join("\n");
|
|
1598
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
|
+
}
|
|
1599
2258
|
function imports(statement) {
|
|
1600
|
-
if (!
|
|
2259
|
+
if (!ts2.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) return [];
|
|
1601
2260
|
const clause = statement.importClause;
|
|
1602
2261
|
if (clause.isTypeOnly) return [];
|
|
1603
2262
|
const source = statement.moduleSpecifier.text;
|
|
1604
2263
|
const bindings = clause.name ? [{ local: clause.name.text, imported: "default", source }] : [];
|
|
1605
|
-
if (clause.namedBindings &&
|
|
2264
|
+
if (clause.namedBindings && ts2.isNamedImports(clause.namedBindings)) {
|
|
1606
2265
|
for (const element of clause.namedBindings.elements) {
|
|
1607
2266
|
if (!element.isTypeOnly) bindings.push({ local: element.name.text, imported: element.propertyName?.text ?? element.name.text, source });
|
|
1608
2267
|
}
|
|
@@ -1618,6 +2277,7 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
|
|
|
1618
2277
|
"ParamField",
|
|
1619
2278
|
"Expandable",
|
|
1620
2279
|
"CodeGroup",
|
|
2280
|
+
"CodeBlock",
|
|
1621
2281
|
"Badge",
|
|
1622
2282
|
"Tooltip",
|
|
1623
2283
|
"Tabs",
|
|
@@ -1627,6 +2287,7 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
|
|
|
1627
2287
|
"InlineRequestExample",
|
|
1628
2288
|
"InlineResponseExample",
|
|
1629
2289
|
"Tree",
|
|
2290
|
+
"FileTree",
|
|
1630
2291
|
"Folder",
|
|
1631
2292
|
"File",
|
|
1632
2293
|
"Mermaid",
|
|
@@ -1648,8 +2309,8 @@ var CLIENT_BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set([
|
|
|
1648
2309
|
function isConfirmedClientBoundaryTag(name) {
|
|
1649
2310
|
return EXTRACTED_CLIENT_COMPONENT_TAG.test(name) || CLIENT_BUILTIN_COMPONENT_TAGS.has(name);
|
|
1650
2311
|
}
|
|
1651
|
-
function functionValuedAttribute(node,
|
|
1652
|
-
const isFunctionValue = (value) =>
|
|
2312
|
+
function functionValuedAttribute(node, declaredNames2) {
|
|
2313
|
+
const isFunctionValue = (value) => declaredNames2.has(value.trim()) || isFunctionInitializer(value.trim(), 0);
|
|
1653
2314
|
for (const attribute of node.attributes ?? []) {
|
|
1654
2315
|
const raw = attribute.value;
|
|
1655
2316
|
const value = raw && typeof raw === "object" ? raw.value : void 0;
|
|
@@ -1662,21 +2323,21 @@ function functionValuedAttribute(node, declaredNames) {
|
|
|
1662
2323
|
}
|
|
1663
2324
|
return void 0;
|
|
1664
2325
|
}
|
|
1665
|
-
function propsTargetExtractedClientComponent(body,
|
|
2326
|
+
function propsTargetExtractedClientComponent(body, declaredNames2) {
|
|
1666
2327
|
const tree = parser.parse(body);
|
|
1667
2328
|
let found = false;
|
|
1668
2329
|
walk(tree, (node) => {
|
|
1669
2330
|
if (found || !node.name || !isConfirmedClientBoundaryTag(node.name)) return;
|
|
1670
|
-
if (functionValuedAttribute(node,
|
|
2331
|
+
if (functionValuedAttribute(node, declaredNames2) !== void 0) found = true;
|
|
1671
2332
|
});
|
|
1672
2333
|
return found;
|
|
1673
2334
|
}
|
|
1674
|
-
function hasAnyFunctionValuedProp(body,
|
|
2335
|
+
function hasAnyFunctionValuedProp(body, declaredNames2) {
|
|
1675
2336
|
const tree = parser.parse(body);
|
|
1676
2337
|
let found = false;
|
|
1677
2338
|
walk(tree, (node) => {
|
|
1678
2339
|
if (found || !node.name) return;
|
|
1679
|
-
if (functionValuedAttribute(node,
|
|
2340
|
+
if (functionValuedAttribute(node, declaredNames2) !== void 0) found = true;
|
|
1680
2341
|
});
|
|
1681
2342
|
return found;
|
|
1682
2343
|
}
|
|
@@ -1684,13 +2345,13 @@ function declarationsReferenceBrowserGlobal(body) {
|
|
|
1684
2345
|
const tree = parser.parse(body);
|
|
1685
2346
|
let found = false;
|
|
1686
2347
|
function inspect(node) {
|
|
1687
|
-
if (
|
|
1688
|
-
|
|
2348
|
+
if (ts2.isIdentifier(node) && (node.text === "document" || node.text === "window")) found = true;
|
|
2349
|
+
ts2.forEachChild(node, inspect);
|
|
1689
2350
|
}
|
|
1690
2351
|
for (const node of tree.children ?? []) {
|
|
1691
2352
|
if (found || node.type !== "mdxjsEsm" || node.value === void 0) continue;
|
|
1692
2353
|
for (const statement of sourceFile(node.value, "inline.tsx").statements) {
|
|
1693
|
-
if (
|
|
2354
|
+
if (ts2.isVariableStatement(statement) || ts2.isFunctionDeclaration(statement)) inspect(statement);
|
|
1694
2355
|
}
|
|
1695
2356
|
}
|
|
1696
2357
|
return found;
|
|
@@ -1794,14 +2455,14 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1794
2455
|
return;
|
|
1795
2456
|
}
|
|
1796
2457
|
const text = content.toString("utf8");
|
|
1797
|
-
const syntax =
|
|
2458
|
+
const syntax = ts2.transpileModule(text, {
|
|
1798
2459
|
fileName: path,
|
|
1799
2460
|
reportDiagnostics: true,
|
|
1800
|
-
compilerOptions: { jsx:
|
|
2461
|
+
compilerOptions: { jsx: ts2.JsxEmit.Preserve, target: ts2.ScriptTarget.ESNext, module: ts2.ModuleKind.ESNext }
|
|
1801
2462
|
});
|
|
1802
|
-
if (syntax.diagnostics?.some((diagnostic) => diagnostic.category ===
|
|
2463
|
+
if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
|
|
1803
2464
|
const ast = sourceFile(text, path);
|
|
1804
|
-
if (ast.statements.some((statement) =>
|
|
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");
|
|
1805
2466
|
const edits = [];
|
|
1806
2467
|
function dependency(literal, importDeclaration) {
|
|
1807
2468
|
const specifier = literal.text;
|
|
@@ -1811,10 +2472,10 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1811
2472
|
const localName = importDeclaration?.importClause?.name?.text;
|
|
1812
2473
|
if (localName) {
|
|
1813
2474
|
let renameToProp2 = function(node) {
|
|
1814
|
-
if (
|
|
2475
|
+
if (ts2.isJsxAttribute(node) && node.name.getText(ast) === "to" && ts2.isJsxOpeningLikeElement(node.parent.parent) && node.parent.parent.tagName.getText(ast) === localName) {
|
|
1815
2476
|
edits.push({ start: node.name.getStart(ast), end: node.name.end, value: "href" });
|
|
1816
2477
|
}
|
|
1817
|
-
|
|
2478
|
+
ts2.forEachChild(node, renameToProp2);
|
|
1818
2479
|
};
|
|
1819
2480
|
var renameToProp = renameToProp2;
|
|
1820
2481
|
renameToProp2(ast);
|
|
@@ -1837,15 +2498,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1837
2498
|
edits.push({ start: literal.getStart(ast), end: literal.end, value: JSON.stringify(portableSpecifier(nextPath.startsWith(".") ? nextPath : `./${nextPath}`)) });
|
|
1838
2499
|
}
|
|
1839
2500
|
function inspect(node) {
|
|
1840
|
-
if ((
|
|
1841
|
-
dependency(node.moduleSpecifier,
|
|
2501
|
+
if ((ts2.isImportDeclaration(node) || ts2.isExportDeclaration(node)) && node.moduleSpecifier && ts2.isStringLiteral(node.moduleSpecifier)) {
|
|
2502
|
+
dependency(node.moduleSpecifier, ts2.isImportDeclaration(node) ? node : void 0);
|
|
1842
2503
|
}
|
|
1843
|
-
if (
|
|
2504
|
+
if (ts2.isCallExpression(node) && (node.expression.kind === ts2.SyntaxKind.ImportKeyword || ts2.isIdentifier(node.expression) && node.expression.text === "require")) {
|
|
1844
2505
|
const argument = node.arguments[0];
|
|
1845
|
-
if (node.arguments.length !== 1 || !argument || !
|
|
2506
|
+
if (node.arguments.length !== 1 || !argument || !ts2.isStringLiteralLike(argument)) throw new Error("computed component imports require manual migration");
|
|
1846
2507
|
dependency(argument);
|
|
1847
2508
|
}
|
|
1848
|
-
|
|
2509
|
+
ts2.forEachChild(node, inspect);
|
|
1849
2510
|
}
|
|
1850
2511
|
inspect(ast);
|
|
1851
2512
|
const replaced = applyReplacements(text, edits);
|
|
@@ -1923,15 +2584,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1923
2584
|
function hasExpressionReference(names, options = {}) {
|
|
1924
2585
|
let found = false;
|
|
1925
2586
|
function inspect2(node) {
|
|
1926
|
-
if (
|
|
1927
|
-
|
|
2587
|
+
if (ts2.isIdentifier(node) && names.has(node.text)) found = true;
|
|
2588
|
+
ts2.forEachChild(node, inspect2);
|
|
1928
2589
|
}
|
|
1929
2590
|
walk(tree, (node) => {
|
|
1930
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;
|
|
1931
2592
|
if (node.name && [...names].some((name) => node.name.startsWith(`${name}.`) || options.includeDirectTags && node.name === name)) found = true;
|
|
1932
2593
|
if (options.includeDeclarations !== false && node.type === "mdxjsEsm" && node.value) {
|
|
1933
2594
|
for (const statement of sourceFile(node.value, "expression.tsx").statements) {
|
|
1934
|
-
if (!
|
|
2595
|
+
if (!ts2.isImportDeclaration(statement)) inspect2(statement);
|
|
1935
2596
|
}
|
|
1936
2597
|
}
|
|
1937
2598
|
const expressions = [
|
|
@@ -1960,13 +2621,13 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1960
2621
|
if (node.value === void 0 || node.position?.start.offset === void 0) continue;
|
|
1961
2622
|
const ast = sourceFile(node.value, "inline.tsx");
|
|
1962
2623
|
for (const statement of ast.statements) {
|
|
1963
|
-
if (!
|
|
2624
|
+
if (!ts2.isImportDeclaration(statement)) {
|
|
1964
2625
|
let inspect2 = function(node2) {
|
|
1965
|
-
if (
|
|
1966
|
-
|
|
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);
|
|
1967
2628
|
};
|
|
1968
2629
|
var inspect = inspect2;
|
|
1969
|
-
let unsupportedDependency =
|
|
2630
|
+
let unsupportedDependency = ts2.isExportDeclaration(statement) && !!statement.moduleSpecifier;
|
|
1970
2631
|
inspect2(statement);
|
|
1971
2632
|
if (unsupportedDependency) {
|
|
1972
2633
|
hasUnsupportedImports = true;
|
|
@@ -1980,7 +2641,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
1980
2641
|
continue;
|
|
1981
2642
|
}
|
|
1982
2643
|
const bindings = imports(statement);
|
|
1983
|
-
const rawSpecifier =
|
|
2644
|
+
const rawSpecifier = ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
1984
2645
|
const specifier = rawSpecifier.startsWith("@site/") ? `/${rawSpecifier.slice("@site/".length)}` : rawSpecifier;
|
|
1985
2646
|
if (isScaffoldProvidedImport(specifier)) {
|
|
1986
2647
|
if (specifier === "react" && bindings.length > 0 && bindings.every((binding) => REACT_GLOBALS.has(binding.imported)) && !hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
|
|
@@ -2043,7 +2704,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2043
2704
|
} catch {
|
|
2044
2705
|
}
|
|
2045
2706
|
}
|
|
2046
|
-
if (!bindings.length || statement.importClause?.namedBindings &&
|
|
2707
|
+
if (!bindings.length || statement.importClause?.namedBindings && ts2.isNamespaceImport(statement.importClause.namedBindings)) {
|
|
2047
2708
|
warn("Namespace or side-effect MDX imports require manual registration; the import was preserved.", currentFile);
|
|
2048
2709
|
hasUnsupportedImports = true;
|
|
2049
2710
|
continue;
|
|
@@ -2116,10 +2777,60 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2116
2777
|
}
|
|
2117
2778
|
}
|
|
2118
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
|
+
}
|
|
2119
2830
|
if (droppedMdxDataBindings.size > 0) {
|
|
2120
2831
|
for (const node of esmNodes) {
|
|
2121
2832
|
let stripDroppedSpreads2 = function(inner) {
|
|
2122
|
-
if (
|
|
2833
|
+
if (ts2.isSpreadElement(inner) && ts2.isIdentifier(inner.expression) && droppedMdxDataBindings.has(inner.expression.text)) {
|
|
2123
2834
|
const start = nodeStart + inner.getStart(ast);
|
|
2124
2835
|
let end = nodeStart + inner.end;
|
|
2125
2836
|
const following = nodeText.slice(inner.end).match(/^\s*,/);
|
|
@@ -2127,7 +2838,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2127
2838
|
edits.push({ start, end, value: "" });
|
|
2128
2839
|
return;
|
|
2129
2840
|
}
|
|
2130
|
-
|
|
2841
|
+
ts2.forEachChild(inner, stripDroppedSpreads2);
|
|
2131
2842
|
};
|
|
2132
2843
|
var stripDroppedSpreads = stripDroppedSpreads2;
|
|
2133
2844
|
if (node.value === void 0 || node.position?.start.offset === void 0) continue;
|
|
@@ -2161,7 +2872,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2161
2872
|
}
|
|
2162
2873
|
});
|
|
2163
2874
|
const statefulDeclarationNames = new Set(
|
|
2164
|
-
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))
|
|
2165
2876
|
);
|
|
2166
2877
|
const extracted = [];
|
|
2167
2878
|
for (const candidate of tree.children ?? []) {
|
|
@@ -2180,12 +2891,12 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2180
2891
|
continue;
|
|
2181
2892
|
}
|
|
2182
2893
|
const jsx = content.slice(node.position.start.offset, node.position.end.offset);
|
|
2183
|
-
const result =
|
|
2894
|
+
const result = ts2.transpileModule(`export default function Inline() { return (${jsx}); }`, {
|
|
2184
2895
|
fileName: "inline.jsx",
|
|
2185
|
-
compilerOptions: { jsx:
|
|
2896
|
+
compilerOptions: { jsx: ts2.JsxEmit.Preserve },
|
|
2186
2897
|
reportDiagnostics: true
|
|
2187
2898
|
});
|
|
2188
|
-
if (result.diagnostics?.some((diagnostic) => diagnostic.category ===
|
|
2899
|
+
if (result.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) {
|
|
2189
2900
|
warn("Interactive MDX could not be safely extracted as JSX; source was preserved.", currentFile);
|
|
2190
2901
|
continue;
|
|
2191
2902
|
}
|
|
@@ -2193,15 +2904,15 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2193
2904
|
}
|
|
2194
2905
|
if (extracted.length && declarations.length) {
|
|
2195
2906
|
let bind2 = function(name) {
|
|
2196
|
-
if (
|
|
2197
|
-
else for (const element of name.elements) if (
|
|
2907
|
+
if (ts2.isIdentifier(name)) declared.add(name.text);
|
|
2908
|
+
else for (const element of name.elements) if (ts2.isBindingElement(element)) bind2(element.name);
|
|
2198
2909
|
};
|
|
2199
2910
|
var bind = bind2;
|
|
2200
2911
|
const declared = /* @__PURE__ */ new Set();
|
|
2201
2912
|
for (const declaration of declarations) {
|
|
2202
2913
|
for (const statement of sourceFile(declaration.source, "declaration.tsx").statements) {
|
|
2203
|
-
if ((
|
|
2204
|
-
if (
|
|
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);
|
|
2205
2916
|
}
|
|
2206
2917
|
}
|
|
2207
2918
|
const hasSharedDeclarations = hasExpressionReference(declared, {
|
|
@@ -2362,22 +3073,22 @@ function mergeComponentRegistry(existing, incoming) {
|
|
|
2362
3073
|
const name = `MigratedRegistry${fingerprint}`;
|
|
2363
3074
|
const registryPath = `./migrated-components-${fingerprint}`;
|
|
2364
3075
|
const source = sourceFile(existing, "custom-components.tsx");
|
|
2365
|
-
const alreadyImported = source.statements.some((statement) =>
|
|
3076
|
+
const alreadyImported = source.statements.some((statement) => ts2.isImportDeclaration(statement) && ts2.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
|
|
2366
3077
|
let initializer;
|
|
2367
3078
|
for (const statement of source.statements) {
|
|
2368
|
-
if (!
|
|
3079
|
+
if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
|
|
2369
3080
|
for (const declaration of statement.declarationList.declarations) {
|
|
2370
|
-
if (
|
|
3081
|
+
if (ts2.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
|
|
2371
3082
|
}
|
|
2372
3083
|
}
|
|
2373
|
-
while (initializer && (
|
|
2374
|
-
if (!initializer || !
|
|
3084
|
+
while (initializer && (ts2.isAsExpression(initializer) || ts2.isSatisfiesExpression(initializer) || ts2.isParenthesizedExpression(initializer))) initializer = initializer.expression;
|
|
3085
|
+
if (!initializer || !ts2.isObjectLiteralExpression(initializer)) {
|
|
2375
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.");
|
|
2376
3087
|
}
|
|
2377
3088
|
const content = alreadyImported ? existing : applyReplacements(existing, [
|
|
2378
3089
|
{
|
|
2379
|
-
start: source.statements.filter((statement) =>
|
|
2380
|
-
end: source.statements.filter((statement) =>
|
|
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,
|
|
2381
3092
|
value: `
|
|
2382
3093
|
import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
|
|
2383
3094
|
`
|
|
@@ -2524,7 +3235,7 @@ function projectedHref(value) {
|
|
|
2524
3235
|
return href;
|
|
2525
3236
|
}
|
|
2526
3237
|
function labelFor(value, fallback) {
|
|
2527
|
-
for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "label", "name", "title"]) {
|
|
3238
|
+
for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "item", "label", "name", "title"]) {
|
|
2528
3239
|
if (typeof value[key] === "string" && value[key]) return String(value[key]);
|
|
2529
3240
|
}
|
|
2530
3241
|
return fallback;
|
|
@@ -2596,29 +3307,40 @@ function readMintlifyConfig(repositoryRoot) {
|
|
|
2596
3307
|
const raw = readMintlifyJson(configPath, repositoryRoot);
|
|
2597
3308
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
2598
3309
|
}
|
|
3310
|
+
function specSource(value) {
|
|
3311
|
+
const source = typeof value === "string" ? value : objectValue(value)?.source;
|
|
3312
|
+
return typeof source === "string" && source.trim() ? source.trim() : void 0;
|
|
3313
|
+
}
|
|
3314
|
+
var hasNavigationChildren = (node) => ["groups", "pages"].some((key) => Array.isArray(node[key]) && node[key].length > 0);
|
|
2599
3315
|
function mintlifyNavigationApiReferences(config) {
|
|
2600
3316
|
const navigation = objectValue(config.navigation) ?? config;
|
|
2601
3317
|
const references = [];
|
|
2602
3318
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus", "languages"];
|
|
2603
3319
|
const nestedKeys = ["groups", "pages"];
|
|
2604
|
-
function
|
|
2605
|
-
const node = withFlattenedProductGroups(rawNode);
|
|
3320
|
+
function collect(node, tabLabel, sibling) {
|
|
2606
3321
|
for (const kind of ["openapi", "asyncapi"]) {
|
|
2607
|
-
const value = node[kind];
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
continue;
|
|
2611
|
-
}
|
|
2612
|
-
const object = objectValue(value);
|
|
2613
|
-
if (object && typeof object.source === "string" && object.source.trim()) {
|
|
3322
|
+
const value = specSource(node[kind]);
|
|
3323
|
+
const directory = objectValue(node[kind])?.directory;
|
|
3324
|
+
if (value) {
|
|
2614
3325
|
references.push({
|
|
2615
|
-
value
|
|
3326
|
+
value,
|
|
2616
3327
|
kind,
|
|
2617
3328
|
tabLabel,
|
|
2618
|
-
...
|
|
3329
|
+
...sibling,
|
|
3330
|
+
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {}
|
|
2619
3331
|
});
|
|
2620
3332
|
}
|
|
2621
3333
|
}
|
|
3334
|
+
}
|
|
3335
|
+
const siblingSources = /* @__PURE__ */ new Map();
|
|
3336
|
+
function uniqueSiblingLabel(label, source) {
|
|
3337
|
+
let candidate = label;
|
|
3338
|
+
for (let n = 2; siblingSources.has(candidate) && siblingSources.get(candidate) !== source; n++) candidate = `${label} (${n})`;
|
|
3339
|
+
siblingSources.set(candidate, source);
|
|
3340
|
+
return candidate;
|
|
3341
|
+
}
|
|
3342
|
+
function visitChildren(rawNode, tabLabel) {
|
|
3343
|
+
const node = withFlattenedProductGroups(rawNode);
|
|
2622
3344
|
for (const key of containerKeys) {
|
|
2623
3345
|
const entries = node[key];
|
|
2624
3346
|
if (!Array.isArray(entries)) continue;
|
|
@@ -2637,10 +3359,45 @@ function mintlifyNavigationApiReferences(config) {
|
|
|
2637
3359
|
if (object) visit(object, tabLabel);
|
|
2638
3360
|
}
|
|
2639
3361
|
}
|
|
3362
|
+
if (Array.isArray(node.menu)) {
|
|
3363
|
+
const tab = tabLabel ?? "Documentation";
|
|
3364
|
+
const items = node.menu.flatMap((entry) => {
|
|
3365
|
+
const object = objectValue(entry);
|
|
3366
|
+
return object ? [object] : [];
|
|
3367
|
+
});
|
|
3368
|
+
const apiItems = items.filter((item) => specSource(item.openapi) || specSource(item.asyncapi));
|
|
3369
|
+
const soleContent = apiItems.length === 1 && !hasNavigationChildren(node) && !items.some(hasNavigationChildren);
|
|
3370
|
+
items.forEach((item, index) => {
|
|
3371
|
+
if (soleContent && apiItems[0] === item) collect(item, tab);
|
|
3372
|
+
else if (apiItems.includes(item)) {
|
|
3373
|
+
const label = uniqueSiblingLabel(
|
|
3374
|
+
`${tab}: ${labelFor(item, `Menu item ${index + 1}`)}`,
|
|
3375
|
+
`${specSource(item.openapi) ?? ""}|${specSource(item.asyncapi) ?? ""}`
|
|
3376
|
+
);
|
|
3377
|
+
const icon = iconName(item.icon);
|
|
3378
|
+
collect(item, label, { parentTab: tab, ...icon ? { icon } : {}, ...item.hidden === true ? { hidden: true } : {} });
|
|
3379
|
+
}
|
|
3380
|
+
visitChildren(item, tabLabel);
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
function visit(node, tabLabel) {
|
|
3385
|
+
collect(node, tabLabel);
|
|
3386
|
+
visitChildren(node, tabLabel);
|
|
2640
3387
|
}
|
|
2641
3388
|
visit(navigation, void 0);
|
|
2642
3389
|
return references;
|
|
2643
3390
|
}
|
|
3391
|
+
function insertApiTab(tabs, tab, parentTab) {
|
|
3392
|
+
let index = -1;
|
|
3393
|
+
if (parentTab) {
|
|
3394
|
+
tabs.forEach((candidate, position) => {
|
|
3395
|
+
if (candidate.tab === parentTab || candidate.tab.startsWith(`${parentTab}: `)) index = position;
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
tabs.splice(index === -1 ? tabs.length : index + 1, 0, tab);
|
|
3399
|
+
return tabs;
|
|
3400
|
+
}
|
|
2644
3401
|
function normalizePageRef(value, pathPrefix = "") {
|
|
2645
3402
|
if (/^(?:https?:)?\/\//i.test(value) || value.startsWith("#")) return null;
|
|
2646
3403
|
let ref = localReference(value).split("?", 1)[0].replace(/^\/+/, "");
|
|
@@ -2726,6 +3483,65 @@ function convertPage(value, context) {
|
|
|
2726
3483
|
}
|
|
2727
3484
|
return null;
|
|
2728
3485
|
}
|
|
3486
|
+
var KNOWN_NAVIGATION_KEYS = /* @__PURE__ */ new Set([
|
|
3487
|
+
"tabs",
|
|
3488
|
+
"anchors",
|
|
3489
|
+
"products",
|
|
3490
|
+
"dropdowns",
|
|
3491
|
+
"versions",
|
|
3492
|
+
"menus",
|
|
3493
|
+
"languages",
|
|
3494
|
+
"productGroups",
|
|
3495
|
+
"groups",
|
|
3496
|
+
"pages",
|
|
3497
|
+
"menu",
|
|
3498
|
+
"global"
|
|
3499
|
+
]);
|
|
3500
|
+
function convertMenuItems(items, context) {
|
|
3501
|
+
return items.flatMap((value, index) => {
|
|
3502
|
+
const item = objectValue(value);
|
|
3503
|
+
if (!item) return [];
|
|
3504
|
+
const label = labelFor(item, `Menu item ${index + 1}`);
|
|
3505
|
+
if (typeof item.description === "string" && item.description.trim()) {
|
|
3506
|
+
warnOnce(
|
|
3507
|
+
context,
|
|
3508
|
+
`menu-item-description:${label}`,
|
|
3509
|
+
`Mintlify menu item "${label}" has a description, which Thally groups cannot display; it was omitted.`
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
const hasUnsourcedSpec = Boolean(item.openapi || item.asyncapi) && !specSource(item.openapi) && !specSource(item.asyncapi);
|
|
3513
|
+
if (hasUnsourcedSpec) {
|
|
3514
|
+
warnOnce(
|
|
3515
|
+
context,
|
|
3516
|
+
`menu-item-spec:${label}`,
|
|
3517
|
+
`Mintlify menu item "${label}" has an openapi/asyncapi value without a "source", so its API reference was not migrated.`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const children = convertNavigationValues([
|
|
3521
|
+
...Array.isArray(item.groups) ? item.groups : [],
|
|
3522
|
+
...Array.isArray(item.pages) ? item.pages : []
|
|
3523
|
+
], context);
|
|
3524
|
+
if (typeof item.href === "string" && item.href) {
|
|
3525
|
+
warnOnce(
|
|
3526
|
+
context,
|
|
3527
|
+
`menu-item-href:${label}`,
|
|
3528
|
+
`Mintlify menu item "${label}" links to ${item.href}, which cannot be represented as a Thally group; the link was omitted.`
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
if (children.length === 0) {
|
|
3532
|
+
if (!specSource(item.openapi) && !specSource(item.asyncapi) && !hasUnsourcedSpec) {
|
|
3533
|
+
warnOnce(context, `menu-item-empty:${label}`, `Mintlify menu item "${label}" has no projectable pages and was omitted.`);
|
|
3534
|
+
}
|
|
3535
|
+
return [];
|
|
3536
|
+
}
|
|
3537
|
+
return [{
|
|
3538
|
+
group: label,
|
|
3539
|
+
...iconName(item.icon) ? { icon: iconName(item.icon) } : {},
|
|
3540
|
+
...item.hidden === true ? { hidden: true } : {},
|
|
3541
|
+
pages: children
|
|
3542
|
+
}];
|
|
3543
|
+
});
|
|
3544
|
+
}
|
|
2729
3545
|
function convertNavigationValues(values, context) {
|
|
2730
3546
|
return values.flatMap((value) => {
|
|
2731
3547
|
const page = convertPage(value, context);
|
|
@@ -2737,6 +3553,13 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
2737
3553
|
if (!rawContainer) return [];
|
|
2738
3554
|
const container = withFlattenedProductGroups(rawContainer);
|
|
2739
3555
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
|
|
3556
|
+
for (const key of Object.keys(container)) {
|
|
3557
|
+
if (KNOWN_NAVIGATION_KEYS.has(key)) continue;
|
|
3558
|
+
const entries = container[key];
|
|
3559
|
+
if (Array.isArray(entries) && entries.some((entry) => objectValue(entry))) {
|
|
3560
|
+
warnOnce(context, `unknown-container:${key}`, `Mintlify navigation key "${key}" is not supported and its contents were not projected.`);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
2740
3563
|
for (const key of containerKeys) {
|
|
2741
3564
|
if (!Array.isArray(container[key])) continue;
|
|
2742
3565
|
const entries = [...container[key]];
|
|
@@ -2748,7 +3571,7 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
2748
3571
|
if (!object) return [];
|
|
2749
3572
|
const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
|
|
2750
3573
|
const href = projectedHref(object.href);
|
|
2751
|
-
const hasNestedContainers = containerKeys.some((containerKey) => Array.isArray(object[containerKey]));
|
|
3574
|
+
const hasNestedContainers = [...containerKeys, "menu"].some((containerKey) => Array.isArray(object[containerKey]));
|
|
2752
3575
|
if (href && !object.pages && !object.groups && !hasNestedContainers) {
|
|
2753
3576
|
return [{
|
|
2754
3577
|
tab,
|
|
@@ -2780,6 +3603,11 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
2780
3603
|
return [];
|
|
2781
3604
|
});
|
|
2782
3605
|
if (tabs.length > 0) {
|
|
3606
|
+
for (const sibling of ["groups", "pages", "menu"]) {
|
|
3607
|
+
if (Array.isArray(container[sibling]) && container[sibling].length > 0) {
|
|
3608
|
+
warnOnce(context, `container-sibling:${key}:${sibling}`, `Mintlify "${sibling}" next to "${key}" was not projected; only the "${key}" containers were kept.`);
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
2783
3611
|
if (depth === 0 && trace) trace.rootContainerKind = key;
|
|
2784
3612
|
return tabs;
|
|
2785
3613
|
}
|
|
@@ -2792,7 +3620,10 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
2792
3620
|
...rawGroups,
|
|
2793
3621
|
...rawPages
|
|
2794
3622
|
];
|
|
2795
|
-
const children =
|
|
3623
|
+
const children = [
|
|
3624
|
+
...convertNavigationValues(values, context),
|
|
3625
|
+
...Array.isArray(container.menu) ? convertMenuItems(container.menu, context) : []
|
|
3626
|
+
];
|
|
2796
3627
|
return children.length > 0 ? [{
|
|
2797
3628
|
tab: fallbackTab,
|
|
2798
3629
|
...hasRootNodes ? { pages: children } : { groups: children },
|
|
@@ -2842,7 +3673,7 @@ function projectedGlobalNavigationLinks(value) {
|
|
|
2842
3673
|
const label = labelFor(item, "");
|
|
2843
3674
|
if (label) links.push({ label, href });
|
|
2844
3675
|
}
|
|
2845
|
-
for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus"]) {
|
|
3676
|
+
for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus", "menu"]) {
|
|
2846
3677
|
if (Array.isArray(item[key])) visit(item[key]);
|
|
2847
3678
|
}
|
|
2848
3679
|
};
|
|
@@ -4676,6 +5507,10 @@ var MAX_PAGE_BYTES = 2e6;
|
|
|
4676
5507
|
var MAX_ASSET_BYTES = 25e6;
|
|
4677
5508
|
var MAX_TOTAL_ASSET_BYTES = 5e8;
|
|
4678
5509
|
var GIT_LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
|
|
5510
|
+
function listAssetPaths(paths) {
|
|
5511
|
+
const shown = paths.slice(0, 5).join(", ");
|
|
5512
|
+
return paths.length > 5 ? `${shown}, and ${paths.length - 5} more` : shown;
|
|
5513
|
+
}
|
|
4679
5514
|
function isGitLfsPointer(content) {
|
|
4680
5515
|
return content.length < 1024 && content.toString("utf8", 0, GIT_LFS_POINTER_PREFIX.length) === GIT_LFS_POINTER_PREFIX;
|
|
4681
5516
|
}
|
|
@@ -5099,17 +5934,23 @@ function parseGitHubRepositoryUrl(rawUrl) {
|
|
|
5099
5934
|
}
|
|
5100
5935
|
var CLONE_RETRY_ATTEMPTS = 3;
|
|
5101
5936
|
var CLONE_RETRY_DELAY_MS = 1e3;
|
|
5102
|
-
var
|
|
5937
|
+
var DEFAULT_CLONE_IDLE_TIMEOUT_MS = 2 * 6e4;
|
|
5938
|
+
var MAX_GIT_PROCESS_MS = 60 * 6e4;
|
|
5939
|
+
var HARD_TIMEOUT_REASON = `still running after ${MAX_GIT_PROCESS_MS / 6e4} minutes`;
|
|
5940
|
+
var GIT_EXIT_DRAIN_MS = 2e3;
|
|
5103
5941
|
var RETRYABLE_CLONE_ERROR = /RPC failed|Recv failure|early EOF|curl \d+|Could not resolve host|Connection (?:reset|refused|timed out)|The remote end hung up|SSL[_ ]?(?:read|connect|write) error|timed out|network is unreachable/i;
|
|
5104
5942
|
function delay(ms) {
|
|
5105
5943
|
return new Promise((resolve3) => {
|
|
5106
5944
|
setTimeout(resolve3, ms);
|
|
5107
5945
|
});
|
|
5108
5946
|
}
|
|
5109
|
-
function
|
|
5947
|
+
function gitIdleTimeoutMs() {
|
|
5110
5948
|
const raw = process.env.THALLY_MIGRATE_CLONE_TIMEOUT_MS;
|
|
5111
5949
|
const parsed = raw ? Number(raw) : NaN;
|
|
5112
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed :
|
|
5950
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_IDLE_TIMEOUT_MS;
|
|
5951
|
+
}
|
|
5952
|
+
function gitErrorTail(stderr) {
|
|
5953
|
+
return stderr.split(/[\r\n]+/).map((line) => line.trim()).filter((line) => line && !/^(?:remote: )?[A-Za-z][A-Za-z ]*:\s+\d+%/.test(line)).slice(-5).join("\n");
|
|
5113
5954
|
}
|
|
5114
5955
|
function runGit(args, options) {
|
|
5115
5956
|
return new Promise((resolve3, reject) => {
|
|
@@ -5119,24 +5960,66 @@ function runGit(args, options) {
|
|
|
5119
5960
|
env: { ...process.env, ...options.env }
|
|
5120
5961
|
});
|
|
5121
5962
|
let stderr = "";
|
|
5122
|
-
let
|
|
5123
|
-
|
|
5124
|
-
|
|
5963
|
+
let timeoutReason = "";
|
|
5964
|
+
let settled = false;
|
|
5965
|
+
let exitCode;
|
|
5966
|
+
let drainTimer;
|
|
5967
|
+
const idleMs = gitIdleTimeoutMs();
|
|
5968
|
+
const idleMessage = `no progress for ${idleMs < 1e3 ? `${idleMs}ms` : `${Math.round(idleMs / 1e3)}s`}`;
|
|
5969
|
+
const clearTimers = () => {
|
|
5970
|
+
clearTimeout(idleTimer);
|
|
5971
|
+
clearTimeout(hardTimer);
|
|
5972
|
+
clearTimeout(drainTimer);
|
|
5973
|
+
};
|
|
5974
|
+
const finish = (code) => {
|
|
5975
|
+
if (settled) return;
|
|
5976
|
+
settled = true;
|
|
5977
|
+
clearTimers();
|
|
5978
|
+
child.stderr.destroy();
|
|
5979
|
+
if (code === 0) resolve3();
|
|
5980
|
+
else if (timeoutReason) {
|
|
5981
|
+
const hint = timeoutReason === HARD_TIMEOUT_REASON ? "" : "; on a very slow link, raise THALLY_MIGRATE_CLONE_TIMEOUT_MS (milliseconds of allowed silence)";
|
|
5982
|
+
reject(new Error(`${options.label}: timed out (${timeoutReason}) and was killed. Check your network connection and try again${hint}.`));
|
|
5983
|
+
} else reject(new Error(`${options.label}: ${gitErrorTail(stderr) || `git exited ${code}`}`));
|
|
5984
|
+
};
|
|
5985
|
+
const kill = (reason) => {
|
|
5986
|
+
if (exitCode !== void 0) {
|
|
5987
|
+
finish(exitCode);
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
timeoutReason = reason;
|
|
5125
5991
|
child.kill("SIGKILL");
|
|
5126
|
-
}
|
|
5992
|
+
};
|
|
5993
|
+
let idleTimer = setTimeout(() => {
|
|
5994
|
+
kill(idleMessage);
|
|
5995
|
+
}, idleMs);
|
|
5996
|
+
const hardTimer = setTimeout(() => {
|
|
5997
|
+
kill(HARD_TIMEOUT_REASON);
|
|
5998
|
+
}, MAX_GIT_PROCESS_MS);
|
|
5127
5999
|
child.stderr.setEncoding("utf8");
|
|
5128
6000
|
child.stderr.on("data", (chunk) => {
|
|
5129
|
-
|
|
6001
|
+
stderr = (stderr + chunk).slice(-16e3);
|
|
6002
|
+
if (exitCode !== void 0 || settled) return;
|
|
6003
|
+
clearTimeout(idleTimer);
|
|
6004
|
+
idleTimer = setTimeout(() => {
|
|
6005
|
+
kill(idleMessage);
|
|
6006
|
+
}, idleMs);
|
|
5130
6007
|
});
|
|
5131
6008
|
child.on("error", (error) => {
|
|
5132
|
-
|
|
6009
|
+
if (settled) return;
|
|
6010
|
+
settled = true;
|
|
6011
|
+
clearTimers();
|
|
5133
6012
|
reject(error);
|
|
5134
6013
|
});
|
|
5135
|
-
child.on("close",
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
6014
|
+
child.on("close", finish);
|
|
6015
|
+
child.on("exit", (code) => {
|
|
6016
|
+
exitCode = code;
|
|
6017
|
+
clearTimeout(idleTimer);
|
|
6018
|
+
clearTimeout(hardTimer);
|
|
6019
|
+
if (timeoutReason) finish(code);
|
|
6020
|
+
else drainTimer = setTimeout(() => {
|
|
6021
|
+
finish(code);
|
|
6022
|
+
}, GIT_EXIT_DRAIN_MS);
|
|
5140
6023
|
});
|
|
5141
6024
|
});
|
|
5142
6025
|
}
|
|
@@ -5152,7 +6035,7 @@ var LFS_FILTER_OVERRIDE_ENV = {
|
|
|
5152
6035
|
GIT_CONFIG_VALUE_3: "false"
|
|
5153
6036
|
};
|
|
5154
6037
|
function cloneOnce(source, targetDir) {
|
|
5155
|
-
const args = ["clone", "--depth", "1", "--single-branch"];
|
|
6038
|
+
const args = ["clone", "--progress", "--depth", "1", "--single-branch"];
|
|
5156
6039
|
if (source.branch !== "HEAD") args.push("--branch", source.branch);
|
|
5157
6040
|
args.push("--", source.cloneUrl, targetDir);
|
|
5158
6041
|
return runGit(args, {
|
|
@@ -5201,6 +6084,7 @@ async function initSubmodules(targetDir, warnings) {
|
|
|
5201
6084
|
"submodule",
|
|
5202
6085
|
"update",
|
|
5203
6086
|
"--init",
|
|
6087
|
+
"--progress",
|
|
5204
6088
|
"--depth",
|
|
5205
6089
|
"1",
|
|
5206
6090
|
"--",
|
|
@@ -5438,6 +6322,13 @@ function classifyApiSpec(path) {
|
|
|
5438
6322
|
if (!topLevel) return "unknown";
|
|
5439
6323
|
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
5440
6324
|
}
|
|
6325
|
+
var SPEC_DIRECTORY = "openapi";
|
|
6326
|
+
function specAssetPath(filename) {
|
|
6327
|
+
return `${SPEC_DIRECTORY}/${filename}`;
|
|
6328
|
+
}
|
|
6329
|
+
function specAsset(filename, content) {
|
|
6330
|
+
return { path: specAssetPath(filename), content, projectRelative: true };
|
|
6331
|
+
}
|
|
5441
6332
|
function mintlifyTopLevelApiReferences(config) {
|
|
5442
6333
|
const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
|
|
5443
6334
|
if (!api) return [];
|
|
@@ -5458,7 +6349,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
5458
6349
|
const seen = /* @__PURE__ */ new Set();
|
|
5459
6350
|
const specs = [];
|
|
5460
6351
|
for (const reference of references) {
|
|
5461
|
-
const dedupeKey = `${reference.kind}:${reference.value}`;
|
|
6352
|
+
const dedupeKey = `${reference.kind}:${reference.value}:${reference.tabLabel ?? ""}`;
|
|
5462
6353
|
if (seen.has(dedupeKey)) continue;
|
|
5463
6354
|
seen.add(dedupeKey);
|
|
5464
6355
|
const tabSuffix = reference.tabLabel ? ` (tab "${reference.tabLabel}")` : "";
|
|
@@ -5470,7 +6361,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
5470
6361
|
continue;
|
|
5471
6362
|
}
|
|
5472
6363
|
if (/^https:\/\//i.test(reference.value)) {
|
|
5473
|
-
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {} });
|
|
6364
|
+
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {}, ...reference.parentTab ? { parentTab: reference.parentTab } : {}, ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {} });
|
|
5474
6365
|
warnings.push({
|
|
5475
6366
|
code: "unsupported-config",
|
|
5476
6367
|
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
@@ -5501,15 +6392,19 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
5501
6392
|
filename: basename3(match.relativePath),
|
|
5502
6393
|
content: readFileSync5(match.absolutePath),
|
|
5503
6394
|
tabLabel: reference.tabLabel,
|
|
6395
|
+
parentTab: reference.parentTab,
|
|
6396
|
+
icon: reference.icon,
|
|
6397
|
+
hidden: reference.hidden,
|
|
5504
6398
|
sourcePath: match.relativePath,
|
|
5505
6399
|
directory: reference.directory
|
|
5506
6400
|
});
|
|
5507
6401
|
}
|
|
5508
6402
|
const filenameOwners = /* @__PURE__ */ new Map();
|
|
6403
|
+
const filenameSources = /* @__PURE__ */ new Map();
|
|
5509
6404
|
for (const spec of specs) {
|
|
5510
6405
|
const groupKey = spec.tabLabel ?? "\0default";
|
|
5511
6406
|
const owner = filenameOwners.get(spec.filename);
|
|
5512
|
-
if (owner !== void 0 && owner !== groupKey) {
|
|
6407
|
+
if (owner !== void 0 && owner !== groupKey && filenameSources.get(spec.filename) !== spec.sourcePath) {
|
|
5513
6408
|
let disambiguated = spec.sourcePath.replace(/\//g, "-");
|
|
5514
6409
|
while (filenameOwners.has(disambiguated) && filenameOwners.get(disambiguated) !== groupKey) {
|
|
5515
6410
|
disambiguated = `${groupKey.replace(/\W+/g, "-")}-${disambiguated}`;
|
|
@@ -5517,6 +6412,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
5517
6412
|
spec.filename = disambiguated;
|
|
5518
6413
|
}
|
|
5519
6414
|
filenameOwners.set(spec.filename, groupKey);
|
|
6415
|
+
filenameSources.set(spec.filename, spec.sourcePath);
|
|
5520
6416
|
}
|
|
5521
6417
|
return specs;
|
|
5522
6418
|
}
|
|
@@ -5571,7 +6467,7 @@ function apiOperationLinkMap(sources, docsConfig) {
|
|
|
5571
6467
|
const prefixLandings = /* @__PURE__ */ new Map();
|
|
5572
6468
|
const apiTabs = docsConfig.tabs.filter((tab) => !tab.hidden && tab.api);
|
|
5573
6469
|
apiTabs.forEach((tab, index) => {
|
|
5574
|
-
const source = sources.find((entry) => tab.api?.source ===
|
|
6470
|
+
const source = sources.find((entry) => tab.api?.source === specAssetPath(entry.filename));
|
|
5575
6471
|
if (!source || !source.prefix) return;
|
|
5576
6472
|
const specId = index === 0 ? "default" : slugifyApiSpecId(tab.tab);
|
|
5577
6473
|
const prefix = source.prefix.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
@@ -5773,18 +6669,18 @@ function interpolateSnippet(snippet, attributes, children) {
|
|
|
5773
6669
|
}).join("\n");
|
|
5774
6670
|
}
|
|
5775
6671
|
function snippetComponentBody(source, componentName) {
|
|
5776
|
-
const parsed =
|
|
6672
|
+
const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
5777
6673
|
for (const statement of parsed.statements) {
|
|
5778
|
-
if (!
|
|
6674
|
+
if (!ts3.isVariableStatement(statement)) continue;
|
|
5779
6675
|
for (const declaration of statement.declarationList.declarations) {
|
|
5780
|
-
if (!
|
|
6676
|
+
if (!ts3.isIdentifier(declaration.name) || declaration.name.text !== componentName) continue;
|
|
5781
6677
|
const initializer = declaration.initializer;
|
|
5782
|
-
if (!initializer || !
|
|
5783
|
-
const returned =
|
|
6678
|
+
if (!initializer || !ts3.isArrowFunction(initializer)) continue;
|
|
6679
|
+
const returned = ts3.isBlock(initializer.body) ? initializer.body.statements.find(ts3.isReturnStatement)?.expression : initializer.body;
|
|
5784
6680
|
if (!returned) continue;
|
|
5785
6681
|
let expression = returned;
|
|
5786
|
-
while (
|
|
5787
|
-
if (
|
|
6682
|
+
while (ts3.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
6683
|
+
if (ts3.isJsxElement(expression) || ts3.isJsxSelfClosingElement(expression) || ts3.isJsxFragment(expression)) {
|
|
5788
6684
|
return expression.getText(parsed);
|
|
5789
6685
|
}
|
|
5790
6686
|
}
|
|
@@ -5792,30 +6688,31 @@ function snippetComponentBody(source, componentName) {
|
|
|
5792
6688
|
return source;
|
|
5793
6689
|
}
|
|
5794
6690
|
function statefulSnippetDeclaration(source, componentName) {
|
|
5795
|
-
const parsed =
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
6691
|
+
const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
6692
|
+
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);
|
|
6693
|
+
if (parsed.statements.length === 0 || !parsed.statements.every(isEsm)) return null;
|
|
6694
|
+
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);
|
|
6695
|
+
if (!statement || !ts3.isVariableStatement(statement)) return null;
|
|
5799
6696
|
const declaration = statement.declarationList.declarations[0];
|
|
5800
|
-
if (!
|
|
6697
|
+
if (!declaration.initializer || !ts3.isArrowFunction(declaration.initializer) || !ts3.isBlock(declaration.initializer.body)) return null;
|
|
5801
6698
|
const body = declaration.initializer.body.statements;
|
|
5802
|
-
if (body.length < 2 || !body.some(
|
|
6699
|
+
if (body.length < 2 || !body.some(ts3.isReturnStatement)) return null;
|
|
5803
6700
|
return source.trim();
|
|
5804
6701
|
}
|
|
5805
6702
|
function staticNamedSnippetValues(source) {
|
|
5806
6703
|
const values = /* @__PURE__ */ new Map();
|
|
5807
|
-
const parsed =
|
|
6704
|
+
const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
5808
6705
|
for (const statement of parsed.statements) {
|
|
5809
|
-
if (!
|
|
6706
|
+
if (!ts3.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword)) continue;
|
|
5810
6707
|
for (const declaration of statement.declarationList.declarations) {
|
|
5811
|
-
if (!
|
|
6708
|
+
if (!ts3.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
5812
6709
|
const expression = declaration.initializer;
|
|
5813
|
-
if (
|
|
6710
|
+
if (ts3.isStringLiteral(expression) || ts3.isNoSubstitutionTemplateLiteral(expression)) {
|
|
5814
6711
|
values.set(declaration.name.text, JSON.stringify(expression.text));
|
|
5815
|
-
} else if (
|
|
6712
|
+
} else if (ts3.isNumericLiteral(expression)) {
|
|
5816
6713
|
values.set(declaration.name.text, expression.text);
|
|
5817
|
-
} else if (expression.kind ===
|
|
5818
|
-
values.set(declaration.name.text, expression.kind ===
|
|
6714
|
+
} else if (expression.kind === ts3.SyntaxKind.TrueKeyword || expression.kind === ts3.SyntaxKind.FalseKeyword) {
|
|
6715
|
+
values.set(declaration.name.text, expression.kind === ts3.SyntaxKind.TrueKeyword ? "true" : "false");
|
|
5819
6716
|
}
|
|
5820
6717
|
}
|
|
5821
6718
|
}
|
|
@@ -5970,7 +6867,15 @@ function preserveLinkedAnchors(pages) {
|
|
|
5970
6867
|
const key = comparable(cell.text);
|
|
5971
6868
|
candidateIndexes.set(key, [...candidateIndexes.get(key) ?? [], { index: cell.index, kind: "table" }]);
|
|
5972
6869
|
}
|
|
5973
|
-
|
|
6870
|
+
const headingSlugs = /* @__PURE__ */ new Map();
|
|
6871
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
6872
|
+
for (const heading of headings) {
|
|
6873
|
+
const slug2 = heading.text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
6874
|
+
existing.add(slug2);
|
|
6875
|
+
const seen = slugCounts.get(slug2) ?? 0;
|
|
6876
|
+
slugCounts.set(slug2, seen + 1);
|
|
6877
|
+
if (slug2) headingSlugs.set(seen === 0 ? slug2 : `${slug2}-${seen}`, heading.index);
|
|
6878
|
+
}
|
|
5974
6879
|
const headingAliases = /* @__PURE__ */ new Map();
|
|
5975
6880
|
const tableAliases = /* @__PURE__ */ new Map();
|
|
5976
6881
|
for (const fragment of fragments) {
|
|
@@ -5984,6 +6889,14 @@ function preserveLinkedAnchors(pages) {
|
|
|
5984
6889
|
const selections = linkIndexes.map((index2) => matches.filter((candidate) => candidate.index < index2).at(-1));
|
|
5985
6890
|
if (selections.length && selections.every((candidate) => candidate?.index === selections[0]?.index)) match = selections[0];
|
|
5986
6891
|
}
|
|
6892
|
+
if (!match && matches.length > 1) {
|
|
6893
|
+
const headingMatches = matches.filter((candidate) => candidate.kind === "heading");
|
|
6894
|
+
if (headingMatches.length === 1) match = headingMatches[0];
|
|
6895
|
+
}
|
|
6896
|
+
if (!match) {
|
|
6897
|
+
const index2 = headingSlugs.get(fragment.toLowerCase());
|
|
6898
|
+
if (index2 !== void 0) match = { index: index2, kind: "heading" };
|
|
6899
|
+
}
|
|
5987
6900
|
if (!match) continue;
|
|
5988
6901
|
const { index, kind } = match;
|
|
5989
6902
|
const aliases = kind === "heading" ? headingAliases : tableAliases;
|
|
@@ -6231,8 +7144,17 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
6231
7144
|
result = result.replace(SNIPPET_TAG_PATTERN, (_tag, doubleQuoted, singleQuoted) => {
|
|
6232
7145
|
const filePath = doubleQuoted ?? singleQuoted;
|
|
6233
7146
|
try {
|
|
6234
|
-
const candidate =
|
|
6235
|
-
|
|
7147
|
+
const candidate = [
|
|
7148
|
+
() => resolveWithin(siteRoot, `snippets/${filePath}`),
|
|
7149
|
+
() => resolveSnippetPath(filePath, currentFile, repositoryRoot, siteRoot)
|
|
7150
|
+
].map((resolveCandidate) => {
|
|
7151
|
+
try {
|
|
7152
|
+
return resolveCandidate();
|
|
7153
|
+
} catch {
|
|
7154
|
+
return void 0;
|
|
7155
|
+
}
|
|
7156
|
+
}).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
|
|
7157
|
+
if (!candidate) throw new Error("file not found");
|
|
6236
7158
|
return inlineMdxSnippets(
|
|
6237
7159
|
withoutFrontmatter(readFileSync5(candidate, "utf8")),
|
|
6238
7160
|
candidate,
|
|
@@ -6254,32 +7176,37 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
6254
7176
|
if (preservedDeclarations.size > 0) {
|
|
6255
7177
|
const frontmatter = result.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
6256
7178
|
const prefix = frontmatter?.[0] ?? "";
|
|
6257
|
-
result = `${prefix}${[...preservedDeclarations.values()].join("\n\n")}
|
|
7179
|
+
result = `${prefix}${[...new Set(preservedDeclarations.values())].join("\n\n")}
|
|
6258
7180
|
|
|
6259
7181
|
${result.slice(prefix.length)}`;
|
|
6260
7182
|
}
|
|
6261
7183
|
return depth === 0 ? hoistMdxImports(result) : result;
|
|
6262
7184
|
}
|
|
6263
7185
|
function injectOpenApiSpecs(config, specs, warnings) {
|
|
6264
|
-
|
|
7186
|
+
const tabs = config.tabs.map((tab) => ({ ...tab }));
|
|
6265
7187
|
for (const spec of specs) {
|
|
6266
7188
|
const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
|
|
6267
7189
|
if (apiTab) {
|
|
6268
7190
|
if (apiTab.api && warnings) {
|
|
6269
7191
|
warnings.push({
|
|
6270
7192
|
code: "unsupported-config",
|
|
6271
|
-
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "
|
|
7193
|
+
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "${specAssetPath(spec.filename)}" was not added. Put it in its own tab to include it.`
|
|
6272
7194
|
});
|
|
6273
7195
|
continue;
|
|
6274
7196
|
}
|
|
6275
7197
|
apiTab.api = {
|
|
6276
|
-
source:
|
|
7198
|
+
source: specAssetPath(spec.filename),
|
|
6277
7199
|
// An API-only tab needs generated endpoint navigation; an authored
|
|
6278
7200
|
// page tab keeps its own groups alongside the bound spec.
|
|
6279
7201
|
...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
|
|
6280
7202
|
};
|
|
6281
7203
|
} else {
|
|
6282
|
-
tabs
|
|
7204
|
+
insertApiTab(tabs, {
|
|
7205
|
+
tab: spec.tabLabel ?? "API Reference",
|
|
7206
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
7207
|
+
...spec.hidden ? { hidden: true } : {},
|
|
7208
|
+
api: { source: specAssetPath(spec.filename) }
|
|
7209
|
+
}, spec.parentTab);
|
|
6283
7210
|
}
|
|
6284
7211
|
}
|
|
6285
7212
|
return { ...config, tabs };
|
|
@@ -6658,9 +7585,9 @@ function migrateRepository(options) {
|
|
|
6658
7585
|
});
|
|
6659
7586
|
continue;
|
|
6660
7587
|
}
|
|
6661
|
-
const
|
|
6662
|
-
if (hasAnyFunctionValuedProp(page.body,
|
|
6663
|
-
if (propsTargetExtractedClientComponent(page.body,
|
|
7588
|
+
const declaredNames2 = functionDeclaredNames(page.body);
|
|
7589
|
+
if (hasAnyFunctionValuedProp(page.body, declaredNames2)) {
|
|
7590
|
+
if (propsTargetExtractedClientComponent(page.body, declaredNames2)) {
|
|
6664
7591
|
skipped++;
|
|
6665
7592
|
warnings.push({
|
|
6666
7593
|
code: "skipped-file",
|
|
@@ -6777,28 +7704,35 @@ function migrateRepository(options) {
|
|
|
6777
7704
|
...assetCandidates.filter((candidate) => !isReferenced(candidate))
|
|
6778
7705
|
];
|
|
6779
7706
|
let totalAssetBytes = 0;
|
|
7707
|
+
const overBudgetAssets = [];
|
|
7708
|
+
let overBudgetReferenced = 0;
|
|
7709
|
+
const lfsPointerAssets = [];
|
|
6780
7710
|
for (const { file, assetPath, size } of orderedAssetCandidates) {
|
|
6781
7711
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
code: "limit-reached",
|
|
6785
|
-
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` : ""}.` : "."),
|
|
6786
|
-
source: file.relativePath
|
|
6787
|
-
});
|
|
7712
|
+
overBudgetAssets.push(file.relativePath);
|
|
7713
|
+
if (referencedAssetPaths.has(assetPath)) overBudgetReferenced++;
|
|
6788
7714
|
continue;
|
|
6789
7715
|
}
|
|
6790
7716
|
const content = readFileSync5(file.absolutePath);
|
|
6791
7717
|
if (isGitLfsPointer(content)) {
|
|
6792
|
-
|
|
6793
|
-
code: "unsupported-config",
|
|
6794
|
-
message: "This asset is a Git LFS pointer, not its real content (Git LFS was skipped during clone because the host has no git-lfs binary). Install git-lfs and re-run the migration, or add the real file to public/ manually.",
|
|
6795
|
-
source: file.relativePath
|
|
6796
|
-
});
|
|
7718
|
+
lfsPointerAssets.push(file.relativePath);
|
|
6797
7719
|
continue;
|
|
6798
7720
|
}
|
|
6799
7721
|
assets.push({ path: assetPath, content });
|
|
6800
7722
|
totalAssetBytes += size;
|
|
6801
7723
|
}
|
|
7724
|
+
if (overBudgetAssets.length > 0) {
|
|
7725
|
+
warnings.push({
|
|
7726
|
+
code: "limit-reached",
|
|
7727
|
+
message: `${overBudgetAssets.length} asset file${overBudgetAssets.length === 1 ? " was" : "s were"} not copied because files over ${MAX_ASSET_BYTES / 1e6} MB, or beyond ${MAX_TOTAL_ASSET_BYTES / 1e6} MB in total, are skipped: ${listAssetPaths(overBudgetAssets)}. ` + (overBudgetReferenced > 0 ? `${overBudgetReferenced} of them ${overBudgetReferenced === 1 ? "is" : "are"} used by pages, so those images will be broken until you copy ${overBudgetAssets.length === 1 ? "it" : "them"}. ` : "") + `Copy ${overBudgetAssets.length === 1 ? "it" : "them"} into public/ manually.`
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
if (lfsPointerAssets.length > 0) {
|
|
7731
|
+
warnings.push({
|
|
7732
|
+
code: "unsupported-config",
|
|
7733
|
+
message: `${lfsPointerAssets.length} asset file${lfsPointerAssets.length === 1 ? " is a Git LFS pointer" : "s are Git LFS pointers"}, not real content, and ${lfsPointerAssets.length === 1 ? "was" : "were"} not copied (Git LFS was skipped during clone because the host has no git-lfs binary): ${listAssetPaths(lfsPointerAssets)}. Install git-lfs and re-run the migration, or copy the real files into public/ manually.`
|
|
7734
|
+
});
|
|
7735
|
+
}
|
|
6802
7736
|
if (!discoveryBudgetApplied && files.length >= MAX_SOURCE_FILES) {
|
|
6803
7737
|
warnings.push({ code: "limit-reached", message: `Stopped scanning after ${MAX_SOURCE_FILES} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.` });
|
|
6804
7738
|
}
|
|
@@ -6887,8 +7821,8 @@ function migrateRepository(options) {
|
|
|
6887
7821
|
}
|
|
6888
7822
|
specFilenameSources.set(filename, spec.absolutePath);
|
|
6889
7823
|
const specContent = readFileSync5(spec.absolutePath);
|
|
6890
|
-
if (!assets.some((asset) => asset.path === filename)) {
|
|
6891
|
-
assets.push(
|
|
7824
|
+
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
7825
|
+
assets.push(specAsset(filename, specContent));
|
|
6892
7826
|
}
|
|
6893
7827
|
resolvedSpecs.push({ filename, tabLabel: section.tabLabel, content: specContent, routeSegments: section.routeSegments ?? [] });
|
|
6894
7828
|
continue;
|
|
@@ -6929,8 +7863,8 @@ function migrateRepository(options) {
|
|
|
6929
7863
|
} else if (platform === "mintlify") {
|
|
6930
7864
|
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
|
|
6931
7865
|
for (const spec of resolvedSpecs) {
|
|
6932
|
-
if (!assets.some((asset) => asset.path === spec.filename)) {
|
|
6933
|
-
assets.push(
|
|
7866
|
+
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
|
|
7867
|
+
assets.push(specAsset(spec.filename, spec.content));
|
|
6934
7868
|
}
|
|
6935
7869
|
}
|
|
6936
7870
|
if (resolvedSpecs.length > 0) {
|
|
@@ -6944,8 +7878,8 @@ function migrateRepository(options) {
|
|
|
6944
7878
|
const fallback = findOpenApi(files);
|
|
6945
7879
|
if (fallback) {
|
|
6946
7880
|
const filename = basename3(fallback.relativePath);
|
|
6947
|
-
if (!assets.some((asset) => asset.path === filename)) {
|
|
6948
|
-
assets.push(
|
|
7881
|
+
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
7882
|
+
assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
|
|
6949
7883
|
}
|
|
6950
7884
|
docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
|
|
6951
7885
|
}
|
|
@@ -7436,7 +8370,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
7436
8370
|
path: `src/content/${page.id}.mdx`,
|
|
7437
8371
|
content: renderPage(bundle, page)
|
|
7438
8372
|
})),
|
|
7439
|
-
...bundle.assets.map((asset) => ({ path: `public/${asset.path}`, content: asset.content })),
|
|
8373
|
+
...bundle.assets.map((asset) => ({ path: asset.projectRelative ? asset.path : `public/${asset.path}`, content: asset.content })),
|
|
7440
8374
|
...(bundle.componentFiles ?? []).flatMap((file) => file.path === "src/mdx/custom-components.tsx" && options.existingComponentRegistry !== void 0 && typeof file.content === "string" ? mergeComponentRegistry(options.existingComponentRegistry, file.content) : [file]),
|
|
7441
8375
|
{ path: "docs.json", content: `${JSON.stringify(config, null, 2)}
|
|
7442
8376
|
` }
|
|
@@ -7463,6 +8397,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
7463
8397
|
"Card",
|
|
7464
8398
|
"CardGroup",
|
|
7465
8399
|
"Check",
|
|
8400
|
+
"CodeBlock",
|
|
7466
8401
|
"CodeGroup",
|
|
7467
8402
|
"Color",
|
|
7468
8403
|
"Column",
|
|
@@ -7473,6 +8408,7 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
7473
8408
|
"Expandable",
|
|
7474
8409
|
"Agent",
|
|
7475
8410
|
"AgentPrompt",
|
|
8411
|
+
"Banner",
|
|
7476
8412
|
"BannerPreview",
|
|
7477
8413
|
"Embed",
|
|
7478
8414
|
"File",
|
|
@@ -7489,6 +8425,8 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
7489
8425
|
"InlineResponseExample",
|
|
7490
8426
|
"Latex",
|
|
7491
8427
|
"LegacyView",
|
|
8428
|
+
"LiteYouTubeEmbed",
|
|
8429
|
+
"MDX",
|
|
7492
8430
|
"Mermaid",
|
|
7493
8431
|
"Note",
|
|
7494
8432
|
"Panel",
|
|
@@ -7512,9 +8450,11 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
7512
8450
|
"Tooltip",
|
|
7513
8451
|
"Tree",
|
|
7514
8452
|
"Update",
|
|
8453
|
+
"Video",
|
|
7515
8454
|
"View",
|
|
7516
8455
|
"Visibility",
|
|
7517
|
-
"Warning"
|
|
8456
|
+
"Warning",
|
|
8457
|
+
"YouTube"
|
|
7518
8458
|
]);
|
|
7519
8459
|
var LOCALE_CODES = /* @__PURE__ */ new Set([
|
|
7520
8460
|
"ar",
|
|
@@ -9172,10 +10112,10 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
9172
10112
|
const body = await downloadSpec(reference.url, fetcher);
|
|
9173
10113
|
const operations = parseOpenApi(body, extension);
|
|
9174
10114
|
const filename = `openapi-${createHash2("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
9175
|
-
assets.push({ path: filename
|
|
10115
|
+
assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
9176
10116
|
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
9177
|
-
if (tab) tab.api = { source:
|
|
9178
|
-
else tabs
|
|
10117
|
+
if (tab) tab.api = { source: `openapi/${filename}` };
|
|
10118
|
+
else insertApiTab(tabs, { tab: reference.tabLabel ?? "API Reference", ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {}, api: { source: `openapi/${filename}` } }, reference.parentTab);
|
|
9179
10119
|
const routes = /* @__PURE__ */ new Map();
|
|
9180
10120
|
for (const operation of operations) {
|
|
9181
10121
|
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|